从零实现 STM32F103 串口 IAP 固件升级系统Boot APP 上位机全流程最近做了一个 STM32 在线升级IAP项目从零实现了完整方案Boot 引导程序、App 应用程序、Python 上位机三个部分全链路实测可用。本文把整个系统的设计思路、通信协议、可靠性和踩过的坑都整理出来希望能给想学 IAP 的朋友一些参考。一、项目能做什么STM32 程序写进 Flash 后要更新怎么办传统做法是用 ST-Link/J-Link 重新下载。但如果产品已经装到现场总不能每台都拆开接下载器吧IAPIn-Application Programming在应用中编程就是解决这个问题的程序自己给自己升级。只需要第一次用下载器烧 Boot之后 App 的所有更新都通过串口完成——“第一次程序靠下载器之后全靠串口”。本项目的三个部分Boot 引导程序上电判断是否进入升级模式决定收新固件还是跳转现有 AppApp 应用程序正常产品程序可通过串口收升级指令主动请求进 Boot 升级Python 上位机图形界面选固件文件一键升级实时显示 App 运行状态二、系统架构┌─────────────┐ 0xAF 升级请求 ┌──────────────────┐ │ Python 上位机 │ ───────────────────▶ │ App 应用程序 │ │ (GUI) │ │ (0x08004000) │ │ │ │ 收指令→写标志→复位 │ └──────┬───────┘ └─────────┬────────┘ │ IAP 协议帧 (115200) │ 软复位 │ ▼ │ ┌─────────────────────────┐ └────────────────────▶│ Boot 引导程序 │ │ (0x08000000) │ │ 查 Config 标志决定 │ │ 进升级 or 跳 App │ └────────────┬────────────┘ │ 升级完成跳转 ▼ 新 App 运行Flash 内存布局STM32F103C8, 64KB0x08000000 ┌──────────────────┐ │ Boot (16KB) │ 引导程序 0x08004000 ├──────────────────┤ │ App (47KB) │ 应用程序可被升级 0x0800F800 ├──────────────────┤ │ Config (1KB) │ 升级标志 固件信息防变砖 0x08010000 └──────────────────┘三、通信协议帧格式发送┌──────┬──────┬──────────┬──────┬──────────────┬────────┬────────┐ │ 0xAA │ 命令 │ 数据长度 │ 序号 │ 数据... │ CRC16低│ CRC16高│ └──────┴──────┴──────────┴──────┴──────────────┴────────┴────────┘帧头0xAACRC16MODBUS 算法初值0xFFFF多项式0xA001对命令~数据计算小端存放波特率1152008N1命令表命令命令码数据说明握手 READY0x0A无进入升级模式擦除 ERASE0x0B无擦除整个 App 区写数据 WRITE0x0C数据(≤128B)seq 从 0 递增地址 APP_BASE seq×128校验 VERIFY0x0D总长度(4B LE) CRC32(4B LE)对写入固件整包校验跳转 JUMP0x0E无校验通过后跳转 App回复帧格式┌──────┬──────────┬──────┬────────┬────────┐ │ 0xBB │ 状态码 │ 序号 │ CRC16低│ CRC16高│ └──────┴──────────┴──────┴────────┴────────┘状态码状态码含义0xA0成功0xB0重复包序号相同0x01CRC 校验错误0x03序号不连续丢包0x04状态错误当前状态不允许此操作升级时序上位机 Boot │ 0x0A 握手 │ ├─────────────────────▶│ 状态→READY │ BB A0 成功 │ │◀─────────────────────┤ │ 0x0B 擦除 │ ├─────────────────────▶│ 擦除 App 区状态→ERASED │ BB A0 成功 │ │◀─────────────────────┤ │ 0x0C 数据帧(seq0) │ ├─────────────────────▶│ 写 Flash 偏移 0 │ BB A0 成功 │ │◀─────────────────────┤ │ 0x0C 数据帧(seq1) │ ├─────────────────────▶│ 写 Flash 偏移 128 │ BB A0 成功 │ │◀─────────────────────┤ │ ... │ │ 0x0D 校验 │ ├─────────────────────▶│ 硬件 CRC32 比对状态→VERIFIED │ BB A0 成功 │ │◀─────────────────────┤ │ 0x0E 跳转 │ ├─────────────────────▶│ 清标志 → 跳转 App │ ▼ │ App 开始运行四、可靠性设计4.1 Config 标志页防变砖Flash 最后一页0x0800F800保存升级标志typedefstruct{u32 magic;// CONFIG_MAGIC判断配置有效性u32 upgrade_flag;// UPGRADE_FLAG是否请求升级u32 reserve[3];// 预留}Config_t;只有升级成功、跳转前才清标志升级中断电/失败 → 标志还在 → 下次上电 Boot停在升级模式等你重发不会变砖已实测升级中断电 2 次重新上电均可恢复重来4.2 关键设计点中断 环形缓冲接收STM32 USART 无 FIFO慢轮询会丢字节。中断每收一字节入环主循环取走处理跳转前善后关全局中断、清 NVIC、停 SysTick、复位时钟再设 MSP 跳转FLASH_ProgramWord强制写魔数避免config_read首次读到擦除态0xFFFFFFFF时把 magic 清 0导致标志永远不生效CRC16 帧校验 CRC32 整包校验双保险4.3 通道抽象transport 接口协议层不直接操作串口而是通过统一的transport接口收发包。好处以后接 WiFi/4G 远程升级时只需新增一个 transport 实现协议层零改动。typedefstruct{void(*init)(void);/* 初始化通道 */void(*send)(uint8_t*buf,uint16_tlen);/* 发送一帧 */uint8_t(*recv)(uint8_t*buf,uint32_ttimeout);/* 收一字节, 超时返回0 */}transport_t;voidtransport_set(transport_t*t);/* 注册当前通道 */transport_t*transport_get(void);/* 获取当前通道 */recv必须带超时参数让上层能感知通道暂时无数据。当前实现transport_uart.cUSART1未来加transport_wifi.c即可切换。五、疑难问题排查踩坑记录现象原因解决握手只收到 1~2 字节USART 无 FIFO 慢轮询 阻塞 printf改用中断环形缓冲区写数据后多回0xB0重复包三个if未用else ifseq自增后误判重复改成else if链擦除后写数据被拒0x04擦除后未设STATE_ERASED擦除成功设状态空片/断电损坏后无法恢复App 无效时iap_load_app返回后死循环兜底进升级模式config 标志写不进去config_read首次读到擦除态把 magic 清 0config_write强制写魔数断电后上位机报拒绝访问串口句柄失效未重连上位机加断线自动重连PC13 LED 不闪用了 CRL 而非 CRH、ODR 位号写错PC13 在 CRH位 13六、移植到其他芯片如 RCT6项C8T6RCT6Flash64KB256KB页大小1KB2KBRAM20KB48KBFlash 下载算法Med-densityHigh-density只需改动 3 处协议/上位机/App 逻辑零改动flash.hFLASH_PAGE_SIZE1024 →2048flash.hCONFIG_ADDR0x800F800 →0x803F800256KB 最后一页KeilAPP 工程IROM1尺寸放大Flash 下载算法选 High-densityAPP_BASE和VTOR仍是0x08004000不用改——当初用偏移量 宏设计就是为了移植时收网。总结这套 IAP 方案麻雀虽小五脏俱全Boot 跳转、Flash 擦写、自定义协议、状态机、上位机、可靠性设计、通道抽象都有。个人认为最有价值的是那几个坑——它们都是真实踩过、花时间排查出来的能帮你少走很多弯路。完整代码已开源两个平台内容同步欢迎 StarGitCode国内访问快 https://gitcode.com/2402_85030385/IAPGitHub国际访问 https://github.com/wenroudezhizi/IAP如果文章对你有帮助欢迎点赞、评论、收藏有问题也欢迎在评论区交流。