本文介绍如何在STM32上实现升级功能,程序包括:bootloader和APP(也叫IAP, In Application Programming),基于STM32F103RCT6型号的MCU作为实验平台,以STM32CubeMX工具进行工程的建立及底层配置等工程。
整体上,在flash上烧写2个程序,bootloader和APP。
bootloader程序位于0x80000000处,即默认的程序启动地址;
APP程序则位于bootloader程序的往后某地址,空间大小需自行定义。
STM32F103RCT6的flash大小为256K。
如我flash空间分配如下:
分区 | 地址 | 大小 | 作用 |
---|---|---|---|
bootloader | 0x8000 0000 | 32K | 校验、引导APP、升级 |
param | 0x8000 8000 | 16K | 参数区,保存一些断电不丢失参数 |
APP | 0x8000 C000 | 192K | 主应用程序 |
reserve | 0x8003 0000 | 16K | 预留区(可用作参数的备份,或其他) |
bootloader的主要功能:校验数据、启动APP、升级APP。
bootloader的工作流程如下:
主要进行一些BSP板级初始化:(仅供参考,因工程而异)
/******************************************************************/
/**
* BSP初始化函数\n
*
*/
/******************************************************************/
void BSP_Init(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
/* Initialize GPIO */
MX_GPIO_Init();
/* Initialize usart */
MX_UART_Init();
/* Initialize timer */
Timer_ParamInit();
MX_TIM3_Init();
MX_TIM4_Init();
}
此步骤为后续启动过程读取一些基础参数,以及校验数据的准确性等。
读取参数区的参数:如我将升级相关的参数写在此分区(目前仅是启动标志,具体可自行定义),根据此标志来判断下一步该如何走。
/* flash参数区信息结构 */
struct param_info
{
UINT16 usStartFlag; // 启动标志 0x0A-跳至APP 0x0B-等待升级 0x0F-已强制启动过
};
校验数据:如我将校验APP程序区的数据是否正常,采用CRC校验。
/******************************************************************/
/**
* 检验flash参数区函数\n
*
*/
/******************************************************************/
int check_paramInfo(UINT32 unParamAddr, UINT32 unAppAddr, UINT32 unAppRunOffset)
{
struct param_info stParams = {0};
INT32 nRetAppHead = 0;
/* 检查参数区-判断启动APP/升级? */
memset(&stParams, 0, sizeof(struct param_info));
cpuflash_read(unParamAddr, (UINT8 *)&stParams, sizeof(struct param_info));
if(stParams.usStartFlag == BOOT_FALG_NORMAL_RUNAPP) // 直接跳转APP
{
/* APP校验 */
nRetAppHead = check_AppInfo(unAppAddr, unAppRunOffset);
if(nRetAppHead == 0)
{
HAL_TIM_Base_Stop_IT(&htim3); // ??? 不关中断跳转不了-原因未明
loadAPP(unAppAddr +unAppRunOffset);
}
else
{
printf("%s: check_AppInfo failed, [may be crc error] !\n", __FUNCTION__);
}
}
/* 若参数/APP头信息错误-尝试强制启动 */
if((stParams.usStartFlag!=BOOT_FALG_WAIT_UPGRADE && stParams.usStartFlag!=BOOT_FALG_FORCE_RUNAPP) || nRetAppHead!=0)
{
force_loadAPP(unParamAddr, unAppAddr, unAppRunOffset);
}
else
printf("%s: ------------ wait to upgrade ------------\n", __FUNCTION__);
return 0;
}
流程:先读取参数,判断下一步是直接启动APP还是留在bootloader等待升级。若是启动APP则校验APP程序数据是否正常,若校验失败则可尝试强制启动一次(启动失败也没关系,看门狗会自动复位)。
如何跳转至APP呢?跳转函数:
/*****************************************************************/
/**
* 加载APP \n
*
*/
/******************************************************************/
void loadAPP(INT32U unLoadAddr)
{
void (*fnJump2APP)(void);
INT32U unJumpAddr;
if(((*(__IO INT32U *)unLoadAddr) & 0x2FFE0000) == 0x20000000) /* 检查栈顶地址是否合法 */
{
printf("%s: ----------------------> run APP addr: 0x%x\r\n", __FUNCTION__, unLoadAddr);
/* 用户代码区第5~8字节为程序开始地址(复位地址) */
unJumpAddr = *(__IO INT32U *)(unLoadAddr + 4);
fnJump2APP = (void (*)(void))unJumpAddr;
/* 初始化APP堆栈指针(用户代码区的前4个字节用于存放栈顶地址) */
__set_MSP(*(__IO INT32U *)unLoadAddr);
fnJump2APP();
}
else
{
printf("ERROR: %s: Stack top address is not valid! Can not run func!\r\n", __FUNCTION__);
while(1);
}
}
升级,即读取到的参数标志为升级状态,则留在bootloader等等接收升级包数据(APP程序数据),并将其写入flash的APP分区。(具体见程序)
APP的主要功能:除升级功能外的所有应用功能,及跳转至bootloader准备升级。
中断向量表重映射:由于APP程序的起始地址的变化,因此需要重映射,否则程序异常。
跳转功能可用bootloader的跳转函数,也可直接重启reboot,两种方式都可达到目的---进入bootloader运行。
未完待续。。。