最近群里好几个兄弟在问IAP怎么搞,说什么”跳转过去就跑飞了”、“升级完断言错误”之类的话。我自己在项目里也踩过不少坑,今天抽空把完整的实现思路整理一下,希望能帮到正在做这个的朋友。
先说下背景:我这次用的是STM32F103RCT6,Flash容量256K,RAM 64K。项目需求是通过UART升级固件,升级完后自动跳转到App运行。
一、Flash分区规划
这是第一步,也是最容易出问题的地方。我的分区方案是这样的:
• Bootloader区:0x08000000 ~ 0x08007FFF(32K)
• App区:0x08008000 ~ 0x0803FFFF(224K)
• 参数存储区:0x08040000 ~ 0x08040FFF(4K,用来存校准参数、版本信息等)
注意STM32F1的Flash是按页操作的,每页2K。所以32K的Bootloader区刚好是16页。App区224K = 112页。分区的时候一定要对齐页边界,不然擦除的时候会出问题。这个我吃过亏,之前没对齐,擦除的时候把Bootloader的代码也给擦了,板子直接变砖,花了一下午才恢复过来。
二、Bootloader代码框架
Bootloader做的事情其实就三件:
1. 初始化系统(时钟、串口、DMA等)
2. 检查是否需要升级(看某个标志位或者超时)
3. 如果需要升级,通过串口把新固件收进来写到App区域;不需要就跳转到App
关键代码贴一下:
typedef void (*pFunction)(void);
void JumpToApp(uint32_t app_addr)
{
uint32_t jump_addr;
pFunction jump_to_application;
// 检查App区起始地址的Stack Pointer是否合法
// STM32的SP存放在Flash起始地址,MSP必须指向SRAM区域
if(((*(__IO uint32_t*)app_addr) & 0xFF000000) == 0x20000000)
{
// 关闭所有中断
__disable_irq();
// 重置SysTick
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
// 设置向量表偏移 —— 这步最关键
SCB->VTOR = app_addr;
// 初始化主堆栈
__set_MSP(*(__IO uint32_t*)app_addr);
// 获取复位中断服务程序地址
jump_addr = *(__IO uint32_t*)(app_addr + 4);
// 跳转
jump_to_application = (pFunction)jump_addr;
jump_to_application();
}
}
这里有个大坑:向量表偏移!STM32启动后默认从0x08000000开始取向量表,如果你把App的向量表放在0x08008000,跳转过去之后中断会找不到服务程序,直接HardFault。所以必须在跳转前设置SCB->VTOR寄存器。
三、App端的配置
App的启动文件里要做两件事:
1. 在main函数最开头设置向量表偏移:
SCB->VTOR = FLASH_BASE | 0x08008000; // 向量表偏移量
2. Linker脚本(.ld文件)也要改,确保App的代码和数据都放在0x08008000之后。Keil里就是在Options->Flash里改Start和Size。
四、通信协议设计
我用的协议比较简单,适合串口升级的场景:
• 帧头(2B) + 命令字(1B) + 长度(2B) + 数据(N) + 校验和(1B)
• 升级流程:上位机先发升级请求 -> Bootloader回复ACK -> 上位机开始发数据块 -> Bootloader每收到一页擦除并写入 -> 全部写完校验CRC -> 校验通过设置标志位并跳转
五、结语
1. 调试的时候一定要开J-Link的Flash编程校验,不然写进去不对debug起来要命。Keil里Options->Debug->Flash Download里勾选”Verify”就行。
2. Bootloader代码尽量精简,别往里塞太多东西。万一升级失败你就砖了,代码越小越安全。
3. 建议加个”安全启动”机制:App启动后先跑个自检,不通过就自动回退到旧版本。这个在量产产品里很重要。
4. 升级过程中断电恢复是个问题,我在参数区做了个状态标记,重启后检查状态决定是否继续升级。不然万一写到一半断电,下次启动就找不到App了。
5. 串口波特率别设太高,115200比较稳。我之前设到921600结果有丢包,调了半天才发现是电平转换芯片限速了。
来源: 整理文章为传播相关技术,网络版权归原作者所有,如有侵权,请联系删除。
我要赚赏金
