admin管理员组文章数量:1530815
本文介绍如何在STM32上实现升级功能,程序包括:bootloader和APP(也叫IAP, In Application Programming),基于STM32F103RCT6型号的MCU作为实验平台,以STM32CubeMX工具进行工程的建立及底层配置等工程,工程基于STM32 HAL库开发。
一、整体框架
整体上,在flash上烧写2个程序,bootloader和APP。
bootloader程序位于0x80000000处,即默认的程序启动地址;
APP程序则位于bootloader程序的往后某地址,空间大小需自行定义。
STM32F103RCT6的flash大小为256K。
1、flash分区概述
如我flash空间分配如下:
2、各分区说明
分区 | 地址 | 大小 | 作用 |
---|---|---|---|
bootloader | 0x8000 0000 | 32K | 校验、引导APP、升级 |
param | 0x8000 8000 | 16K | 参数区,保存一些断电不丢失参数 |
APP | 0x8000 C000 | 192K | 主应用程序 |
reserve | 0x8003 0000 | 16K | 预留区(可用作参数的备份,或其他) |
二、bootloader
bootloader的主要功能:校验数据、启动APP、升级APP。
bootloader的工作流程如下:
1、基础功能初始化(时钟、外设等);
主要进行一些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();
}
2、数据校验(参数区信息、APP程序的检验)
此步骤为后续启动过程读取一些基础参数,以及校验数据的准确性等。
读取参数区的参数:如我将升级相关的参数写在此分区(目前仅是启动标志,具体可自行定义),根据此标志来判断下一步该如何走。
/* 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程序数据是否正常,若校验失败则可尝试强制启动一次(启动失败也没关系,看门狗会自动复位)。
3、跳转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
APP的主要功能:除升级功能外的所有应用功能,及跳转至bootloader准备升级。
中断向量表重映射:由于APP程序的起始地址的变化,因此需要重映射,否则程序异常。
/* 宏定义 */
#define IS_NVIC_VECTTAB(VECTTAB) (((VECTTAB) == SRAM_BASE) || ((VECTTAB) == FLASH_BASE))
#define IS_NVIC_OFFSET(OFFSET) ((OFFSET) < 0x000FFFFF)
/****************************************************************
* Func :
* Desc : 中断向量表重映射
* Input : flash中断向量地址(一般为0x08000000U)、偏移地址(flash程序烧写地址相对NVIC_VectTab偏移)
* Output:
* Return:
*****************************************************************/
void NVIC_SetVectorTable(UINT32 NVIC_VectTab, UINT32 Offset)
{
/* Check the parameters */
assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
assert_param(IS_NVIC_OFFSET(Offset));
SCB->VTOR = NVIC_VectTab | (Offset & (UINT32)0x1FFFFF80);
}
跳转功能可用bootloader的跳转函数,也可直接重启reboot,两种方式都可达到目的---进入bootloader运行。
四、升级功能
升级,即是将新的程序数据替换旧的程序数据,因此,只需在程序数据所在区域擦除旧数据再写上新数据即可。
程序数据位置CPU内部flash区,因此需要以flash的读写擦等函数操作为基础,如下:
/****************************************************************
* Func :
* Desc : 读取CPU内部flash
* Input :
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_read(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{
if(pData == NULL)
return -1;
memcpy(pData, (INT8U *)unStartAddr, usSize);
return 0;
}
/****************************************************************
* Func :
* Desc : 写入CPU内部flash (要先erase才能写)
* Input :
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_write(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{
INT32 i = 0;
INT32 nRet = 0;
UINT16 usTemp1 = 0;
UINT16 usTemp2 = 0;
UINT16 usTempALL = 0;
if(usSize%2 != 0)
{
usSize += 1;
}
HAL_FLASH_Unlock(); // unlock
for(i=0; i<usSize/2; i++)
{
usTemp1 = *pData;
usTemp2 = *(pData+1);
usTempALL = ((usTemp1&0X00FF) | ((usTemp2<<8)&0XFF00));
//usTemp = ((*pData>>8)&0X00FF) | (*(pData+1)&0XFF00);
//usTemp = *(INT16U *)pData;/*这个会导致硬件崩溃*/
nRet = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, unStartAddr, usTempALL);
if(nRet != HAL_OK)
{
HAL_FLASH_Lock(); // lock
printf("ERROR: %s: program[%d %d] failed-code[%d]\n", __FUNCTION__, usTemp1, usTemp2, nRet);
return -1;
}
unStartAddr += 2;
pData += 2;
}
HAL_FLASH_Lock(); // lock
return 0;
}
/****************************************************************
* Func :
* Desc : 擦除CPU内部flash(整页)
* Input :
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_erase(UINT32 unStartAddr, UINT32 unEndAddr)
{
FLASH_EraseInitTypeDef stEraseInit;
UINT32 ucPageErr = 0;
UINT32 unTempAddr = 0;
INT32 nRet = 0;
HAL_FLASH_Unlock(); // unlock
for(unTempAddr=unStartAddr; unTempAddr<=unEndAddr; unTempAddr+=FLASH_PAGE_SIZE)
{
stEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
stEraseInit.PageAddress = unTempAddr;
stEraseInit.NbPages = 1;
nRet = HAL_FLASHEx_Erase(&stEraseInit, &ucPageErr);
if(nRet != HAL_OK)
{
HAL_FLASH_Lock();
return -1;
}
GPIO_feedDog();
}
HAL_FLASH_Lock(); // lock
return 0;
}
顺便说下,STM32内部flash库的保护问题,若不加保护,则内部程序可轻易被J-Flash等工具读出。因此,常用的措施是:对内部flash添加读写保护机制。锁定与解除函数如下:
/****************************************************************
* Func :
* Desc : 使能读保护函数
* Input :
* Output:
* Return:
*****************************************************************/
void cpuflash_enableReadProtect(void)
{
FLASH_OBProgramInitTypeDef OBInit;
__HAL_FLASH_PREFETCH_BUFFER_DISABLE();
HAL_FLASHEx_OBGetConfig(&OBInit);
if(OBInit.RDPLevel == OB_RDP_LEVEL_0)
{
printf("%s: ------------ set ----------\n", __FUNCTION__);
OBInit.OptionType = OPTIONBYTE_RDP;
OBInit.RDPLevel = OB_RDP_LEVEL_1;
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&OBInit);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
//HAL_FLASH_OB_Launch();
}
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
}
/****************************************************************
* Func :
* Desc : 失能读保护函数
* Input :
* Output:
* Return:
*****************************************************************/
void cpuflash_disableReadProtect(void)
{
FLASH_OBProgramInitTypeDef OBInit;
__HAL_FLASH_PREFETCH_BUFFER_DISABLE();
HAL_FLASHEx_OBGetConfig(&OBInit);
if(OBInit.RDPLevel == OB_RDP_LEVEL_1)
{
printf("%s: ------------ set ----------\n", __FUNCTION__);
OBInit.OptionType = OPTIONBYTE_RDP;
OBInit.RDPLevel = OB_RDP_LEVEL_0;
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&OBInit);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
//HAL_FLASH_OB_Launch();
}
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
}
以上为flash操作的相关函数。
有了flash操作的函数,升级操作也就顺理成章了,无非就是一包一包地接收数据,然后一包一包地写入flash。
其中,通信是通过串口,自己定义协议传送数据,将程序数据拆成N个包,一包一包地传输,例如设定3条协议,分别是:开始升级、发送升级数据、升级完成确认(自定义啦,暂不提供)。
处理一包升级数据的函数:
/****************************************************************
* Func :
* Desc : 初始化
* Input : usSeq-顺序号 pData-数据 usDatalen-长度 pstAPPHead-APP头信息(只第一包有用)
* Output:
* Return:
*****************************************************************/
int update_packDataHandle(UINT16 usSeq, UINT8 *pData, UINT16 usDatalen, struct APP_headinfo *pstAPPHead)
{
static INT16U usLastSeq = 0; // 上一次成功的序号
static INT32U unProgAddr = 0; // 烧写地址
INT32 nRet = 0;
if(usSeq == usLastSeq) // 重复包
return 0;
if(usSeq == 1)
{
printf("%s: ----------------- the first packet -----------------\n", __FUNCTION__);
/* erase APP area */
nRet = cpuflash_erase(FLASH_PAGE_APP_START, FLASH_PAGE_APP_START +pstAPPHead->unAPPSize);
if(nRet != 0)
return -1;
unProgAddr = FLASH_PAGE_APP_START;
}
if(unProgAddr<FLASH_PAGE_APP_START || unProgAddr>=FLASH_PAGE_APP_END+FLASH_PAGE_SIZE)
return -1;
nRet = cpuflash_write(unProgAddr, pData, usDatalen);
if(nRet != 0)
return -1;
/* 设置烧写地址偏移和升级包序号-0xFFFF表示最后一包 */
if(usLastSeq!=0xffff && usSeq!=0xffff)
{
unProgAddr += usDatalen;
}
usLastSeq = usSeq;
if (usLastSeq == 0xffff)
{
printf("%s: Recv the last packet data.\r\n", __FUNCTION__);
usLastSeq = 0;
}
return 0;
}
升级大概如此,不尽详细,欢迎吐槽。
附件:示例工程链接
https://github/zengzhaorong/stm32_IAP-demo
。
本文标签: 功能BootLoaderIAPapp
版权声明:本文标题:STM32具备升级功能的bootloader及APPIAP的实现 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dianzi/1725875263a1046583.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论