资讯详情

STM32串口IAP升级:Ymodem协议Bootloader分区实现详解

📅 2026/10/16 19:40:18 | 华诺云谱 👁 阅读
STM32串口IAP升级:Ymodem协议Bootloader分区实现详解
简介一套基于STM32F103C8T6与STM32F407ZGT6的Ymodem串口IAP远程升级代码资源面向嵌入式固件开发与物联网产品维护场景适合需要实现bootloader引导升级、远程固件更新的开发者学习参考。压缩包共1103个文件包含197个h头文件、194个c源文件以及bin/hex烧录文件、uvprojx工程文件等典型Keil MDK项目结构另有o、axf、map等编译中间文件包体约40.34MB。两份工程代码均包含点灯型APP测试程序呼吸灯与亮暗灯已通过SecureCRT上位机实际发送文件验证完整升级流程能帮助读者理解Ymodem协议移植、串口收发、Flash分区跳转及APP与bootloader协同工作的关键细节。目前已有1645人学习/下载可用作IAP远程升级的入门资料也可直接抽取协议栈与启动逻辑移植到实际产品项目中节省协议对接与调试时间。1. STM32 串口 IAP 的现场为什么选 Ymodem 而不是裸传固件做过产线固件维护的人都有这种体会板子已经封壳、没有引出 SWD或者设备部署在远端改一版逻辑就得寄回工厂开盖烧录来回一周没了。STM32F103C8T6 和 STM32F407ZGT6 这两颗芯片一个只有 64KB Flash、20KB RAM一个拥有 1MB Flash、192KB RAMFlash 擦除单位和容量差异非常大但“不能开盖升级”的需求完全一样。IAPIn-Application Programming就是解决这个问题的标准路子出厂时烧一个 bootloader后续固件通过串口下载到指定 Flash 区重启跳转运行。传输协议里 Ymodem 是我一直推荐的选择它有文件名、文件大小、CRC16 校验和分包重传复杂度比 Zmodem 低得多在 Cortex-M 上几百行 C 代码就能跑起来而且主流串口终端都原生支持不需要额外写上位机。这篇笔记把这套 bootloader 的 Flash 分区、协议实现、App 侧配合和踩过的坑一次讲清楚适合正在做远程升级方案、或者想把当前固件升级链路换成 Ymodem 的开发者。2. Flash 分区与链接脚本bootloader 和 app 的边界不能靠感觉IAP 的核心前提是“Flash 里有两套程序各自不越界”。这个边界不是在代码里用 if 判断出来的而是靠链接脚本和下载地址硬性约束的。分区错了轻则 app 起不来重则擦除操作把 bootloader 自己抹掉。2.1 F103C8T6 与 F407ZGT6 的 Flash 擦除结构差异先把两颗芯片的 Flash 物理结构摆出来这是所有分区决策的基础。STM32F103C8T6 是 64KB Flash按 1KB 页擦除总共 64 页没有 sector 的概念。擦除函数只认页地址只要地址对齐到 1KB 边界就不会误伤相邻区域。它的 RAM 只有 20KB所以 bootloader 里的缓冲区、协议栈、擦写函数要精打细算不适合开大的接收缓冲。STM32F407ZGT6 是 1MB Flash擦除单位是 sector而且 sector 大小不均匀具体划分如下Sector地址范围大小Sector 00x08000000 - 0x08003FFF16KBSector 10x08004000 - 0x08007FFF16KBSector 20x08008000 - 0x0800BFFF16KBSector 30x0800C000 - 0x0800FFFF16KBSector 40x08010000 - 0x0801FFFF64KBSector 5 - 110x08020000 - 0x080FFFFF各 128KB也就是说F407 的 app 起始地址必须落在某个 sector 的起始地址上。如果 app 从 0x08010000 开始擦除 Sector 4 就能整体覆盖如果从 0x08018000 这种不对齐的地址开始擦除 Sector 4 会把 0x08010000 之前的区域也清掉那里面可能是 bootloader 的尾巴或者变量区升级一次直接变砖。2.2 分区方案bootloader 占哪、app 从哪开始、参数区放尾我常用的分区方案是“bootloader 独占头部app 紧跟其后参数区放 Flash 尾部”。对 F103C8T6区域地址大小Bootloader0x08000000 - 0x08007FFF32KBApp0x08008000 - 0x0800EFFF28KB参数区0x0800F000 - 0x0800FFFF4KB对 F407ZGT6区域地址大小Bootloader0x08000000 - 0x0800FFFF64KBSector 0-3App0x08010000 - 0x080FEFFF约 956KB参数区0x080FF000 - 0x080FFFFF4KBSector 11 尾部参数区放的是升级标志、固件版本号、app 长度和 CRC32 校验值bootloader 启动时先读这一块决定“进升级模式”还是“直接跳 app”。为什么 bootloader 要占 32KB 甚至 64KB因为 Ymodem 接收状态机、环形缓冲区、Flash 擦写函数、CRC 校验表、串口驱动外加调试打印轻松超过 16KB。我见过有人把 bootloader 压到 8KB结果中途加个回滚功能就得删功能极其被动。2.3 链接脚本bootloader 和 app 的 LD / 散列文件怎么改bootloader 的链接脚本基本不用动Flash 起点保持 0x08000000长度按分区改小即可。以 GCC 环境下 F407 的 bootloader 为例MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 0x10000 RAM (xrw) : ORIGIN 0x20000000, LENGTH 0x30000 }LENGTH 0x10000 就是 64KB程序超过这个值链接阶段就会报错比运行期翻车好排查得多。app 的链接脚本是重头戏。F407 app 的 Flash 起点必须改成 0x08010000MEMORY { FLASH (rx) : ORIGIN 0x08010000, LENGTH 0xE0000 RAM (xrw) : ORIGIN 0x20000000, LENGTH 0x30000 }LENGTH 0xE0000 是 1MB 减掉 bootloader 占用的 64KB 和参数区的 4KB正好对齐 0x080FF000 参数区起点。F103 的 app 则把 FLASH ORIGIN 改为 0x08008000LENGTH 改为 0x7000。如果你用的是 Keil MDK不用手改 .sct 文件在 Options - Target 页面里设置 IROM1 的 Start 和 Size 就行。Start 填 0x08010000Size 填 0xE0000MDK 会自动生成对应的散列文件。注意这里有一个非常容易忽略的点改完链接脚本后app 工程里如果有绝对地址取址的代码比如某些 Bootloader 跳转库要同步检查这些地址是否还落在新 Flash 范围内。3. Ymodem 协议落地帧格式、CRC16 校验与接收状态机Ymodem 协议不复杂但细节极其容易翻车。它跟 Xmodem 最大的区别是第一个帧是文件头帧包含文件名、文件大小等信息接收端可以据此校验文件长度数据帧支持 128 字节和 1024 字节两种长度通过帧首字符区分。3.1 握手流程与帧格式从发 C 到 EOT 的状态转换Ymodem 的主动权在接收端。bootloader 进入升级模式后先往外发一个字符 C0x43表示“我准备好了请开始传文件”。PC 端串口工具收到 C 后开始发送文件头帧。帧格式如下SOH0x01表示本帧是 128 字节块STX0x02表示本帧是 1024 字节块序号第一帧为 0x00第二帧为 0x01第三帧又回到 0x00如此循环序号范围 0-255序号取反对序号按位取反校验序号完整性数据区128 字节或 1024 字节文件头帧的数据区前 8 字节是文件名紧接一个 0x00然后是文件大小 ASCII 字符串再一个 0x00剩余补 0x00CRC16两个字节高字节在前文件头帧示例/* 文件名 APP.BIN文件大小 50000 字节 */ uint8_t header_frame[133] { 0x01, /* SOH, 128 字节帧 */ 0x00, /* 序号 0 */ 0xFF, /* 序号取反 */ A, P, P, ., B, I, N, 0x00, 5, 0, 0, 0, 0, 0x00, /* 后面全部补 0x00 */ };接收端收到文件头帧后解析出文件名和大小回一个 ACK0x06。发送端接着发序号为 1 的数据帧接收端每收一帧就校验 CRC16通过回 ACK失败回 NAK0x15。数据全部传完后发送端发 EOT0x04接收端回 ACK然后发送端再发一个 EOT接收端再回 ACK整个传输结束。这里要特别注意很多串口工具实现的是“EOT 后回 ACK 再发 C 等待下一个文件”bootloader 如果没实现第二个 EOT 的处理PC 端会一直等。我一般建议 bootloader 在第二个 EOT 后直接判断“所有文件已收到”退出升级模式。3.2 CRC16 校验XMODEM 多项式与查表法实现Ymodem 用的是 CRC-16/XMODEM多项式 0x1021初值 0x0000输入输出都不反转结果也不异或。这个算法跟 Modbus 的 CRC16多项式 0x8005初值 0xFFFF结果异或 0x0000完全是两回事混用必然翻车。逐位实现的版本uint16_t crc16_xmodem(const uint8_t *buf, uint32_t len) { uint16_t crc 0x0000; while (len--) { crc ^ ((uint16_t)*buf) 8; for (int i 0; i 8; i) { if (crc 0x8000) { crc (crc 1) ^ 0x1021; } else { crc 1; } } } return crc; }代码逻辑很直白每个输入字节先异或到 CRC 高八位然后左移 8 次最高位为 1 就与多项式异或。逐位版本在 F103 上跑 50000 字节固件大概要几十毫秒完全可接受如果嫌慢可以预生成一张 256 项的查表。字节序是另一个坑Ymodem 规定 CRC 高字节先发送低字节后发送。有部分国产串口工具发的是低字节在前bootloader 如果用统一收包流程会校验失败。我做兼容处理时会同时算两种字节序或者先按高字节在前试失败后交换再试一次。在代码里用宏控制if (rx_crc_hi (crc 8) rx_crc_lo (crc 0xFF)) { /* CRC 匹配序列号正确 */ } else if (rx_crc_lo (crc 8) rx_crc_hi (crc 0xFF)) { /* 字节序反了按低字节在前处理 */ } else { /* 直接 NAK */ }强烈建议保留第二个分支它在现场救过我两次尤其是对接不可控的第三方升级工具时。3.3 串口接收状态机环形缓冲区加主循环解析串口中断里做协议解析是初学者的常见错误中断里调用 CRC 函数一个字节一个字节地算遇上高波特率时中断占用极重还会跟 Flash 擦除互相干扰。正确做法是中断只收数据进环形缓冲区主循环里做协议解析。环形缓冲区用最经典的结构typedef struct { uint8_t buf[1025]; /* 必须大于 1024留出帧首字节 */ volatile uint16_t head; volatile uint16_t tail; } ringbuf_t; ringbuf_t g_uart_rx; void UARTx_IRQHandler(void) { if (USART_GetITStatus(UARTx, USART_IT_RXNE)) { uint16_t next (g_uart_rx.head 1) % sizeof(g_uart_rx.buf); if (next ! g_uart_rx.tail) { g_uart_rx.buf[g_uart_rx.head] USART_ReceiveData(UARTx); g_uart_rx.head next; } } }这里有个参数细节环形缓冲区大小设为 1025是因为 Ymodem 的 1024 字节帧加帧首一共 1025 字节。如果缓冲区只有 1024数据稍一积压就会丢帧。F103 的 RAM 只有 20KB这个 1KB 缓冲区占掉 5%但 Ymodem 传大数据块时性能收益值这个代价F407 的 192KB RAM 完全没压力。主循环的接收状态机我一般拆成四个状态状态含义处理IDLE等待文件头收到 SOH/STX 后进入 WAIT_HDRWAIT_HDR正在收文件头帧收满 128 字节解析文件名和大小回 ACKWAIT_DATA正在收数据帧按 SOH/STX 区分帧长收满后算 CRCWAIT_EOT数据接收完收到 EOT 回 ACK收第二个 EOT 后退出超时处理是状态机的灵魂。bootloader 每隔 1 秒检查一次当前状态从上次收到字节到现在的时间超过 1000ms 没有新数据就重新发一个 C 或者回一个 NAK。这个超时值不是随便定的太小会在 PC 端工具处理文件时误触发重传太大会让用户觉得升级卡死。4. App 侧改造与跳转逻辑中断向量表偏移和跳转前清理bootloader 写好了app 这边不改是跑不起来的。很多人卡在“bootloader 跳过去了但 app 不跑”或者“app 的外设中断一触发就死机”基本都是 app 侧的两个改动没做到位。4.1 App 工程的三个必备改动VTOR、链接脚本、中断处理第一链接脚本第 2.3 节已经写过app 的 Flash 起点必须改。第二中断向量表偏移。Cortex-M 内核默认从中断向量表的起始地址取栈顶指针和复位入口偏移后必须告诉内核“向量表搬家了”。F407 在标准库或 HAL 库下SCB-VTOR 0x08010000;F103 的老库不一定定义 VTOR 寄存器可以直接操作地址#define SCB_VTOR (*(volatile uint32_t *)0xE000ED08) SCB_VTOR 0x08008000;这一行必须放在 app 的 main 函数最前面在外设初始化之前。因为只要外设中断一打开而向量表还指向 bootloader 的地址中断来了就会取错入口直接 HardFault。第三如果 app 用到了加密库或者 Bootloader 跳转库里的绝对地址变量要确保这些符号在 app 的链接范围内。一个常见排查动作编译 app 后看 .map 文件里 app 的 RESET 段是不是落在 0x08010000 开始的区域而不是 0x08000000。4.2 Bootloader 跳转 App 的干净姿势关中断、读栈顶、跳转函数跳转代码几乎是固定模板typedef void (*pFunction)(void); void jump_to_app(uint32_t app_base) { uint32_t sp_val *(volatile uint32_t *)app_base; uint32_t pc_val *(volatile uint32_t *)(app_base 4); /* 栈顶指针必须落在 RAM 区域否则直接放弃跳转 */ if ((sp_val 0xFFF00000) ! 0x20000000) { return; } __disable_irq(); /* 关闭 Systick清中断标志 */ SysTick-CTRL 0; SysTick-LOAD 0; SysTick-VAL 0; /* 关闭外设时钟 */ RCC-AHB1ENR 0; RCC-APB1ENR 0; RCC-APB2ENR 0; /* 清空 NVIC 挂起中断 */ NVIC-ICER[0] 0xFFFFFFFF; NVIC-ICER[1] 0xFFFFFFFF; NVIC-ICER[2] 0xFFFFFFFF; __set_MSP(sp_val); ((pFunction)pc_val)(); }逻辑分四步先取 app 向量表前两个字的栈顶指针和复位向量地址校验栈顶指针是否真落在 RAM 区间这是跳转失败的第一道保险然后关中断、关外设时钟、清 NVIC 挂起标志最后设置主栈指针并跳转。这里有个需要强调的细节__set_MSP(sp_val)不能省。虽然复位后 SP 本来就从上电地址加载但 bootloader 运行期间如果用过中断、嵌套或者 RTOSMSP 已经被改写不显式设置会让 app 一开始就栈错乱。__set_MSP在 CMSIS 里直接提供F103 和 F407 通用。NVIC 清挂起这步特别容易被忽略。bootloader 在 Ymodem 传输过程中开了串口中断跳转前如果不把 NVIC 的 pending 位清掉app 一启动就会触发残留中断而 app 那边可能根本没有对应的中断服务函数于是又 HardFault。4.3 F103 与 F407 的跳转细节差异F103 的 app 起始地址按 1KB 页对齐即可0x08008000 这种地址完全没问题。F407 的 app 起始地址必须对齐 sector 边界0x08010000 是默认选择。F103 的 NVIC 只有 60 个中断清挂起时 ICER[0] 和 ICER[1] 就够了F407 的中断更多ICER[0]、[1]、[2] 全清。这也是跳转模板里为什么写三行而不是一行的原因。另外F407 有 D-Cache 和 I-Cache如果 bootloader 开了 Flash 预取或缓存跳转前最好把 CACHE 也关闭避免 app 执行到被缓存的旧指令。实际场景里 bootloader 很少开缓存但如果你在 bootloader 里启用过 CCM 或者 ART 加速跳转前清一下更稳妥。5. 避坑清单协议不兼容、扇区擦除翻车与升级掉电这一章写的是我在几个真实项目里踩过的坑每个都对应一个现象、一个深层原因和一个已验证的解决办法。5.1 现象PC 端发 1024 字节帧bootloader 收到乱码或丢数据原因Ymodem 允许发送端在传输过程中自由切换 SOH128 字节帧和 STX1024 字节帧很多工具在文件较大会自动切到 1024 字节块。bootloader 如果只按 128 字节处理或者环形缓冲区只有 512 字节STX 帧一进来直接溢出。解决状态机里必须同时处理 SOH 和 STX 两种帧首帧长分别取 128 和 1024环形缓冲区大小至少要 1025 字节。解析时不要边收边算 CRC等整帧齐了再校验。代码里判断帧长的位置如下if (frame_start 0x01) { frame_len 128; } else if (frame_start 0x02) { frame_len 1024; } else { send_nack(); return; }5.2 现象F407 升级完第一次跳转正常第二次升级擦除了 bootloader原因这是 app 起始地址没对齐 sector 引起的。app 放在 0x08018000 这种地址擦除 Sector 4 时把 0x08010000 到 0x08018000 的 bootloader 尾部数据一并清了。解决F407 的 app 起始地址只能选 sector 起始地址。推荐 0x08010000Sector 4 起点如果 app 特别大后续 sector 都是 128KB 对齐0x08020000、0x08040000 都可以。改完分区表之后把所有 Flash 擦除的起始地址都对照这张表过一遍别只改一处。5.3 现象跳转后 app 卡死在 HardFault调试发现 SP 指向非法地址原因bootloader 跳转前没有校验栈顶指针或者没有调用__set_MSP。app 的向量表读出来的第一个字根本不是合法栈地址跳过去当然跑飞。解决跳转前强制检查(sp_val 0xFFF00000) 0x20000000不合法就不跳留在 bootloader 重新进入 Ymodem 等待。同时 app 侧确认向量表第一项确实是初始 SP。判断栈区范围时F407 的 RAM 上限是 0x20030000F103 是 0x20005000如果你用高 12 位判断两个芯片都适用。5.4 现象升级到一半断电板子彻底不开机原因app 区被擦了一半新固件没写完旧固件已经没了。bootloader 启动时没有校验 app 有效性还试图跳转跳转失败卡死。解决引入“参数区 app 头校验”机制。app 区最前面 16 字节预定义一个描述头包含 magic、固件长度、CRC32bootloader 每次启动都算一遍 app 区的 CRC32跟描述头比对。不匹配就进 Ymodem 等待升级。这样断电后只是“升级失败”至少还能重连串口重新升级不算变砖。远程升级的产品里我还会在参数区写一个“升级进行中”的标志bootloader 看到这个标志就直接进升级模式而不是跳 app。5.5 现象串口波特率对但 PC 端一直显示等待接收原因发送端PC和接收端bootloader在等谁先说话的问题。Ymodem 的发起方是接收端必须由 bootloader 先发 CPC 端收到 C 才会弹文件选择框或自动发文件。如果 bootloader 没发 CPC 端就一直干等。解决bootloader 进入升级模式后先延时 200ms 等串口线稳定然后循环发 C直到收到有效帧或超时。延时太短会造成第一个 C 在 PC 端初始化前就丢了。部分 PC 工具有“手动发送”模式可以先发一帧测试握手是否顺畅。6. 把串口升级做成真正的远程升级命令字、CRC 校验与回滚验证串口直连升级只是第一步项目里真正要的是“远程”——设备部署在别处通过 4G 模块、DTU 或者以太网把固件推下去。Ymodem 本身跑在串口上所以只要把远端链路透传成串口即可。我习惯在 Ymodem 外面再包一层轻量命令帧用来做升级状态控制。命令帧格式可以自定义我常用的是帧头 0xAA55、命令字、长度、数据区、CRC32。bootloader 上电后先串口监听这些命令帧命令字含义用途0x01版本查询返回当前 bootloader 和 app 版本号0x02进入升级设置参数区标志重启后进 Ymodem0x03读取固件信息返回 app 长度、CRC、打包时间0x04复位设备软重启配合第 5.4 节的 app 描述头这套流程可以做到三个目标升级前查版本判断要不要升升级后校验 CRC 确认成功失败还能自动回滚或等待重传。我自己测试时固定走一遍下表的流程步骤操作预期1串口发 0x01 查版本bootloader 返回版本号2发 0x02 进入升级设备重启串口出现 C3PC 端选择 bin 文件 Ymodem 发送进度条走完返回 OK4重新上电app 正常运行输出新版本号5升级到一半断开电源再上电bootloader 检测 app 校验失败重新发 C 等待升级有一次我就是偷懒省掉了步骤 5 的断电测试结果现场设备升级到一半断电bootloader 还傻乎乎地往一个半擦除的 app 区跳板子彻底没响应最后只能靠人工拆机救回来。从那以后我每次改升级流程都强制把“断电注入”跑一遍校验不过就进 Ymodem 等待这已经是习惯动作了。这套资源把 F103C8T6 和 F407ZGT6 的 Ymodem 串口 IAP 都实现了bootloader 和 app 工程齐全拿过来改一下串口号和 Flash 起点就能直接编译使用。开发时多花半小时看完这篇笔记现场就能少熬一个通宵。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑