ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32 IAP实战:YMODEM协议Bootloader设计与跳转卡死排查

STM32 IAP实战:YMODEM协议Bootloader设计与跳转卡死排查 简介面向STM32嵌入式开发者的IAP Bootloader实践资料基于YModem协议实现串口在线升级方案完整覆盖bootloader启动、数据接收、CRC校验、Flash写入及跳转APP的关键流程。压缩包共475个文件以C语言源码、头文件、编译生成的.o/.hex目标文件及Keil工程文件uvprojx/uvoptx为主附带调试配置、映射文件与说明文档便于直接打开工程对照学习。资源约9.71MB已有2366人学习下载。资料中同时提供“1_IAP”与“2_APP程序”两部分清晰展示Bootloader与应用程序的地址分区和交互机制针对YModem协议中的分帧、校验与重传逻辑也给出完整实现可直接移植到实际项目或作为二次开发基础。同时工程内丰富的源文件和编译中间文件有助于深入理解STM32 Flash编程规范及IAP运行流程适合希望掌握STM32在线升级与串口传输协议的嵌入式工程师参考学习。 STM32 IAP 这块只要做过产品量产或者后期维护的工程师基本都绕不开 bootloader 的设计。我之前给一个跑 RTOS 的 STM32F103 项目做远程升级功能时第一版用的是最简单粗暴的做法擦除一整片 Flash跑一个固定的串口协议把 bin 文件整包收下来再跳转。功能倒是能跑但稍微一改固件大小、一换调试工具就得跟着改上位机后来干脆把协议换成了 YMODEM 才彻底理顺。这篇文章就把整个 bootloader 的实现思路、YMODEM 协议细节、App 侧需要配合的改动全部写清楚同时也把「IAP 跳转后卡死在 HAL_Delay」这类高频问题做个完整复盘。这篇文章适合正在做 STM32 固件升级、量产烧录、OTA 差分升级的开发者也适合那些已经跑通了 bootloader 但被各种跳转后异常程序跑飞问题折磨的同学。我会从 Flash 分区讲到 YMODEM 帧格式从 bootloader 状态机写到 App 向量表偏移全部是能直接落地的方案。1. IAP 不是抄一段跳转代码那么简单先想清楚启动架构很多新手接触 IAPIn Application Programming时第一反应是bootloader 不就是判断一下要不要升级然后跳转到 App 吗。这话对了一半。跳转确实是最后一步但真正的难点在跳转之前固件从哪来、放在哪、怎么保证写入过程中断电不出问题、App 改完地址之后还能不能正常跑中断。1.1 上电启动流程与 IAP 的位置STM32 上电后从0x08000000取出栈顶指针 MSP再从0x08000004取出复位向量然后跳转到复位处理函数执行。如果没有 bootloader那么0x08000000就是 App 的起始地址。一旦引入 IAPFlash 就被分成两块或更多块Bootloader 区和App 区。启动流程 0x08000000 Bootloader 区上电先跑这里 0x08008000 App 区正常业务代码 Bootloader 做的事 while(1) { 判断是否需要升级 如果需要升级 - 接收固件 - 写入 Flash - 跳转 如果不需要升级 - 直接跳转 App }这样设计的核心价值有两点一是产品出厂后可以通过串口、USB、CAN 等接口升级固件不必拆机接 SWD二是升级失败还能停在 bootloader 里重新升级不至于变砖。1.2 Flash 分区规划大小和起始地址怎么定分区是 IAP 的第一步决策也是最容易被低估的一步。我见过有人 bootloader 只分 8KB用的还是标准外设库结果编译出来 9KB 多直接塞不下。bootloader 的 Flash 占用要预留充足尤其是用了 HAL 库之后一个简单的串口接收程序动不动就 5~8KB。以 STM32F103C8T664KB Flash为例我的分区方式是区域起始地址大小说明Bootloader0x0800000016KB0x4000包含启动代码、YMODEM 接收、Flash 驱动App 区0x0800400048KB0xC000业务代码向量表从这里开始App 末尾预留0x0800F8002KB存放升级标志、版本信息这里有个细节App 区起始地址必须按扇区/页对齐。F103 是 1KB 一页小容量或 2KB 一页中容量F4 是扇区结构16KB、64KB、128KB 不等。如果 App 起始地址没对齐页边界擦除时会把 App 开头给擦掉或者出现写入覆盖。F103 中容量每页 1KB所以 0x08004000 是页对齐的没问题。1.3 升级标志位的设计不需要升级时bootloader 直接跳 App。但怎么判断需不需要升级常见方式有几种按键检测上电时按住某个按键进入升级模式上位机命令bootloader 里跑一段超时窗口等待主机发送升级指令超时则跳转标志位App 里收到升级指令后在备份寄存器或 Flash 末尾写一个标记然后软复位bootloader 检测到标记后进入升级模式我项目里用的是上位机命令 备份寄存器标志的组合。App 收到升级指令后把标志写到 RTC 备份寄存器RTC-BKPxR然后NVIC_SystemReset()。Bootloader 启动后先查标志有则进入 YMODEM 接收流程没有则直接跳 App。用备份寄存器比 Flash 写标志好一点因为不占 Flash 寿命但要注意PWR时钟和备份域访问必须在跳转前使能。1.4 升级流程的完整时序整个升级过程最理想的状态是主机PC 端串口助手或自研上位机发一个进入升级指令设备重启进 bootloaderbootloader 回复一个 C 字符主机选择固件文件通过 YMODEM 发送bootloader 收到完整文件后校验通过写入完成跳转 App。App 启动后向上位机回一条版本信息完成闭环。这套流程里YMODEM 解决的是固件文件怎么可靠地通过串口发过去的问题。它有文件名字段、文件大小字段、块编号、CRC 校验比裸传 bin 靠谱得多也比 XMODEM 更适合固件升级场景。2. YMODEM 协议拆解为什么它是固件升级的优选方案YMODEM 不是新东西上世纪 80 年代就有了但到今天依然是嵌入式 IAP 的常客。它的设计初衷是文件传输具备 XMODEM 没有的文件元数据传递能力文件名、大小、时间戳同时保留 128/1024 字节两种数据块、CRC16 校验和自动重传机制。在串口这种容易受干扰的物理链路上可靠性足够高。2.1 YMODEM 帧格式与关键控制字符YMODEM 传输的基本单位是块Block每块分为头部、序号、数据、校验四个部分。数据区有两种长度128 字节SOH和 1024 字节STX。协议中几个关键控制字符字符值含义SOH0x01128 字节数据块头STX0x021024 字节数据块头EOT0x04发送结束ACK0x06确认NAK0x15否定确认请求重发CAN0x18取消传输C0x43请求发送CRC 模式YMODEM 的起始帧文件信息帧是 128 字节块结构为SOH | 00 | FF | filename\0filesize\0mtime\0 | ...NUL填充... | CRC_HI | CRC_LO注意序号第一块是00第二块的补码是FF这不是 bug是协议规定。序号从 0 开始每发送一块加 1到 255 后回绕到 0。接收端判断顺序就是靠这个序号。2.2 CRC16 校验的计算YMODEM 用的是CRC-16/XMODEM多项式0x1021初始值0x0000没有反转输入输出。我最初在这里踩过一个坑直接用标准 CRC-16/CCITT 的查表法算出来的值怎么都对不上。后来才发现 YMODEM 的 CRC 是 XMODEM 变体查表法虽然多项式相同但初始值和处理方式有差异。手写计算逻辑uint16_t ym_crc16(const uint8_t *data, uint32_t len) { uint16_t crc 0x0000; for (uint32_t i 0; i len; i) { crc ^ (uint16_t)data[i] 8; for (uint8_t j 0; j 8; j) { if (crc 0x8000) crc (crc 1) ^ 0x1021; else crc 1; } } return crc; }接收端每收到一块先算 CRC再和帧尾的两个字节比较一致才回 ACK否则回 NAK 请求重发。如果发的是 1024 字节块CRC 覆盖的是整个 1024 字节数据区。2.3 起始帧的解析细节YMODEM 的起始帧是整个协议里最容易解析出错的地方。数据区以\0分隔三个字段文件名、文件大小十进制 ASCII 字符串、文件修改时间。例如firmware.bin\0123456\01123456789\0文件大小字段是十进制 ASCII 字符串不是十六进制。我第一次写解析时想当然按strtol的 16 进制解析结果固件大小全错写入一半 Flash 就溢出了。正确做法是拿到\0分隔的字符串后用十进制方式strtoul(str, NULL, 10)转成整数。另外起始帧的序号是 0如果收到序号非 0 的起始帧应该直接回 CAN 取消传输。2.4 数据块大小协商与切换YMODEM 的发送方会根据文件大小自动选择数据块长度文件剩余数据超过 1024 字节发 STX 块不足 1024 字节的最后一块发 SOH 块。接收端无需协商只需同时支持两种头字符的解析。实际测试中我发现一个细节某些 YMODEM 上位机实现尤其是改版过的在整个传输过程中固定用 STX 块最后不足 1024 字节的部分用0x1ASUBDOS 文件填充符补齐。bootloader 写 Flash 时要按实际文件大小截断不能把填充字节也写进去否则 App 区尾部会多出一段垃圾数据。分区够大时问题不大但强迫症看着难受而且如果固件签名校验覆盖整个 App 区填充字节会导致校验失败。最后是结束流程发送方发 EOT接收方回 ACK然后再发一个C字符发送方回一个全 0 的起始帧或直接结束接收方回 ACK整个传输完成。有些实现省略了结束帧直接发完 EOT 就等 ACKbootloader 做容错时要能处理这两种情况。3. Bootloader 侧实现从串口收包到 Flash 写入的完整链路Bootloader 的代码结构不需要多花哨一个状态机就够。核心思路是不阻塞、不超时、不丢包。我第一版用阻塞式HAL_UART_Receive接收结果主机那边发快了丢字节发慢了 bootloader 卡在等待中出不来。后来改成中断接收 主循环状态机的方式才稳定。3.1 串口接收与环形缓冲区Bootloader 的串口配置没什么特殊之处但接收方式要特别处理。我用的方案是 DMA 空闲中断 环形缓冲区逻辑上等同于把串口收到的每个字节都塞进一个 FIFO主循环从 FIFO 里取字节喂给 YMODEM 协议状态机。#define RING_BUF_SIZE 1024 static uint8_t ring_buf[RING_BUF_SIZE]; static volatile uint16_t head 0, tail 0; void UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { uint16_t next (head 1) % RING_BUF_SIZE; if (next ! tail) { ring_buf[head] rx_byte; head next; } HAL_UART_Receive_IT(huart1, rx_byte, 1); } }串口中断接收每次只收 1 字节效率不高但简单可靠。在主循环里不断检查环形缓冲区有没有数据有则取出来喂状态机。这个模式在 bootloader 场景下完全够用——YMODEM 传输速率最高也就 115200bps每秒约 11.5KB1KB 的环形缓冲区足够扛住中断突发。3.2 YMODEM 接收状态机YMODEM 接收端的状态机大致分四个阶段IDLE - START_RECV收到起始帧- DATA_RECV收数据块- FINISH收到EOT伪代码逻辑switch (ym_state) { case YM_IDLE: if (rx_char C) // 收到发送方的块请求 // 实际上接收端主动发 C 进入等待起始帧 break; case YM_WAIT_START: if (rx_char SOH) { 接收 128 字节帧解析文件名和大小 回 ACK发 C 状态切到 YM_WAIT_DATA 擦除 App 区 } else if (rx_char CAN) { 取消传输 } break; case YM_WAIT_DATA: if (rx_char STX) { 接收 1024 字节帧校验 CRC写入 Flash回 ACK/NAK } else if (rx_char SOH) { 接收 128 字节帧校验 CRC写入 Flash回 ACK/NAK } else if (rx_char EOT) { 回 ACK发 C等待结束帧 状态切到 YM_FINISH } else if (rx_char CAN) { // 连续两个 CAN 才是取消 取消传输 } break; case YM_FINISH: if (rx_char SOH) { // 结束帧全 0 回 ACK升级完成 } else if (rx_char EOT) { 回 ACK升级完成 } break; }注意一个细节取消传输需要连续收到两个 CAN 字符单个 CAN 可能是干扰。bootloader 里要加一个简单的计数逻辑连续收到两个 CAN 才确认取消防止误判。3.3 Flash 写入流程与擦除策略Flash 写入有几个必须遵守的规则写之前必须先擦除擦除以页/扇区为单位写入按半字16位或字32位对齐写入过程中不能有中断打断尤其不能有 Flash 操作的中断。F103 的页大小是 1KB中容量App 区从 0x08004000 到 0x0800F7FF共 46 个页。接收起始帧、解析出文件大小后就可以按需擦除这些页。擦除时机放在起始帧校验通过、进入数据接收前避免先擦后收导致中途失败丢原有固件。页擦除和写入用 HAL 库static uint32_t flash_write_buf(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t write_addr addr; uint16_t half_word; for (uint32_t i 0; i len; i 2) { half_word buf[i] | (buf[i 1] 8); if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, write_addr, half_word) ! HAL_OK) { return 0; } write_addr 2; } return 1; }写入时按半字处理因为 STM32F1 的 Flash 编程最小单位就是半字。如果用字节写会直接报错。F4 最少按字32位写这个要区分开。一个经验边收边写优于收完再写。收到一块数据、CRC 校验通过后立即写入 Flash这样即使传输中断之前的数据已经固化了重传时可以从断点继续当然 YMODEM 本身没有断点续传机制但至少不需要整包重来。而且边收边写对 RAM 需求低不需要开大数组缓存整个固件。3.4 跳转前的安全检查与执行Bootloader 在跳转前必须做两件检查一是 App 区起始地址的栈顶值是否合法在 RAM 地址范围内二是 App 的复位向量是否合理。如果跳转到一块空白 Flash 或者擦了一半的 Flash跑出来的结果就是 HardFault。typedef void (*pFunction)(void); void jump_to_app(uint32_t app_addr) { uint32_t app_stack *(volatile uint32_t *)app_addr; uint32_t app_reset *(volatile uint32_t *)(app_addr 4); // 栈顶地址必须在 RAM 范围内 if (app_stack SRAM_BASE || app_stack SRAM_BASE SRAM_SIZE) { return; // 固件无效留在 bootloader } // 复位向量必须在 Flash 范围内 if (app_reset app_addr || app_reset FLASH_END_ADDR) { return; } __disable_irq(); HAL_UART_DeInit(huart1); // 复位所有外设时钟 __HAL_RCC_DEInit(); // 关闭 SysTick 并清除 pending SysTick-CTRL 0; SysTick-LOAD 0; SysTick-VAL 0; // 清所有 NVIC pending for (int i 0; i 8; i) { NVIC-ICER[i] 0xFFFFFFFF; NVIC-ICPR[i] 0xFFFFFFFF; } // 设置 MSP 为 App 栈顶 __set_MSP(app_stack); // 获取并执行 App 复位函数 pFunction jump (pFunction)app_reset; jump(); }这里的__HAL_RCC_DEInit()是容易漏掉的一步。它把所有外设时钟复位到默认状态避免 bootloader 里开了某个外设的中断跳转到 App 后中断还在触发导致 App 初始化顺序混乱或跑飞。4. App 侧的三个关键改动向量表、链接脚本与编译选项Bootloader 写好之后App 不是拿来就能跑的。最典型的问题就是跳转过去以后程序死在启动文件里。原因基本都是向量表位置没改对。4.1 中断向量表偏移VTORARM Cortex-M 内核的中断向量表默认在0x00000000或0x08000000。App 的向量表在0x08004000如果系统发生任何中断CPU 去0x08000000取中断向量取到的是 bootloader 的中断处理函数App 的中断就全乱了。解决办法是用SCB-VTOR设置向量表偏移。在 App 的main最开始处甚至启动文件里加一行SCB-VTOR 0x08004000;对于 STM32F1 系列VTOR 是存在的但有些老版本的启动文件没有初始化它需要在 main 里手动设置。F4 系列部分型号如 F405VTOR 不支持任意偏移只支持对齐到 0x400 的倍数分区时已经按 0x400 对齐就没问题。设置完 VTOR 之后所有中断才能正确跳转到 App 的向量表。这个设置要放在任何外设初始化和中断使能之前。如果开启了某个外设中断后才设置 VTOR中断可能已经触发了向量表还没切过去就会跳到一个错误的地址执行。4.2 链接脚本和 Keil 分散加载设置App 的烧录地址也要改。如果用 Keil在 Options for Target - Target 标签页里IROM1 起始地址改为0x08004000大小改为0xC00048KBIRAM 保持默认如果用 GCC则修改链接脚本.text段的起始地址FLASH (rx) : ORIGIN 0x08004000, LENGTH 48K RAM (xrw) : ORIGIN 0x20000000, LENGTH 20K这里有一个我踩过的坑IROM1 大小改了之后App 的可执行文件大小不能超过 48KB。编译时如果没注意链接器会报错但有些优化选项下可能不会立即报错而是静默生成了一个超出分区大小的 bin。这个 bin 烧进去会覆盖下一个区域的数据运行时各种诡异问题。建议在编译后脚本里加一步检查 bin 文件大小的步骤超过分区大小直接编译失败。4.3 烧录时的地址设置App 开发阶段用 ST-Link 烧录时程序烧到0x08004000而不是默认的0x08000000。Keil 在 Debug 设置的 Flash Download 里要勾选 Erase Sectors 并设置起始地址如果用 STM32CubeProgrammer则把起始地址填成0x08004000。如果直接一键烧录默认地址会覆盖 bootloader导致设备变砖需要重新 SWD 连回来刷 bootloader。所以 App 工程里我会单独维护一个烧录配置确保不覆盖 bootloader 区。4.4 中断向量表与启动文件的关系一个常被忽略的细节如果 App 是从 bootloader 跳转过来的不是复位上电系统时钟、外设时钟、中断向量表都要由 App 重新初始化。HAL 库的SystemInit()在启动文件里被调用负责设置时钟源、PLL 等。跳转过去后启动文件里的SystemInit已经在 bootloader 阶段不执行了如果 App 的main里不手动调用SystemInit()系统时钟可能停留在 bootloader 配置的状态。保险做法是 App 的 main 最开始就调用SystemInit()然后SystemCoreClockUpdate()再设置 VTOR。5. 跳转后卡死是头号翻车现场一次完整的 root cause 排查如果你在网上搜 STM32 IAP出现频率最高的坑就是跳转后卡死和HAL_Delay 不执行或者干脆 HardFault。我当时调第一版 bootloader 时也栽在这里前前后后花了两个下午才定位到原因。这里把排查过程完整复盘一遍。5.1 现象描述与初步判断现象是bootloader 正常跳转App 的 LED 初始化和串口打印都正常说明基本启动流程跑通了但主循环里第一次调用HAL_Delay(500)就再也回不来了程序卡死。当时第一反应是看门狗——但项目没开 IWDG排除然后怀疑是某个中断没关干净打断导致死循环也不像最后打开调试器在HAL_Delay里打断点发现卡死在while循环等待 SysTick 中断置位。5.2 卡死根因SysTick 与 HAL_Delay 的绑定关系HAL 库的HAL_Delay依赖uwTick变量而这个变量是在 SysTick 中断里HAL_IncTick()累加的。SysTick 中断要正常工作必须满足SysTick 定时器使能中断使能NVIC 中 SysTick 的优先级配置正确SysTick 的中断向量指向 App 的SysTick_HandlerApp 里通过HAL_Init()调用了HAL_InitTick()来配置 SysTick但问题在于从 bootloader 跳转过来时SysTick 的向量表还指向 bootloader 区因为 VTOR 设置的位置不对或者 VTOR 设置的地址在 Flash 上但向量表内容没更新中断触发后 CPU 跳到 bootloader 的SysTick_Handler而这个函数在 App 的启动阶段没执行——它属于 bootloader 的代码此时 bootloader 已经跳走不会做任何有效操作。于是uwTick永远不增长HAL_Delay死等。5.3 排查链路的三个关键步骤如果你自己也遇到类似卡死建议按下述顺序排查每走一步都能排除一类原因。第一步确认 VTOR 是否正确设置。在调试器中查看SCB-VTOR的值必须是 App 区的起始地址如0x08004000。如果不是在 main 开头手动赋值SCB-VTOR 0x08004000;这是系统性原因一次设错会导致所有中断异常。第二步确认跳转前是否禁用了中断。__disable_irq()之后如果 App 没有重新使能SysTick 中断永远不会触发。HAL 库的HAL_Init()会调用HAL_InitTick()其中就有使能 SysTick 中断的操作。你在调试时看SysTick-CTRL的 TICKINT 位和 NVIC 的使能位。第三步确认 App 的SysTick_Handler是否在向量表正确位置。打开 App 工程的启动文件.s文件查看__Vectors表中SysTick_Handler的入口是否是 App 的SysTick_Handler函数地址。如果启动文件里 SysTick 向量缺失或写错某些剪裁过的启动文件只有部分中断向量也会卡死。5.4 另一个隐蔽坑bootloader 里的串口中断残留F103 的串口接收中断如果在跳转前没有关闭跳转到 App 之后串口 RXNE 中断会持续触发。因为 App 一开始可能没初始化串口、没清标志位中断一进来就进busy状态甚至死循环。这就是为什么跳转函数里要先HAL_UART_DeInit再关中断。排查这类问题的一个有效技巧是跳转前把所有外设 RCC 复位前面代码里的__HAL_RCC_DEInit()让所有外设回到上电默认状态。这样不管 bootloader 里用了什么外设都不会把状态污染给 App。代价是 App 启动时要重新初始化这些外设但这是正常流程不影响功能。5.5 跳转后 HardFault 的另一类原因有些情况下卡死变成了 HardFault多见于 App 的栈指针设置错误。跳转代码里__set_MSP(app_stack)如果拿到的栈顶值不在 RAM 范围内比如从 Flash 里读出来的是0xFFFFFFFF说明该地址没有有效代码跳转后第一条指令就会触发异常。所以我前面特意写了两层检查栈顶值范围检查和复位向量范围检查。这两步可以拦截绝大多数无效固件导致的崩溃。6. 上位机与整机测试工具选择、接收链路验证与生产注意点Bootloader 本身跑通还不够整个升级链路要能经受住真实场景的考验。这里讲讲上位机如何选、怎么验证接收链路、以及量产时要特别注意的几个点。6.1 上位机工具几个可用的 YMODEM 串口工具市面上支持 YMODEM 的串口工具不少但质量参差不齐。我实际用过几种列个对比工具优点缺点推荐度SecureCRT支持 YMODEM稳定能在脚本里自动交互收费配置项多高ExtraPuTTY免费内置 YMODEM可直接选文件发送界面老部分版本对中文文件名支持差高Tera Term免费开源YMODEM 支持完善默认界面英文中高MobaXterm集成多种协议串口 YMODEM 组合好用体积大中自研脚本完全可控支持自定义校验和日志需要开发成本条件允许时最佳最稳妥的方式是自己写一个 Python 脚本调用串口发送 YMODEM因为可以在发送前做文件大小检查、CRC 预计算、发送后对比返回值。如果只是调试用 SecureCRT 或 Tera Term 就够了关键是串口参数要对波特率、8N18 数据位、无校验、1 停止位流控关闭。6.2 YMODEM 与裸 bin 发送的对比有人可能会问既然要发 bin 文件为什么不用更简单的裸串口发送对比一下就能理解项目裸 bin 发送YMODEM文件信息大小、名称需自行定义协议协议自带错误检测需自行加 CRC每块自带 CRC16重传机制需自行实现协议自带 NAK/重传接收端对齐需自行处理块边界清晰调试工具支持需自研上位机现成工具多裸发送的一个致命问题是如果传输过程中串口丢了一个字节整个数据流从那个字节开始错位后续所有数据全错而接收端很可能毫无察觉。YMODEM 有块序号和 CRC 校验错一块检测到就重传不会累计错误。6.3 升级链路验证从 App 写入到版本确认验证升级功能时不要直接升级大固件。我建议按这个顺序测试空升级测试bootloader 进入升级模式但不发数据等待超时看是否能正常跳转 App小文件测试放一个只翻转 LED 的小程序验证 YMODEM 接收、CRC 校验、Flash 写入全流程完整 App 测试用当前版本的固件重新升级一遍验证升级后所有功能包括中断、RTOS、外设正常断电测试升级过程中断电几次确认下次上电能回到 bootloader 并重新升级不会变砖干扰测试串口线靠近电机或继电器干扰源观察重传机制是否有效升级完成后的确认最好做双向bootloader 在写完固件后读回几个关键地址做比对App 启动后向上位机上报版本号。我之前做产品时App 启动第一件事就是串口打印APP v2.1.0测试人员看到版本号变新了才算升级成功。6.4 批量生产时的注意事项量产烧录场景下IAP 面临的问题略有不同。建议做三件事一是 bootloader 区加写保护。通过 option bytes 设置 Flash 写保护防止量产时误擦 bootloader。F103 的写保护以页为单位保护 bootloader 区不影响 App 区升级。二是把上位机升级软件做成命令行工具。在产线自动化里集成升级脚本不要靠人工点发送文件。人工操作在批量场景下误触率太高容易把固件发到错误设备。三是固件文件加版本号和校验和。YMODEM 只负责传输不负责这个固件能不能在这个硬件上跑。产品规模大了之后不同硬件版本对应不同固件建议在 bin 文件尾部追加一段元数据版本号、硬件 ID、CRC 校验bootloader 收到后先验元数据不匹配就拒绝升级。7. 实际项目中积累的几条经验这一节放一些零散但很有价值的实操心得不像前面那么系统但每一条都是实际跑过项目之后的教训。第一bootloader 的串口波特率不要定 115200 以上。有些方案鼓吹 921600 高速升级实际在普通杜邦线和 USB 转串口模块上高速率下误码率明显上升CRC 重传频繁总耗时反而可能更慢。115200 是最稳妥的选择升级一个 64KB 的固件大约 6 秒完全可接受。第二YMODEM 的起始帧要宽容解析。某些上位机实现的文件名字段会带路径比如C:\Users\...\firmware.bin有些只带文件名。bootloader 解析时统一取最后一个\之后的部分或者干脆忽略文件名只取大小字段。第三升级过程中不要喂独立看门狗。如果产品开了 IWDGbootloader 的 YMODEM 接收等待过程必须定期喂狗否则等待主机发送时看门狗超时设备重启升级永远无法完成。喂狗的位置放在状态机主循环里即可确保没有长时间阻塞。第四App 区一定要留够余量。产品迭代过程中固件只会越来越大很少缩小。分区时按当前固件大小的 1.5~2 倍预留 App 区否则半年后加个新功能就发现 App 区装不下了。第五调试时开着日志量产时关掉。bootloader 里的printf调试信息在产线升级时可能干扰 YMODEM 通信——严格来说不会因为 YMODEM 只在特定状态窗口有效但调试信息会占用串口时间某些实现下可能抢占总线导致丢字节。正式版本建议把调试输出全部关掉只保留协议应答。这套方案我在 STM32F103、F303、F405 上都跑过核心代码基本可以平移。YMODEM 串口 Flash 分区的组合虽然谈不上多先进但胜在简单可靠、调试工具多、排障方便。如果有更复杂的场景比如无线升级、加密固件、签名校验可以在这一套基础上迭代扩展。本文还有配套的精品资源点击获取
返回列表