GD32E230C8T6_OTA升级
- 运行环境
- 简介
- 程序的起始地址
- 进行分区
- 总体流程图
- Bootloader 程序
- Bootloader 编译设置
- APP 分区部分
- APP 编译设置
- 重点步骤
运行环境
1.Windows10
2. Keil5(MDK5) Version 5.27.0.0
3. MCU GD32E230C8T6
简介
本例程主要分析在线升级(OTA)的实现过程, 主要是针对IAP OTA从原理分析, 分区划分, 到代码编写和实验验证等过程阐述这一过程. 和大家以前理解OTA的原理.
作者也是通过已有的网上DEMO来理解编写,所以只是提供个人的理解思路。有需要优化的地方望大家指教。
程序的起始地址
正常情况下, 我们写的程序都是放在GD32片内Flash中(暂不考虑外扩Flash). 我们写的代码最终会变成二进制文件, 放进Flash中 。起始地址在0x08000000。
进行分区
64KBFlash MCU分区方案
Flash 空间划分出 4 个区域:Bootloader、FLAG、APP 分区、APPBAK 分区。
总体流程图
参考此流程图(可更具实际情况自己更改)
先执行BootLoader程序, 先去检查FLAG区有没有升级标志, 如果有就将APPBAK区(备份区)的程序拷贝到APP区, 然后再跳转去执行APP的程序.
然后执行APP程序, 因为BootLoader和APP这两个程序的向量表不一样, 所以跳转到APP之后第一步是先去更改程序的向量表. 然后再去执行其他的应用程序.
在应用程序里面会加入程序升级的部分, 这部分主要工作是拿到升级程序, 然后将他们放到APPBAK区(备份区), 以便下次启动的时候通过BootLoader更新APP的程序. 流程图如下图所示:
此图出自https://gitee.com/leafguo
Bootloader 程序
Bootloader 的主要职能是在有升级任务的时候将 APPBAK 分区里面的固件拷贝到 APP 区域。当然,这期间需要做很多的工作,比如升级失败的容错等等。具体的流程可以参考图示。需要注意的是,在校验 MD5 正确后开始搬运固件数据期间,MCU 出现故障(包括突然断电),MCU 应发生复位操作(FLAG 区域数据未破坏),复位后重新开始执行 Bootloader,从而避免 MCU 刷成板砖。(可以根据难度进行裁剪)
- /* 程序跳转函数 */
void execute_user_code(void)
{uint32_t JumpAddress;JumpAddress = *(__IO uint32_t*) (APP_CODE_OFFSET+ 4);usart_disable(USART0);__set_MSP(*(__IO uint32_t*) APP_CODE_OFFSET);(*( void (*)( ) )JumpAddress) ();
}
在需要跳转的地方执行这个函数就可以了execute_user_code();
- 所需要的宏定义
#define BOOTLOADER (0x08000000) // 11KB //BootLoader起始地址 #define UPDATE_FLAG (0x08002C00) //升级标志地址
#define UPDATE_FLAG_MAX_SIZE (0x0400) // 1KB#define APP_CODE_OFFSET (0x08003000) //APP起始地址
#define APP_CODE_SIZE (0x6800) // 26KB #define UPDATE_CODE_OFFSET (0x08009800) //APPBAK区(备份区)起始地址
#define UPDATE_CODE_SIZE (0x6800) // 26KB #define FLASH_SECTOR_SIZE (0x400) // 1KB
Bootloader 编译设置
BootLoader的代码默认是最开始的所以不需要特别设置代码的下载位置
按照下图, 修改擦除方式为Erase Sectors, 大小限制在0X2C00(11KB)
APP 分区部分
做好 Bootloader 工作后,我们开始写 APP 分区的代码。APP 分区固件的编写要注意硬件版本号和软件版本号,软件版号作为升级迭代很重要的标志。APP 分区代码我们只需要增加从网络接口接收到新固件到APPBAK区(备份区)功能,并给更新标志区写入相应数据。需要注意的是,中断向量地址偏移的定义,我们 APP 分区实际的偏移是 0x3000。如果不修改,APP 分区也可以正常加载运行,但是不会相应中断。所以,我们需要根据实际 APP 分区下载的起始地址,对中断向量地址偏移做定义。按照协议规定,具体如下:
- 修改中断向量表
nvic_vector_table_set (BOOTLOADER,0x3000);
APP 编译设置
因为硬件 FLASH 空间限定,我们需要对 APP 分区的固件大小做严格的限制。本例程可允许的最大固件为 26KB。需要升级的新固件同样最大可支持 26KB。
重点步骤
- 程序的跳转
- APP中的修改中断向量表
- Flash内存的操作(重点)
int main(void)
{systick_config();usart_gpio();usart_init();uint32_t temp;while(1){printf("current version:"BOOTLOAD_VERSION"\n");//读取升级标志temp=option_byte_value_get(UPDATE_FLAG);//判断升级标志并把APPBAK区固件拷贝到APP区if(43690 == temp){printf("Enter the firmware upgrade process\n");copy_updata();//先Flash解锁 清除标志 再擦除 进行拷贝//完成拷贝后清除标志上锁 fmc_unlock();fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);fmc_page_erase(UPDATE_FLAG);fmc_lock();usart_disable(USART0); //程序跳转前失能中断execute_user_code();}else{printf("Firmware upgrade failed, jump to app\n");execute_user_code();}}
}
中间的Flash操作各MCU大体相同 故参考相关例程
此文章仅个人的思路及理解,如有需要优化的地方大家提出,共同优化。
更新代码参考
#include "gd32e23x.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFF_SIZE 1024
uint32_t flash_address = 0x08002000; // Flash起始地址
uint8_t recv_buff[BUFF_SIZE]; // 接收缓冲区
uint32_t recv_len = 0; // 已接收数据长度
uint32_t recv_total_len = 0; // 接收数据总长度
uint16_t crc = 0; // 接收数据的校验和
uint32_t timeout = 0; // 超时计数器
// USART初始化函数
void usart_init(void)
{rcu_periph_clock_enable(RCU_USART0); // 使能USART0时钟rcu_periph_clock_enable(RCU_GPIOA); // 使能GPIOA时钟gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); // PA9作为USART0_TX引脚输出gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10); // PA10作为USART0_RX引脚输入usart_deinit(USART0); // 复位USART0usart_baudrate_set(USART0, 115200); // 设置波特率为115200usart_word_length_set(USART0, USART_WL_8BIT); // 设置数据位为8位usart_stop_bit_set(USART0, USART_STB_1BIT); // 设置停止位为1位usart_parity_config(USART0, USART_PM_NONE); // 设置无奇偶校验usart_receive_config(USART0, USART_RECEIVE_ENABLE); // 使能接收usart_enable(USART0); // 使能USART0
}
// USART发送数据函数
void usart_send_data(uint8_t *data, uint32_t len)
{uint32_t i;for (i = 0; i < len; i++) {usart_data_transmit(USART0, data[i]); // 发送数据while (usart_flag_get(USART0, USART_FLAG_TBE) == RESET); // 等待发送完成}
}
// 接收数据回调函数
void usart_receive_data(uint8_t ch)
{static uint8_t prev_ch = 0;static uint8_t state = 0;switch (state) {case 0:if (ch == 0x7E && prev_ch == 0x7E) {state = 1;crc = 0xFFFF;recv_len = 0;recv_total_len = 0;timeout = 0;}break;case 1:if (ch == 0x7E) {state = 2;} else {recv_buff[recv_len++] = ch;recv_total_len++;crc = crc16_update(crc, ch);}break;case 2:if (ch == 0x7E) {state = 1;if (crc == 0) {// 接收到一帧完整的OTA数据包flash_write_data(flash_address, recv_buff, recv_len); // 将数据写入Flashflash_address += recv_len;usart_send_data("OK", 2); // 发送确认回复} else {usart_send_data("ERR", 3); // 发送错误回复}} else {state = 0;}break;}prev_ch = ch;
}
int main(void)
{usart_init(); // 初始化USARTwhile (1) {if (recv_total_len == 0) {timeout++;if (timeout > 1000000) { // 超时处理timeout = 0;state = 0;}} else {timeout = 0;}}return 0;
}