GD32E230C8T6_OTA升级

本文详细介绍了GD32E230C8T6芯片的OTA固件升级流程,包括Bootloader和APP分区的设置、程序跳转、中断向量表修改以及Flash内存操作。通过分析升级原理和代码实现,展示了如何进行固件的在线更新,确保在升级过程中遇到故障时能避免设备损坏。
摘要由CSDN通过智能技术生成

运行环境

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此图出自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 分区下载的起始地址,对中断向量地址偏移做定义。按照协议规定,具体如下:
在这里插入图片描述

  1. 修改中断向量表
nvic_vector_table_set (BOOTLOADER,0x3000);

APP 编译设置

因为硬件 FLASH 空间限定,我们需要对 APP 分区的固件大小做严格的限制。本例程可允许的最大固件为 26KB。需要升级的新固件同样最大可支持 26KB。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

重点步骤

  1. 程序的跳转
  2. APP中的修改中断向量表
  3. 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);               // 复位USART0
    usart_baudrate_set(USART0, 115200); // 设置波特率为115200
    usart_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); // 将数据写入Flash
                flash_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(); // 初始化USART
    while (1) {
        if (recv_total_len == 0) {
            timeout++;
            if (timeout > 1000000) {    // 超时处理
                timeout = 0;
                state = 0;
            }
        } else {
            timeout = 0;
        }
    }
    return 0;
}
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Quieeeet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值