1.1 mcu设备远程升级程序的应用场景
在远程无线监控设备中,IAP程序远程升级功能起着重要作用。比如安装在监控区域的设备需要进行功能升级或者Bug修改,远程监控端可以通过WIFI、蓝牙、lora、以太网等模块将工程编译的bin文件远程发送给现场监控设备,从而避免人工拆除再重新烧录程序,节约大量人工、时间成本。本文主要介绍如何基于GD32mcu实现IAP(程序远程升级)设计思路。
1.2 MCU IAP实现基本原理
IAP程序远程升级的本质原理是将现场监控设备mcu的flash地址空间划分为两部分,一部分作为接收远程监控端的bin文件的bootloader区域,一部分作为应用层功能user app区域。bootloader区主要是通过无线设备将接收到bin文件的内容从user app区域的起始flash地址按顺序写入,接收完成后,修改SP、PC指针为user app区域,然后再跳转到user app区域执行应用层程序。本质就是通过bootloader区域对user app 的flash code区进行改写,从而实现远程设备功能升级或bug修改。mcu设备flash区域划分框图如下所示。
1.3 IAP keil工程设置
本文IAP实现是基于GD32F350芯片,通过查阅GD32用户手册可知F350芯片flash大小为128k,ram大小为16k,故我们将flash前28k、ram前6k作为bootloader区域,flash后面的100k、ram的10k作为userapp区域。如果mcu内部的flash空间足够大,可以将user区分为两部分,其中一部分作为备份区域。这样做的目的可以防止远程设备在接收监控端功能升级时出现断电情况,从而再次上电PC跳转到user区会导致程序跑飞,故每次都更新user备份区的代码,备份区的地址范围不是固定的,在user两个区域之间互换。远程mcu设备每次上电后,从bootloader区域开始执行,usb、usart、以太网、lora、wifi等设备检测是否有远程监控端更新user区域代码要求,有则接收监控端bin文件更新user code区,无则关闭所有中断跳转到user区执行程序。通过对bootloader区域flash某一地址写1或者0,判断是更新user区或者user备份区代码。按照首次user区划分的地址范围,第一次更新user备份区域,则将程序跳转到user 备份区域执行,首次的user备份区变为user app区域,原user app区域变为备份区。每次跳转到bootloader区,在跳转到user区时,读取flash某地址的值,决定跳转的user区。本文实现无user备份区,仅提供一个实现思想。bootloader区keil工程设置如下图:
bootloader 区域主函数代码如下:
#include "gd32f3x0.h"
#include <stdio.h>
#include "gd32f350r_eval.h"
#include "main.h"
#include "systick.h"
void (*user_code)();//声明一个函数指针
#define user_start 0x8007000 //user code区起始地址
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
uint8_t num=0;
/*configure EVAL_COM1*/
gd_eval_com_init(EVAL_COM);
/*------------------------
* 1、usb/usart/lora/wifi 等端口初始化
* 2、判断是否接收更新user code代码请求,是则接收更新
*/
/* 判断user_start地址内容是否为user code区域的SP指针*/
if(((*(uint32_t*)user_start) & 0x2ffe0000)==0x20000000){
/*起始地址加4的内容则为PC指针起始地址,将0x8007004转换为uint32*指针再取地址内容,则PC指针地址,
再将PC指针地址强制转换为函数指针并赋值给user_code。
*/
user_code=(void(*)())*(uint32_t*)(user_start+4);
/*设置栈顶指针为user 区栈顶 */
__set_MSP(*(uint32_t*)user_start);
/*跳转前关闭所有中断*/
__disable_irq();
/*-----跳转到用户代码区执行程序*/
user_code();
}
while(1){
}
}
user区 keil工程设置为如下图:
需要注意的是在user code keil工程中需要修改中断向量的偏移地址为0x7000,代码如下:
#define NVIC_VECTTAB_FLASH 0x08000000
#define VECT_TAB_OFFSET 0x7000
nvic_vector_table_set(NVIC_VECTTAB_FLASH,VECT_TAB_OFFSET);//该函数为GD 库函数,不同的芯片不一样,属于ARM cortex内核相关操作
user main函数代码如下所示:
#include "gd32f3x0.h"
#include <stdio.h>
#include "gd32f350r_eval.h"
#include "main.h"
#include "systick.h"
void (*bootloader_code)();//声明一个函数指针
#define bootloader_start 0x8000000 //bootloader 区起始地址
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
uint8_t num=0;
/*configure EVAL_COM1*/
gd_eval_com_init(EVAL_COM);
printf("skip user code success!!");
/*------------------------
* user app 代码
*/
/*----------再次跳转到bootloader区是否有更新user code 请求----------*/
/* 判断bootloader地址内容是否为bootloader code区域的SP指针*/
if(((*(uint32_t*)bootloader_start) & 0x2ffe0000)==0x20000000){
/*起始地址加4的内容则为PC指针起始地址,将0x8000004转换为uint32*指针再取地址内容,则PC指针地址,
再将PC指针地址强制转换为函数指针并赋值给user_code。
*/
bootloader_code=(void(*)())*(uint32_t*)(bootloader_start+4);
/*设置栈顶指针为user 区栈顶 */
__set_MSP(*(uint32_t*)bootloader_start);
/*跳转前关闭所有中断*/
__disable_irq();
/*-----跳转到bootloader代码区执行程序*/
bootloader_code();
}
while(1){
}
}
本文是通过user code区代码轮询来检测远程监控端是否有更新需求,实际项目中代码更新不会这么频繁,可以通过定时器设定每隔一定时间 跳转到bootloader区执行。基于GD32 mcu IAP程序远程升级实现大体框架就这些,具体项目应用还要结合项目需求,比如现场监控设备与远程监控端更新代码的通信协议需要自己定,用那种校验方式等等。