资讯动态

STM32F103串口IAP升级实战:从Bootloader分区到跳转App避坑指南

发布时间:2026/10/17 3:36:41 来源:尧图企业网站定制
简介这份完整版原创STM32F103串口IAP升级程序面向嵌入式开发学习者与固件升级方案实践者围绕STM32F103平台解决设备运行中通过串口更新固件的问题。资源包含Bootloader、串口通信、固件接收校验擦除写入、错误恢复与固件合法性校验等关键环节并附带测试app与完整IAP流程便于对照验证升级稳定性。压缩包共290个文件以72个h头文件、68个c源文件为核心辅以o、d、crf等编译中间文件及hex、bin、axf、map等烧录与调试产物整体约5.68MB目录结构清晰适合按模块检索学习。目前已有2786人学习下载读者可从中获取串口IAP的完整实现思路、Bootloader编写方法、CRC校验与回滚恢复机制以及测试app的验证场景适合熟悉HAL或LL库、希望深入理解STM32F103固件升级细节的开发者参考。1. 串口IAP升级为什么我劝你先跑通再谈“远程维护”去年帮一个做工业采集的朋友救急现场十几台设备装在配电柜里固件有个小bug要改拆机接ST-Link根本不现实。当时就想如果有个串口IAP一根USB转TTL就能搞定。后来拿到这份STM32F103串口IAP升级程序包含测试app和完整IAP我第一件事不是看代码而是先烧进去跑一遍——因为IAP这东西跑不通比看不懂更让人抓狂。它解决的就是“不拆机、不接线、只用串口就能把新固件写进Flash并跳转运行”的问题。适合两类人一是手上有STM32F103板子、想搞明白Bootloader和App怎么分区的嵌入式新手二是产品已经出货、需要留一个串口升级后门的开发者。整套代码基于标准外设库没有RTOS逻辑直白拿来改比从头写省事得多。2. 工程结构拆解IAP和App到底怎么分家2.1 两个工程、两块Flash这份资源里有两个独立工程一个是IAP工程编译出来烧到0x08000000另一个是测试App工程编译出来烧到0x08008000具体偏移看IAP里定义的App地址。IAP负责上电后判断要不要升级、接收串口数据、擦写Flash、跳转App就是正常业务代码只是启动时多了一步重定位向量表。常见做法是IAP占前16KB或32KBApp从后面开始。为什么是16KB因为STM32F103C8T6只有64KB FlashIAP代码量一般不超过8KB留16KB足够剩下48KB给App。如果你用的是RCT6这种256KB的IAP可以只占8KBApp空间更大。区域起始地址大小存放内容Bootloader0x0800000016KBIAP工程Application0x08004000剩余App工程升级标志0x0800C0002KB自定义标志位注意升级标志地址不要和App区域重叠我一般放在Flash最后一页擦除时单独处理。2.2 中断向量表重定位App能跑起来的关键一步是把中断向量表从默认的0x08000000搬到App自己的起始地址。否则一进中断就跳回IAP的向量表程序直接跑飞。在App的main函数开头加这么一段// App起始地址必须和IAP里定义的一致 #define APP_ADDR 0x08004000 int main(void) { // 重定位向量表SCB-VTOR指向App起始地址 SCB-VTOR APP_ADDR; // 下面才是正常的初始化 SystemInit(); Delay_Init(); Uart_Init(115200); while(1) { /* 业务逻辑 */ } }SCB-VTOR是Cortex-M3的中断向量表偏移寄存器写进去之后所有中断入口都从新地址取。参数APP_ADDR必须和IAP里跳转时用的地址完全一致差一个字节都会进HardFault。我见过有人IAP里写0x08004000App里写0x08004000但编译时Keil的IROM1起始地址没改结果生成的bin文件里中断向量还是按0x08000000链接的跳过去照样死。2.3 Keil里的IROM设置App工程必须改Keil的Target选项IROM1起始地址改成0x08004000大小改成0xC00048KB。这一步不做编译出来的hex烧进去也是错的。IAP工程则保持默认0x08000000。两个工程分开编译先烧IAP再烧App或者通过IAP的串口升级流程把App的bin文件传进去。3. 核心代码逐段讲从串口收包到跳转App3.1 升级标志判断与超时机制IAP上电后不能无脑等串口否则正常运行时也卡在Bootloader里。常见做法是上电后先检查一个Flash标志位如果标志位是“要升级”就进接收流程否则等3秒看有没有串口命令没有就直接跳App。// 升级标志0x5A5A5A5A表示需要升级 #define UPGRADE_FLAG_ADDR 0x0800C000 #define UPGRADE_FLAG_VALUE 0x5A5A5A5A void IAP_Check(void) { uint32_t flag *(volatile uint32_t*)UPGRADE_FLAG_ADDR; if(flag UPGRADE_FLAG_VALUE) { // 标志有效进入串口接收升级流程 Uart_SendString(Ready for upgrade...\r\n); IAP_ReceiveApp(); } else { // 无标志等3秒看有没有上位机发命令 if(Wait_Cmd_Timeout(3000) CMD_UPGRADE) { IAP_ReceiveApp(); } else { // 超时直接跳App Jump_To_App(APP_ADDR); } } }UPGRADE_FLAG_ADDR选在Flash最后一页擦写时单独擦这一页不影响App。Wait_Cmd_Timeout用SysTick做毫秒计数3秒是个经验值——太短上位机来不及握手太长正常启动会明显变慢。我一般设2到5秒看上位机响应速度。3.2 串口接收与Flash写入接收流程一般是上位机先发一个“开始升级”命令IAP回应OK然后上位机按固定长度分包发bin数据每包带序号和校验IAP收到一包写一包写完回ACK全部发完后上位机发“结束”命令IAP写升级标志、复位。// 接收一包数据并写入Flash // buf: 数据缓冲区, len: 本包长度, addr: 写入地址 uint8_t IAP_WritePacket(uint8_t *buf, uint16_t len, uint32_t addr) { uint16_t i; // 解锁Flash FLASH_Unlock(); // 擦除目标页2KB一页 if(FLASH_ErasePage(addr) ! FLASH_COMPLETE) { FLASH_Lock(); return 0; // 擦除失败 } // 逐半字写入 for(i 0; i len; i 2) { uint16_t halfword buf[i] | (buf[i1] 8); if(FLASH_ProgramHalfWord(addr i, halfword) ! FLASH_COMPLETE) { FLASH_Lock(); return 0; // 写入失败 } } FLASH_Lock(); return 1; // 成功 }FLASH_ErasePage每次擦2KB所以上位机分包最好按2KB对齐否则跨页擦除会麻烦。FLASH_ProgramHalfWord是半字写入STM32F103的Flash编程粒度就是16位所以循环里i每次加2。参数addr必须2KB对齐不对齐擦除会返回错误。写完一包回一个字节的ACK上位机收到ACK再发下一包这样不会因为缓冲区溢出丢数据。3.3 跳转App的完整动作跳转前必须做三件事关总中断、关外设时钟、设置主栈指针。少一步都可能跳过去跑不起来。// 跳转到App // appAddr: App起始地址 void Jump_To_App(uint32_t appAddr) { uint32_t jumpAddr; // 检查栈顶是否合法在RAM范围内 if(((*(volatile uint32_t*)appAddr) 0x2FFE0000) ! 0x20000000) { return; // 栈顶不合法不跳 } // 关总中断 __disable_irq(); // 关外设时钟按需关至少关掉用到的 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, DISABLE); // 设置主栈指针 __set_MSP(*(volatile uint32_t*)appAddr); // 取复位向量 jumpAddr *(volatile uint32_t*)(appAddr 4); // 跳转 ((void (*)(void))jumpAddr)(); }*(volatile uint32_t*)appAddr取的是App向量表的第一个字也就是初始MSP值。appAddr 4是复位向量。__set_MSP把主栈指针设成App的栈顶这样App里的局部变量才有正确的栈空间。关中断是因为跳转过程中如果来个中断向量表还没切过去会跑到IAP的中断服务程序里。关外设时钟是防止IAP里初始化的外设在App里重复初始化出问题至少把串口关掉。4. 避坑与排查IAP跳转失败的5个血泪现场4.1 跳过去就HardFault现象串口打印“Jumping...”之后没反应调试器显示HardFault。原因通常是App的向量表没重定位或者IAP里跳转前没关中断。解决确认App的SCB-VTOR设置正确确认IAP跳转前调用了__disable_irq()。还有一个隐蔽原因App编译时用了--strict或优化等级过高启动代码里先跑了SystemInit才设VTOR顺序反了。把VTOR设置放到main第一行。4.2 串口收包丢数据现象升级到一半卡住上位机显示超时。原因一般是IAP接收缓冲区太小或者没开串口接收中断用轮询方式在擦Flash时错过了数据。解决擦Flash期间关中断会导致串口数据丢失常见做法是先把整包数据收完再擦写或者用双缓冲。我一般把接收缓冲区设成2KB以上收到完整一包再擦写。4.3 升级标志擦不掉现象每次上电都进升级模式跳不到App。原因写升级标志的地址和擦除地址不一致或者擦除后没写回默认值。解决升级完成后把标志地址所在页擦掉或者写一个非0x5A5A5A5A的值。注意擦除是按页擦的别把App区域误擦了。4.4 App能跑但中断不响应现象App主循环正常但串口接收中断、定时器中断都不进。原因VTOR设了但App的启动文件里中断向量表还是链接到0x08000000。解决改Keil的IROM1起始地址重新编译App。用fromelf或objdump看生成的bin前4个字节应该是App的栈顶第5到第8字节是复位向量不是IAP的。4.5 升级后第一次运行正常复位后死机现象升级完直接跳转能跑断电重启就死。原因升级标志没清或者清标志时把App的某页擦了。解决升级完成后先擦标志页再跳转。跳转前确认标志页地址不在App范围内。我一般把标志页放在Flash最后一页App大小不超过总容量减2KB。5. 二次开发把串口IAP改成你的专属升级通道拿到这套代码最值钱的不是照搬而是按自己产品改。我一般从三个地方下手协议、存储、触发方式。先说协议。原版用的是简单“命令长度数据校验”格式你可以换成YModem上位机用SecureCRT或Xshell直接发文件省得自己写上位机。改的时候注意YModem每包1024字节IAP接收缓冲区要开到1KB以上擦写按2KB对齐收到完整2KB再擦一页。校验用CRC16比累加和靠谱尤其是串口线长、干扰大的工业现场。再说存储。如果你的App超过48KB或者要存两版固件做回滚可以把Flash分区改成IAP 16KB AppA 48KB AppB 48KB 标志2KB。升级时写到备用区写完后改标志下次启动跳备用区。这样升级失败还能回滚到旧版。代价是Flash要够大F103C8T6只有64KB玩不了双区至少得RCT6。最后说触发方式。串口触发适合产线但现场设备可能没有串口引出来。可以加一个“长按按键3秒进升级模式”的逻辑或者检测某个GPIO电平。我一般保留串口命令触发同时加一个按键触发两者互不干扰。按键用外部中断上电时如果检测到按键按下直接进IAP接收流程不跳App。验证方法很简单改完IAP后先烧IAP再烧一个闪灯App确认能正常跳转然后用串口发一个不同闪烁频率的App bin确认升级后闪烁频率变了最后断电重启确认还是新频率。这三步走完基本就稳了。提示每次改完IAP先把升级标志擦干净再测试否则一上电就进升级模式容易误判是跳转代码有问题。从那以后我每次改IAP都强制走一遍“擦标志→烧IAP→烧App→跳转→升级→复位”的完整流程不再只测跳转那一步。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑