GD32f103C8T6 IAP 升级教程

参考stm32的IAP升级原理

IAP测试源码

GD32和stm32的内核都是一样的,又因为IAP升级主要涉及升级的路径之和内核先关,所以gd32和stm32升级IAP升级是一样的。

0.升级原理

在这里插入图片描述

GD32的内部(FLASH)地址起始于0x08000000,一般情况下,程序就从此地址开始写入,此外GD32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是0x08000004,当中断来临,GD32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。在上图中,GD32在复位后,先从0x08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示:在复位中断服务程序执行完之后,会跳转到我们的main函数,如图标号②所示;而我们的main函数一般都是一个死循环,在main函数执行过程中,如果收到中断请求(发生中断),此时GD32强制将PC指针指向中断向量表处,如图标号③所示;然后根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回main函数执行,如图⑤所示。

当加入IAP程序之后,程序运行流程如图下所示:

在这里插入图片描述

在上面所示流程图中,GD32复位后,还是从0x08000004地址取出复位中断向量的地址并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAP的main函数,如图标号①所示;在执行IAP以后(即将新的APP代码写入GD32的FLASH,灰底部分。新程序的复位中断向量起始地址0x08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数,如图标号②和③所示,同样main函数为一个死循环,并且注意到此时GD32的FLASH,在不同位置上,共有两个中断向量表。在main函数执行过程中,如果CPU得到一个中断请求,PC指针仍强制跳转到地址0x08000004中断向量处,而不是新程序的中断向量处,如图④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回main函数继续运行,如标号⑥所示

1.升级条件/环境

gd32f103c8t6 ROM 地址范围ROM 地址范围: 0x800 0000~0x800 FFFF 合计64K

扇区大小 1024Byte

falsh 空间分配设置

boot loader 分配30K空间 flash 0x8000000-0x80077ff
user bin 分配34k空间 flash 0x8007800-0x800ffff

2.bin程序

app程序功能是 LED0间隔1s闪烁,每秒打印app is run

1.设置flash地址和flash大小

在这里插入图片描述

2.添加生成bin文件地址

注意:下边命令中涉及到的路径需要和实际路径向匹配,涉及3个路径

  1. fromelf.exe 文件路径(一般都和我一样的)
  2. GD32F103C8T6.bin 文件路径 保存bin文件路径
  3. GD32F103C8T6.axf mkd生成axf的文件路径
C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o GD32F103C8T6.bin  .\Objects\GD32F103C8T6.axf

在这里插入图片描述

3.修改中断偏移地址

在这里插入图片描述

2.bootload 程序

使用串口的中断+空闲中断方式接收数据,然后使用按键作为触发动作写入bin文件内容到flash,

最后跳转至APP程序;

1.中断接收程序

#ifndef __GD32_BSP_UART_H__
#define __GD32_BSP_UART_H__


/*
	串口初始化
*/

#include "stdint.h"
#define CACHE_NUM 1024*10

extern volatile uint16_t rxcount ; 		//已经接收到的数据数量
extern volatile uint8_t idle_or_full; //数据接收完成标志
extern uint8_t rxbuffer[CACHE_NUM];		//接收数据缓冲区


/*! 
    串口基本配置
*/
void usart_base_init(void);
/*
	使能中断相关配置
*/
void enable_uart_interrupt(void);

#endif

#include "gd32f10x.h"
#include "stdio.h"
#include "bsp_uart.h"

uint8_t rxbuffer[CACHE_NUM];
volatile uint16_t rxcount = 0; 
volatile uint8_t idle_or_full=0;



/* 重定向printf函数 */
int fputc(int ch, FILE *f)
{
    usart_data_transmit(USART0, (uint8_t)ch);
	int cnt=1000;
    while(RESET == usart_flag_get(USART0, USART_FLAG_TBE) &&cnt--);
    return ch;
}

/*! 
    串口基本配置
*/
/*基本初始化函数*/
void usart_base_init(void)
{
    /* 使能串口时钟 */
    rcu_periph_clock_enable(RCU_USART0);
    
    #if defined USART0_REMAP //如果串口引脚需要重映射
        /* 使能gpio时钟  */
        rcu_periph_clock_enable(RCU_GPIOB);
        rcu_periph_clock_enable(RCU_AF); //打开重映射时钟
        /* USART0 串口重映射使能 */
        gpio_pin_remap_config(GPIO_USART0_REMAP, ENABLE);
				/* 发送引脚初始化 */
        gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
        /* 接收引脚初始化 */
        gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
    #else
        /* 使能gpio时钟 */
        rcu_periph_clock_enable(RCU_GPIOA);
        
	/* gpio IO 初始化发送引脚 */
        gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
        /* 初始化接收引脚 */
        gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
    #endif
    
    /* USART configure 串口参数初始化 */
    usart_deinit(USART0);
		//设置波特率
    usart_baudrate_set(USART0, 115200U);
		//设置数据长度
    usart_word_length_set(USART0, USART_WL_8BIT);
		//设置停止位
    usart_stop_bit_set(USART0, USART_STB_1BIT);
		//设置检验位
    usart_parity_config(USART0, USART_PM_NONE);
		//硬件流管理 都关闭
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
    usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
		//串口接收使能
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
		//串口发送使能
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
		//使能串口
    usart_enable(USART0);
}


/*
	使能中断相关配置
*/
/*
	使能串口中断
*/
void enable_uart_interrupt(void)
{
		/*中断管理器使能,并分配优先级*/
		 nvic_irq_enable(USART0_IRQn, 1, 1);
		/*清除中断标志*/
		usart_interrupt_flag_clear(USART0, USART_INT_RBNE);
		usart_interrupt_flag_clear(USART0, USART_INT_IDLE);
    /* 使能串口中断 */  
		//usart_interrupt_enable(USART0, USART_INT_TBE);//发送为空中断
		usart_interrupt_enable(USART0, USART_INT_RBNE);//接收不为空中断
		usart_interrupt_enable(USART0, USART_INT_IDLE);//空闲中断
}


/*
	串口0中断处理函数
*/
/*
	串口0中断处理函数
*/
void USART0_IRQHandler(void)
{
    if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE))//读取缓冲区不为空
		{
        rxbuffer[rxcount++] = usart_data_receive(USART0);//读取数据
		}

		else if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE))//发送缓冲区为控制
		{
			usart_data_receive(USART0);//仅仅是为了清除空闲中断标记
			//if(rxbuffer[rxcount-2]==0x0d && rxbuffer[rxcount-1]==0x0a)
				idle_or_full=1;//表示接收到一帧数据
		}
}


2.flash跳转程序

#ifndef __GD32_FLASH_H__
#define __GD32_FLASH_H__
#include "stdint.h"
#include "stdio.h"
/*
	写入flash数据,函数内部做了4字节对齐操作
*/

void write_flash(uint32_t write_addr, uint32_t *data, int len);
/*
	读取flash数据,函数内部做了4字节对齐操作
*/
void read_4Btye(uint32_t read_addr, uint32_t *data, uint32_t len);
#endif

#include "gd32f10x.h"
#include <stdio.h>
#include "bsp_flash.h"
/*
	对于主存储闪存容量不多于512KB的GD32F10x_CL和GD32F10x_HD,只使用了bank0;
	对 于 GD32F10x_MD , 闪 存 页 大 小 为 1KB 。
	GD32F10x_CL 和 GD32F10x_HD ,GD32F10x_XD, bank0的闪存页大小为2KB, bank1的闪存页大小为4KB; 
*/
/*页大小*/
#define FMC_PAGE_SIZE           ((uint16_t)0x400U)

/*
	读取flash数据,函数内部做了4字节对齐操作
*/
void read_4Btye(uint32_t read_addr, uint32_t *data, uint32_t len)
{
    int dest_addr;
    dest_addr = read_addr & 0xfffffffc;//4字节对齐
    dest_addr += read_addr & 0x03 > 0 ? 4 : 0;
    int real_len = len >> 2;
    uint32_t *addr = (uint32_t*)dest_addr;

    for(int i = 0; i < real_len; i++)
    {
        *(data + i) = *(addr + i);
    }
}

/*
	检查是否需要擦除页
	addr:待检查页起始地址

*/
int check_ease(int start_addr )
{
    //指着类型转换
    uint32_t *addr = (uint32_t*)start_addr;

    for(int i = 0; i<FMC_PAGE_SIZE >> 2; i++)
    {
        if(*(addr + i) != 0xffffffff)
        {
            return 1;
        }
    }

    return 0;
}

/*
	擦除需要使用的扇区,并保存扇区前半部分没有使用的部分
*/
void ease_flash(uint32_t write_addr, int page_num)
{
    int i = 0;
    //当前页的偏移地址
    uint32_t page_offset = write_addr & (FMC_PAGE_SIZE - 1);
    page_offset = page_offset >> 2; //换算为4字节
    //页起始地址
    uint32_t start_addr  = write_addr & (0xffffffff - FMC_PAGE_SIZE + 1);
    //指着类型转换
    uint32_t *addr = (uint32_t*)start_addr;

    uint32_t *buff = (uint32_t*)malloc(FMC_PAGE_SIZE);
    //将不操作的空间保存起来

    if(page_offset)
    {
        for( i = 0; i < page_offset; i++)
        {
            buff[i] = *(addr + i);
        }
    }

    uint32_t erase_counter;

    /* 解锁flash */
    fmc_unlock();


    //清除操作结束标志
    fmc_flag_clear(FMC_FLAG_BANK0_END);
    //清除擦除/错误标志
    fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
    //清楚页编程错误标志
    fmc_flag_clear(FMC_FLAG_BANK0_PGERR);

    /* 擦除使用到的页 */
    for(erase_counter = 0; erase_counter < page_num; erase_counter++)
    {
        //擦除指定的页,参数页地址
        if(check_ease(start_addr + FMC_PAGE_SIZE * erase_counter))
        {
            fmc_page_erase(start_addr + FMC_PAGE_SIZE * erase_counter);
            fmc_flag_clear(FMC_FLAG_BANK0_END);
            fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
            fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
        }
    }

		//写入扇区前半部分没有使用的空间
    if(page_offset)
    {
        for( i = 0; i < page_offset; i++)
        {
            //对flash编程,也就是写数据,每次写入1个字=4个字节
            fmc_word_program(start_addr + (i * 4), *(buff + i));
            fmc_flag_clear(FMC_FLAG_BANK0_END);
            fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
            fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
        }
    }

    /* flash加锁,禁止编程 */
    fmc_lock();
    free(buff);
}

/*
	写入flash数据,函数内部做了4字节对齐操作
*/

void write_flash(uint32_t write_addr, uint32_t *data, int len)
{

    int dest_addr;
    dest_addr = write_addr & 0xfffffffc;//4字节对齐
    dest_addr += write_addr & 0x03 > 0 ? 4 : 0;

    uint32_t page_num = len / FMC_PAGE_SIZE; //存放数据需要的扇区大小
    page_num += len % FMC_PAGE_SIZE ? 1 : 0;//
    //擦除扇区
    ease_flash(dest_addr, page_num);
    int len_4Byte = len >> 2;
    /* 解锁 */
    fmc_unlock();

    for(int i = 0; i < len_4Byte; i++)
    {

        //对flash编程,也就是写数据,每次写入1个字=4个字节
        fmc_word_program(dest_addr + i * 4, *(data + i));
        fmc_flag_clear(FMC_FLAG_BANK0_END);
        fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
        fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
    }

    /* 加锁*/
    fmc_lock();


}

3.APP程序跳转

#ifndef _IAP_
#define _IAP_

#include "gd32f10x.h"
#include "stdint.h"

#define FLASH_APP1_ADDR     0x08007800      //第一个应用程序起始地址(存放在FLASH)

typedef  void (*iapfun)(void);              //定义一个函数类型的参数.
void MSR_MSP(uint32_t addr);    //设置堆栈地址


void iap_load_app(uint32_t appxaddr);           //执行flash里面的app程序

#endif
#include "bsp_iap_bootloader.h"

//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(uint32_t addr) 
{
    MSR MSP, r0 			//set Main Stack value
    BX r14
}

iapfun jump2app; 
  
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(uint32_t appxaddr)
{
	if(((*(uint32_t*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		jump2app=(iapfun)*(uint32_t*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(uint32_t*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}		 


4.主函数

#include "gd32f10x.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "string.h"
#include "bsp_uart.h"
#include "bsp_exti.h"
#include "gd32f103c_sys.h"
#include "bsp_flash.h"
#include "bsp_iap_bootloader.h"
#define LED0 PBout(13)
#define LED1 PBout(14)
#define LED2 PBout(15)
#define LED3 PAout(8)

int main(void)
{
		int bin_size;
    /* 配置系统时钟 */
    systick_config();
    //设置中断分组
    nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
    /* gpio时钟使能*/
    rcu_periph_clock_enable(RCU_GPIOB);
    //rcu_periph_clock_enable(RCU_GPIOA);
    // LED gpio 初始化
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15);
    //gpio_init(GPIOA,GPIO_MODE_OUT_PP,GPIO_OSPEED_50MHZ,GPIO_PIN_8);
    //串口初始化
    usart_base_init();
    //使能接收中断
    enable_uart_interrupt();
    /*GPIO初始化函数*/
    exit_gpio_init();
		//外部中断初始化
    input_exti_init();
    while (1)
    {
        LED2 = !LED2;
        delay_1ms(50);
        if (idle_or_full)
        {
            printf("read len = %d:", rxcount);
            for (int i = 0; i < rxcount; i++)
            {
                printf("%02x ", (int)rxbuffer[i]);
            }
            printf("\r\n");
            idle_or_full = 0;
						bin_size  = rxcount;
            rxcount = 0;
						
        }
				if(exti_flag)
				{
					exti_flag=0;
					write_flash(FLASH_APP1_ADDR,(uint32_t*)rxbuffer,bin_size);
					printf("wirte app\r\n");
					if(((*(uint32_t*)(FLASH_APP1_ADDR+4))&0xFF000000)==FLASH_BASE)//判断是否为0X08XXXXXX.
					{	 
						iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
					}
					printf("update app\r\n");
					
				}

    }
}




其他辅助:

文件发送工具XCOM:
在这里插入图片描述

测试结果

在这里插入图片描述