简介围绕STM32串口升级应用的完整工程资源面向嵌入式开发者和正在学习IAP在线升级的工程师可用于产品远程固件更新功能预研或自学实践。资源以实际项目为基础系统展示如何利用UART串口完成固件在线更新覆盖Bootloader启动流程、中断向量表重映射、串口收发中断配置、固件分包与CRC校验、Flash扇区擦除写入、升级完成后App跳转执行等核心环节同时展示PC端升级工具的设计思路与安全校验机制。压缩包共875个文件体积13.97MB文件类型以C源文件、H头文件、汇编启动文件为主另含Object中间文件、列表文件、链接脚本、Keil工程配置以及hex、bin格式固件文档和文本说明可用于梳理代码结构与排查升级异常。目前已有2964人学习下载。借助该资源可快速搭建串口升级实验环境掌握引导程序与应用程序切换的关键细节也能复用PC端串口工具代码借鉴固件接收、校验及Flash编程中的排错经验减少自行摸索带来的时间损耗。 一根普通的USB转TTL线对应好波特率直接对着设备的调试串口就能把整个固件换掉。这不是什么花哨操作就是STM32的串口升级方案。我最早产生这个需求是因为一批设备已经装进机箱发到客户现场临时要加功能既没有预留SWD接口也不可能把整机拆回来最后靠的就是串口把新固件灌进去。串口升级的核心思路不复杂芯片里先放一段Bootloader引导程序它负责通过串口接收新固件、把固件写入Flash、再跳转运行真正干活的App则放在另一块Flash区域。这套机制在STM32上实现起来涉及Flash分区、启动流程、中断向量表偏移、链接地址调整等不少细节任何一个地方弄错跳转瞬间就会死机。这篇文章把我最终跑通的完整方案和踩过的坑都捋一遍适合正在做量产设备维护、想给产品留一条串口升级通道的嵌入式开发者参考。1. 什么场景下得靠串口升级封箱设备、批量维护和烧录器无能为力的时刻先明确一个前提不是所有项目都非得上串口升级。但遇到下面几类场景你会发现手里只有烧录器根本不够用。第一类设备已经完全装配好外壳密封没引出SWD/JTAG口甚至没有BOOT0跳线。第二类设备已经在客户那边运行售后人员手里只有一根串口线你不可能要求每个现场人员都带着几百块的调试器去处理固件问题。第三类小批量试产阶段功能需求来回改每次都拆机用烧录器刷时间成本直接把人拖垮。有朋友可能会说STM32出厂自带ISP功能把BOOT0拉高、BOOT1拉低再复位就能通过串口用官方工具烧写这不也是串口升级吗确实可以但ISP有两个局限它烧写的是整片Flash做不到“引导程序常驻、只更新App区”这种模式而且每次升级都得动BOOT0引脚对于密封好的设备同样不方便。真正的量产主推方案是IAP式的串口升级。IAP的全称是In-Application Programming意思是在应用运行过程中就能对程序存储区重新编程。实现方式是Bootloader和App都烧在Flash里Bootloader固定在0x08000000起始地址上电先执行它App放在后面的独立区域。平时Bootloader检测不到升级请求就直接跳转App检测到请求就进入接收模式等固件包。我用一张表把几种烧录方式的适用场景理清楚方式是否需要外接工具是否需要操作引脚典型场景SWD/JTAG烧录需要调试器需要引出SWD口开发调试、首次烧录ISP串口下载只需要串口线需要BOOT0上拉工厂生产批量烧录串口IAP只需要串口线不需要软件判断现场维护、远程协作升级OTA无线升级不需要连线不需要带WiFi/4G模块的产品串口升级也不是万能钥匙。第一次烧Bootloader必须用SWD因为芯片出厂Flash是空的如果升级中途断电Flash里既没有完整App也没有可用固件也可能需要SWD来救。所以我的习惯是产品设计时无论如何都保留一组SWD下载座哪怕只是PCB上的几个测试点关键时刻能救命。这也是不少工程师容易忽略的“退路设计”。2. 先把Flash分区规划好Bootloader和App怎么分、启动流程怎么走2.1 Flash容量和偏移量的计算方法串口升级第一步不是写代码而是决定Bootloader占多大Flash、App从哪个地址开始。以最常用的STM32F103C8T6为例它拥有64KB Flash地址范围0x08000000到0x0800FFFF一个页Page大小是1KB。我给Bootloader分配16KB也就是0x08000000到0x08003FFFApp从0x08004000开始这样App能使用48KB空间对大多数逻辑不复杂的设备足够。这里有个关键点App起始地址必须按Flash页对齐。F103一个页1KB所以偏移量只要是1KB的整数倍就行但如果你换到STM32F407扇区结构完全不同最小扇区16KB、还有128KB的大扇区App起始地址就必须对齐到扇区边界。这个细节在芯片移植时最容易翻车。除了App区还要考虑参数存储区。很多设备运行时会往Flash里保存配置参数这部分如果也塞在App区内每次升级固件都会把参数一起覆盖掉。我通常会在Flash末尾预留一页或者几页作为参数区App通过固定地址访问固件分区时就把这块区域隔离开。2.2 启动流程与升级请求判断代码层面的启动流程是这样的复位后CPU从0x08000000取出栈顶指针和复位向量开始执行Bootloader。Bootloader主函数第一件事是判断是否进入升级模式判断方式常见的有三种工程上都成立。第一种是按键检测。上电时检测某个GPIO电平如果按键被按下就认为要升级。这种方式最直观现场人员只要知道“按住按键再上电”就能进入升级模式几乎不需要培训。第二种是串口握手。Bootloader启动后等待2秒等上位机发来约定的握手指令比如字符串“STMA”收到就进入升级模式收不到就跳转App。这种方案不用动任何按键远程电话指导时很方便我自己实际项目里用得最多的就是它。第三种是标志位跳转。App收到升级指令后往Flash某个特定地址写一个魔数然后软复位Bootloader启动时发现这个魔数清除魔数后进入升级模式。这种方式适合设备已经在正常运行、需要从远程主动升级的场景。要注意魔数标记必须放在Bootloader管理的区域之外否则擦除固件时把标记一起擦掉反而出问题。2.3 超时机制无论用哪种方式Bootloader等待升级请求时都必须加超时。比如等待2秒没有检测到按键、也没收到握手命令就直接跳转App避免设备每次上电都卡在Bootloader里浪费时间。这个超时建议用SysTick或者定时器实现不要用while循环死等否则后面想加功能会非常痛苦。3. Bootloader侧代码帧协议、Flash擦写、跳转App的实现细节3.1 帧协议不要裸发bin文件有人图省事Bootloader里直接用串口接收中断一字节一字节收收到数据就写Flash看起来简单实际用起来全是坑。我的建议是设计一个极简帧协议把数据切片、打包、校验后再传输。自己用的帧格式如下所有长度都是大端序字段长度说明帧头2字节0xAA 0x55命令字1字节0x01开始烧写 / 0x02写数据 / 0x03跳转App数据长度2字节高字节在前数据域可变一帧最多256字节累加和1字节从帧头到数据域末尾逐字节累加帧协议的意义在于先判断帧头再依据长度收齐整帧最后做累加和校验校验通过才认为这一帧有效。这样串口线上哪怕有干扰数据非法就不会被当作固件写进Flash。这个道理跟收快递一样——不拆开看一眼你永远不知道里面是零件还是废纸。Bootloader接收端的核心流程大致如下#define FRAME_HEAD0 0xAA #define FRAME_HEAD1 0x55 #define CMD_START 0x01 #define CMD_DATA 0x02 #define CMD_JUMP 0x03 #define APP_FLASH_ADDR 0x08004000 #define FLASH_PAGE_SIZE 1024 // STM32F103 一页 1KB void bootloader_loop(void) { uint8_t *frame rx_buf; // 从串口收到的完整帧 uint32_t write_addr APP_FLASH_ADDR; // 当前写入地址 FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); while (1) { if (uart_receive_frame(frame, 512) ! 0) { // 帧头、长度、累加和校验失败丢弃当前帧 continue; } switch (frame[2]) { case CMD_START: // 收到开始命令先擦除App区全部页面 for (uint32_t addr APP_FLASH_ADDR; addr APP_FLASH_ADDR FIRMWARE_MAX_SIZE; addr FLASH_PAGE_SIZE) { FLASH_ErasePage(addr); } write_addr APP_FLASH_ADDR; break; case CMD_DATA: // frame[4]为数据长度数据从frame[5]开始 for (uint16_t i 0; i frame[4]; i 2) { uint16_t half_word frame[5 i] | (frame[6 i] 8); FLASH_ProgramHalfWord(write_addr i, half_word); } write_addr frame[4]; break; case CMD_JUMP: FLASH_Lock(); jump_to_app(APP_FLASH_ADDR); return; } } }3.2 Flash擦写注意事项Flash操作有三个容易忽略的细节。第一擦除之后才能编程。Flash只能把1写成0不能把0写成1所以每页必须先整页擦除再写入。上面的代码在收到CMD_START时一次性把整个App区擦掉后面的CMD_DATA只管按地址写。第二写Flash必须是半字或字为单位。标准外设库提供FLASH_ProgramHalfWordHAL库对应HAL_FLASH_Program参数里用FLASH_TYPEPROGRAM_HALFWORD指定16位编程。想一次写一个字节要么报错要么写入结果不对。第三擦写Flash期间CPU取指令会被阻塞。F103的Flash控制器在擦写时会暂停对Flash本文还有配套的精品资源点击获取
