STM32 IAP 在线升级实战:不拆壳不接烧录器,串口就能刷固件

STM32 IAP 在线升级实战:不拆壳不接烧录器,串口就能刷固件 产品刚发出去客户说要改个逻辑。你总不能让人把机器寄回来拆开外壳夹上 SWD 烧录器再一个个刷。现场几百台设备这么干人先疯了。IAPIn Application Programming在应用中编程解决的就是这件事MCU 自己给自己刷固件。设备上电先跑一段常驻的 Bootloader它负责收新程序、写进 Flash、再跳过去运行。整个过程用户只要点一下上位机的「升级」串口或者 USB 传个包就行。这篇文章把 Bootloader 怎么分区、跳转那几行代码为什么少一个步骤就死机、以及怎么做到「升级中断电也不变砖」讲清楚。先搞懂 Flash 是怎么被切开的STM32 的 Flash 从0x0800 0000开始是一片连续的空间。IAP 的做法是把这片空间切成两块Bootloader 区放在最开头比如0x0800 0000到0x0800 FFFF占 64KB。这段代码出厂后基本不动永远能跑。App 区紧接在后面比如0x0801 0000开始。真正的功能代码在这里可以被覆盖重写。参数区再留一小块存「是否要升级」「固件版本」「CRC」这类标志位最好单独一个扇区擦写不影响前两块。上电永远从0x0800 0000开始执行也就是先跑 Bootloader。Bootloader 读一下参数区的「升级标志」决定是原地等升级还是跳到 App 去干活。关键认知Bootloader 和 App 是两个独立的程序各有一套自己的中断向量表。默认情况下内核复位后只认0x0800 0000那张向量表。App 跑在偏移地址上如果不告诉内核「向量表挪地方了」App 里一进中断就飞。这个「告诉内核」的动作靠一个叫VTOR的寄存器。跳转那几行少一步就 HardFaultBootloader 收完固件、校验通过、写进 App 区之后要跳过去执行。跳转函数看着就几行但每一步都不能省typedefvoid(*pFunc)(void);voidJumpToApp(uint32_tapp_addr){// 1. 校验栈顶地址是否合法SRAM 起始在 0x2000 0000uint32_tstack_top*(__IOuint32_t*)app_addr;if((stack_top0x2FFE0000)!0x20000000)return;// 非法绝不跳留在 Bootloader// 2. 关掉所有中断避免跳转到 App 后旧中断乱进__disable_irq();// 3. 把主栈指针 MSP 设成 App 的栈顶__set_MSP(stack_top);// 4. 向量表第二项才是 Reset_Handler不是 app_addr 本身uint32_treset_handler*(__IOuint32_t*)(app_addr4);pFunc SysJump(pFunc)reset_handler;// 5. 跳SysJump();}上图把向量表头两项画出来了第一项是 MSP 初值数据第二项是 Reset_Handler 地址代码。app_addr 4取的才是后者跳错位置必崩。这里最容易被坑的是第 4 步app_addr是向量表起点第一项是 MSP 初值第二项是复位函数入口。很多人直接把app_addr当函数跳结果 PC 指向了数据立刻死。App 这边要改两处不然白跳Bootloader 跳过去了App 还得配合否则中断还是乱。两处改动第一处改向量表偏移。在system_stm32xxxx.c里找到VECT_TAB_OFFSET改成你的 App 偏移量#defineVECT_TAB_OFFSET0x00010000U// App 从 64KB 处开始HAL 库会在SystemInit()里执行SCB-VTOR FLASH_BASE | VECT_TAB_OFFSET;内核从此认 App 的向量表。第二处改链接脚本的 Flash 起点。以 STM32CubeIDEGCC为例打开STM32Fxxx_FLASH.ld把 FLASH 的 ORIGIN 和 LENGTH 改掉MEMORY { FLASH (rx) : ORIGIN 0x08010000, LENGTH 384K RAM (xrw) : ORIGIN 0x20000000, LENGTH 128K }ORIGIN 从0x0801 0000起LENGTH 是总 Flash 减掉 Bootloader 和参数区。这两处不改成同一个偏移编译出来的固件烧错位置或者中断全进 Default_Handler症状千奇百怪。收固件和写 Flash 的真活Bootloader 里真正干活的是「接收数据 → 校验 → 写 Flash」。串口可以自己定个简单帧协议比如一帧[命令][地址][长度][数据][CRC16]。写 Flash 用 HAL 库voidFlash_Write(uint32_taddr,uint32_t*data,uint32_tlen){HAL_FLASH_Unlock();// 按扇区/页擦除写之前必须先擦FLASH_EraseInitTypeDef erase{0};erase.TypeEraseFLASH_TYPEERASE_PAGES;erase.PageAddressaddr;erase.NbPages(lenFLASH_PAGE_SIZE-1)/FLASH_PAGE_SIZE;uint32_terr0;HAL_FLASHEx_Erase(erase,err);// 按字32 位写地址必须 4 字节对齐for(uint32_ti0;ilen;i4){HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,addri,*(uint32_t*)(datai/4));}HAL_FLASH_Lock();}注意两点写之前必须整页擦除且只能把 1 写成 0想改回来得先擦写操作地址和数据都得 32 位对齐半字对齐的芯片就按半字。STM32 内部 Flash 擦写期间 CPU 会停摆几毫秒到几十毫秒这段时间别指望它干别的。升级中断电怎么不变砖这是 IAP 和「能用的产品」之间最大的沟。最简单的防砖思路收到固件先写进 App 区先不急着跳。算整包 CRC32和包里带的校验值比对不一致就丢掉重来。在参数区写「新固件已就绪」标志再软复位。复位后 Bootloader 看到标志校验通过才跳 App校验不过留在 Bootloader 等下次升级。更稳的是双区备份Flash 里留两块 App 位置正在跑 A 就往 B 写写完好校验再切。哪怕这一包写坏了下次上电还能从 A 起来重刷。我手上的项目总线电压高、现场没法拆机这种双区方案是兜底的最后一道闸。十个坑踩过才知道疼App 没改VECT_TAB_OFFSET。中断全进 Default_Handler表现像定时器不动、串口只收不发。这是出现率最高的一坑。跳转前没__disable_irq()。Bootloader 开着的中断在 App 里没处理函数一触发就 HardFault。跳转第一件事先关总中断。没校验栈顶地址就跳。App 区是空的或者没写全跳过去 PC 指着 0xFF FF FF FF直接死机。先用0x2FFE0000掩码确认栈顶落在 SRAM 范围。跳转地址用了app_addr而不是app_addr 4。app_addr是 MSP 初值数据第二项是 Reset_Handler代码。跳错位置必崩。App 链接脚本的 FLASH 起点没改。编译产物从0x0800 0000开始一烧把 Bootloader 自己盖了下次上电啥都没有。Bootloader 和 App 的偏移量对不上。一边0x10000一边0x08000写的和跳的不是同一块现象是反复复位进 Bootloader。Flash 写没对齐。地址或数据不是 4 字节对齐HAL_FLASH_Program直接返回错误写了个寂寞。DMA 搬来的缓冲区要手动对齐。升级中途掉电变砖。没做 CRC 校验 就绪标志写到一半断电App 半残Bootloader 还以为它完好就跳结果卡死。加校验和双区。跳转前没清外设中断和 DMA。串口收了一半进来的数据、DMA 还在搬跳到 App 后这些硬件还在跑总线冲突直接 HardFault。跳转前把用到的外设DeInit掉。Bootloader 区没写保护。万一升级逻辑算错地址把 Bootloader 自己也擦了设备彻底成砖只能回厂夹 SWD。关键扇区用FLASH_OB_WRP锁住Bootloader 永不被覆盖。把这件事收一下IAP 不神秘本质就是「一段常驻代码 一块可被覆盖的 App 区 一次干净的跳转」。难的地方不在写 Flash在那几行跳转代码和 App 的向量表偏移少改一处就让你怀疑人生。真要上产品CRC 校验和双区备份才是区分「能演示」和「敢发货」的分水岭。不拆壳、不接线固件自己更新自己。Bootloader 守住底线App 才能放心迭代跳转前那几行关中断、对栈顶、取复位地址一步都不能省。