ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F407远程升级实战:IAP分区、Bootloader跳转与回滚机制

STM32F407远程升级实战:IAP分区、Bootloader跳转与回滚机制 简介面向 STM32F407 嵌入式开发者的一份远程固件升级IAP方案资源基于 DTU 透传实现应用运行中的在线编程省去现场拆机与外部编程器适合维护 Bootloader、需要批量升级或研究 Flash 分区管理的项目参考。资源包共 663 个文件压缩后约 21.88MB以 C 源码与头文件为核心辅以 Keil 工程配置.uvproj/.uvprojx、编译产物.axf/.hex/.map及备份说明文件可完整看到工程从编辑、编译到生成固件的全过程。目前已有 669 人学习下载。包内提供 Bootloader 与 App 分区跳转、Flash 擦写与写入、DTU 串口数据透传、固件接收与 CRC/MD5 校验等关键代码工程结构和代码注释便于对照 IAP 流程逐段理解同时附带多个 hex/bin 固件及编译中间文件可用于验证升级结果、排查链接或烧写问题。对于正在搭建 STM32F407 远程升级机制或准备移植 IAP 方案的开发者而言这份打包完整的资料能直接提供可参考的代码框架与清晰的目录结构节省大量从零开始摸索的时间。1. STM32F407 远程升级先过 IAP 这一关把编译好的固件通过网络或串口发到设备里让 STM32F407 自己把 Flash 擦了再写进去这就是远程升级。而 F407 的远程升级绕不开 IAPIn Application Programming因为它的 Flash 虽然有一兆字节但出厂并没有自带一个能更新自身的引导程序。多数人第一个坑也在这里直接对 0x08000000 地址所在的整个 Flash 做整片擦除把引导区也抹了板子当场变砖只能接 SWD 救回来。IAP 的思路是让固件分成两块甚至三块区域Bootloader 管接收和写入App 跑业务逻辑升级时把新固件放进预留的 Flash 空间校验通过后再跳过去执行。这篇文章按我自己的做法来写先用 Linker 脚本把 F407 的 Flash 分区讲清楚再给出 Bootloader 跳转和 App 中断向量重映射的可抄代码最后把远程下载、CRC 校验和回滚这三个关键点逐一落地。适合已经在用 STM32 标准库或 HAL 库做产品、想把 OTA 能力加进去的嵌入式工程师。2. 给 stm32f407 的 IAP 分区Bootloader 与 App 怎么分 Flash2.1 先看 F407 的 Flash 扇区表再动手STM32F407 的 Flash 是 1MB 的型号时扇区划分不是均等的。看参考手册 RM0090 的 Flash 章节0x08000000 到 0x080FFFFF 一共 12 个扇区前四个都是 16KB第五个是 64KB后面六个是 128KB。这个不对称的扇区结构直接影响分区方案因为 IAP 升级时擦除的最小单位是扇区不是字节。扇区地址范围大小常见用途Sector 00x08000000 - 0x08003FFF16KBBootloaderSector 10x08004000 - 0x08007FFF16KB参数存储或 Bootloader 扩展Sector 20x08008000 - 0x0800BFFF16KBApp 起始区域Sector 30x0800C000 - 0x0800FFFF16KBAppSector 40x08010000 - 0x0801FFFF64KBApp / 升级暂存Sector 5-110x08020000 - 0x080FFFFF128KB x 7App 主体我一般把 Bootloader 放在 Sector 0也就是从 0x08000000 开始占用 16KB 就够一个最简引导程序了。App 从 0x08010000Sector 4 的起始地址开始正好对齐扇区边界擦除和写入都不牵扯前面几个小扇区。App 编译出来的 bin 放在 0x08010000 到 0x080FFFFF 之间大约 960KB 可用对绝大多数业务固件绰绰有余。2.2 分区方案里必须给回滚留位置做远程升级如果只留一个 App 区升级过程中断电或写入校验失败设备就只能停留在半旧的固件上甚至完全无法启动。给回滚留位置的意思是双备份区。常见做法是把 Flash 分成 Boot App App_BackupApp 和 App_Backup 各占足够大的块。以 F407 为例可以把 App 放在 Sector 4 到 Sector 7App_Backup 放在 Sector 8 到 Sector 11各自 448KB 左右。升级流程变成新固件先整体写入 App_Backup全部写完并校验 CRC 通过后把 App_Backup 的内容搬到 App 区再跳转执行。如果搬移过程中掉电Bootloader 检测到 App 区首地址不是合法的栈顶值就不跳转等待下一次升级指令设备最多停留在旧版本不会被彻底锁死。2.3 Bootloader 跳转 App 的代码才算真正分完区分区只是 Linker 脚本里改了地址真正让 IAP 转起来的是跳转代码。Bootloader 里跳转到 App 的代码要处理两个关键点关闭全局中断以及重新设置主堆栈指针。typedef void (*pFunction)(void); void jump_to_app(uint32_t app_addr) { uint32_t app_msp *(volatile uint32_t *)app_addr; // App 起始处放的是栈顶地址 pFunction app_reset (pFunction)(*(volatile uint32_t *)(app_addr 4)); // 第二位是 Reset_Handler if ((app_msp 0x2FFE0000) 0x20000000) { __disable_irq(); // 跳转前必须关中断 SCB-VTOR app_addr; // 设置向量表偏移 __set_MSP(app_msp); // 切换主栈指针 app_reset(); // 跳转 } }这段代码的逻辑是F407 上电后从 0x08000000 读取第一个 32 位数据作为 MSP第二个 32 位数据作为 PC 的初始值。跳转前先检查 App 首地址是否像合法的 RAM 地址防止 Flash 为空或数据损坏时跳到一个非法地址。SCB-VTOR写 App 地址是为了让中断向量表跟随 App 走否则任何中断事件都会去 Bootloader 的向量表里找处理函数直接进 HardFault。提示__disable_irq()只能关掉 NVIC 里的中断SysTick 等通过异常机制触发的处理器事件仍然可能介入跳转前最好把 SysTick 也停掉并清空挂起的中断标志。3. 改造 stm32f407 的 App 工程中断向量重映射与 bin 生成3.1 链接脚本的起始地址决定 App 烧到哪App 工程拿到手时默认链接地址是 0x08000000这样的固件烧进 Flash只能从复位向量执行配合 Bootloader 跳转根本跑不起来。改造的第一步是把链接脚本里的 Flash 起始地址改到和 Bootloader 约定好的分区位置。以 GCC 的链接脚本为例修改前后对比/* 修改前 */ FLASH (rx) : ORIGIN 0x08000000, LENGTH 1024K /* 修改后 */ FLASH (rx) : ORIGIN 0x08010000, LENGTH 960K把 ORIGIN 和 LENGTH 同时改掉LENGTH 不能超过从 0x08010000 到 Flash 末尾的距离。如果链接脚本没改App 里所有函数地址和全局变量的位置都按 0x08000000 起算Bootloader 跳转过去后PC 指向的指令根本不是 App 的 Reset_Handler几乎必然跑飞。3.2 启动文件里要处理向量表偏移App 工程使用标准外设库或 HAL 库时SystemInit 函数会做一次时钟初始化但不会主动设置向量表。较新的 Cortex-M4 内核支持运行时修改 SCB-VTOR但 F407 要求向量表地址必须是 0x400 字节对齐也就是低 10 位必须为 0。0x08010000 的十六进制低 10 位是 0所以不需要额外对齐处理。在 App 的 main 函数最前面加上这一句int main(void) { SCB-VTOR FLASH_BASE | 0x10000; // 0x08010000 HAL_Init(); SystemClock_Config(); // ... 业务初始化 }注意如果把这句话放在 HAL_Init 之后HAL_Init 内部可能已经开了 SysTick 等中断向量表切换时如果有中断发生仍会走旧的向量表查地址。经验是放在进入 main 后的第一行执行而且在使用任何外设中断之前完成。3.3 生成 bin 文件时记得指定起始地址Keil MDK 用 AC6 编译器时调试器加载 axf 文件没有影响但远程升级要用的是纯二进制 bin 文件。生成 bin 的 User 选项卡命令是fromelf.exe --bin --outputapp.bin Objects\app.axf如果用的是 GCC 工具链对应的命令是arm-none-eabi-objcopy -O binary app.elf app.binfromelf 和 objcopy 生成的都是无地址信息的原始数据bin 文件的内容从链接脚本里设置的 ORIGIN 开始。也就是说app.bin 的第一个字节就是对 0x08010000 处数据的拷贝。Bootloader 接收这个 bin 文件时直接把它搬运到 0x08010000 即可不需要额外填一个偏移地址字段。以 bin 格式传输的固件还包含链接阶段确定的全部向量表和常量数据App 里所有中断向量、函数入口、字面量池都已经在编译时定好了绝对地址运行时靠 PC 跳转访问和 Flash 的物理位置一一对应。远程下发时如果不小心把 axf 或 hex 文件当作 bin 发过去Bootloader 写入的是带地址信息的对象格式跳到 0x08010000 执行时会发现那只是一段记录头设备立刻死机。这一点在搭建上位机或服务器端时最容易踩务必在固件文件命名或下载请求里明确标识 bin 后缀。3.4 App 里用 CCM RAM 也要注意启动拷贝F407 有一块 64KB 的 CCM RAM地址在 0x10000000只能由内核访问DMA 访问不到。如果把某些高频变量放到 CCM RAM链接脚本里要单独指定一个内存区域并且启动文件里负责把 data 段拷贝到 RAM 的循环需要认识这块区域。简单做法是只放零初始化变量不放带初值的变量这样启动代码不用增加额外的拷贝段CCM RAM 的 bss 清零逻辑在 GCC 下通常不用额外处理但使用不同启动文件时要在移植后实际测试一遍。4. 远程升级的协议处理固件下发、CRC 校验与回滚策略4.1 用 Ymodem 或自定义协议接收固件文件远程升级到了真正接收固件这一步方式不只一种。产品在局域网内可以通过以太网 TCP 把固件拉下来走现成的串口调试工具最常见的方案是 Ymodem 协议。IAR 和 Keil 的串口助手插件普遍支持 Ymodem直接把 bin 文件选中就传完了不用自己设计帧结构。如果固件已经放在 TF 卡或 U 盘里升级流程就是读文件、校验、写入。这里的主流程逻辑是一致的int upgrade_from_buffer(uint8_t *fw_buf, uint32_t fw_len) { if (check_crc32(fw_buf, fw_len, expected_crc) ! 0) { return -1; } if (erase_flash_sectors(APP_BACKUP_START, fw_len) ! 0) { return -2; } for (uint32_t i 0; i fw_len; i FLASH_WRITE_UNIT) { uint32_t write_len MIN(FLASH_WRITE_UNIT, fw_len - i); if (flash_write_bytes(APP_BACKUP_START i, fw_buf i, write_len) ! 0) { return -3; } } copy_backup_to_app(); NVIC_SystemReset(); return 0; }先校验再擦写这个顺序很重要。如果先擦后校验固件校验失败时原 App 已经被擦掉一大片现场回天无术。Flash 写入单位用 8 字节或 32 字节都行F407 的双字写入要求地址按 8 字节对齐固件长度如果不是 8 的倍数最后一段要单独处理。4.2 CRC32 校验要连版本号一起算CRC32 是嵌入式固件完整性的常见校验方式STM32F407 的硬件 CRC 外设能加速计算但要注意它算出的多项式是 0x04C11DB7初始值 0xFFFFFFFF结果不做异或反转和 Zlib 的 CRC32 并不一致。大多数上位机工具用的是 Zlib 的 CRC32两边对不上时校验必然失败。uint32_t crc32_zlib(uint32_t crc, const uint8_t *data, uint32_t len) { crc ~crc; while (len--) { crc ^ *data; for (int i 0; i 8; i) { crc (crc 1) ^ (0xEDB88320 (uint32_t)(-(crc 1))); } } return ~crc; }参数说明crc 参数初始传入 0xFFFFFFFFlen 是字节数返回的是标准 Zlib CRC32 值。写入 Flash 使用的固件文件应该在文件末尾附加 4 字节 CRC 值上位机生成 bin 时统计整个文件字节算出 CRC追加在末尾Bootloader 收完文件后先取最后 4 字节作为期望值再对前面的数据重新计算 CRC 比对。提示版本号也应该参与校验。两个设备固件版本不一致时Bootloader 直接拒绝写入避免高版本固件被低版本覆盖导致功能回退。做法是在 bin 文件头部预留版本号字段或在升级指令里单独携带版本字段。4.3 回滚策略搬到 App 区之前再验一次第 2 章分区时预留了 App_Backup现在说它怎么用。新固件写入 App_Backup 后不要急着搬先在 Bootloader 里对 App_Backup 再做一次完整 CRC 校验。为什么做两遍接收过程中写入 API 可能会意外出错第一遍校验只证明数据完整第二遍校验才证明 Flash 里的内容和内存里的一致。static int verify_flash_region(uint32_t addr, uint32_t len, uint32_t expect_crc) { uint32_t crc 0xFFFFFFFF; for (uint32_t i 0; i len; i 4) { uint32_t word *(volatile uint32_t *)(addr i); crc crc32_step(crc, (uint8_t *)word, 4); } return (crc expect_crc) ? 0 : -1; }搬移函数仍用前面的flash_write_bytes从 App_Backup 读出来写到 App 区。如果搬移过程掉电Flash 里可能留了一块不完整的 App这就要靠下一章讲的启动校验来兜底。4.4 硬件 IIC 和其他外设冲突时怎么处理升级过程中 Bootloader 如果遇到硬件 IIC 卡死或某个外设初始化时间过长直接在超时函数里重启会陷入升级失败死循环。处理方法是把 Bootloader 全程的看门狗喂狗时间调到 1 秒以上给整片 Flash 擦写预留充足时间。F407 的硬件 IIC 是出了名的难伺候如果产品主系统里用了硬件 IIC 读外部 EEPROM 或传感器在 Bootloader 阶段尽快把用不到的外设 Deinit 掉避免意外中断打断 Flash 擦写。擦写 Flash 期间任何中断都可能导致操作时序被拉长最坏情况是擦写正在进行时 CPU 进入低功耗模式Flash 控制器报错。5. 用调试器验证 IAP 升级链路断点、看门狗与现场恢复技巧5.1 第一次联调先别急着跑远程拿到一块崭新的 F407 板子我会先做一次有线 IAP 的最小验证Bootloader 烧到 0x08000000App 烧到 0x08010000然后断电重启。这一步就两个目的确认链接脚本地址没改错、确认跳转能成功。用 SWD 连接时把断点打在jump_to_app()的app_reset()那一行单步走进去能看到 PC 跳到 0x0801xxxx 附近再继续跑 App 里的串口打印。5.2 看门狗与升级超时的配合逻辑远程升级最怕设备在固件写入过程中被看门狗复位。独立看门狗 IWDG 一旦开启就无法关闭超时时间通常设成几百毫秒到几秒整片 Flash 擦除需要几十毫秒写一个扇区最多也就几百毫秒看起来够用但 Ymodem 或 TCP 传大固件时每包间隔的等待时间远比单个扇区擦写长。正确做法是把 IWDG 喂狗放在升级主循环里每收到一包数据就喂一次而不是放在 Flash 写入函数里。while (upgrade_state ! UPGRADE_DONE) { int ret await_packet_with_timeout(1000); if (ret TIMEOUT) { watchdog_feed(); continue; } if (ret PACKET_OK) { write_packet_to_backup(); watchdog_feed(); } }5.3 启动校验失败时的恢复技巧Bootloader 在每次上电后、跳转前都会检查 App 区的合法性。检查标准一般是栈顶地址落在 RAM 范围内以及首条 Reset_Handler 地址落在 Flash 的 App 区内。static int check_app_valid(uint32_t addr) { uint32_t msp *(volatile uint32_t *)addr; uint32_t reset_vec *(volatile uint32_t *)(addr 4); if ((msp 0x2FFE0000) ! 0x20000000) return 0; if ((reset_vec 0x2FFE0000) ! 0x08010000) return 0; return 1; }校验失败时 Bootloader 不跳转原地等待串口或网络重新发送固件。这个兜底逻辑让设备在升级断电后仍然能够进入恢复模式。实际项目中我还在 App 里加了一个升级标志位App 启动后跑三秒如果业务初始化失败就把 Flash 里某个专门存储系统状态的扇区写成“请求回滚”然后软复位。Bootloader 看到这个标志就把 App_Backup 重新搬到 App 区。这比单纯依赖外部指令触发回滚更稳因为设备往往在无人值守的现场等不到服务器来救。5.4 给升级失败留一条串口逃生门无论回滚做得再完善也有连 Bootloader 都进不去的极端情况比如 Bootloader 本身被误擦。此时只能靠 SWD 接口重新烧录。量产产品如果外壳密封把 BOOT0 引脚通过电阻拉到 3.3V配合串口 ISP 模式就能救回来这是 STM32 的系统存储器自带功能不需要外部编程器。验证手段上我会在 Bootloader 和 App 里各放一组不同的串口打印确认每次重启后进入的到底是哪个固件。升级完成后用bin文件的 SHA256 与服务器端记录比对确定写入的字节和服务器下发的版本完全一致才算一次远程升级真正闭环。本文还有配套的精品资源点击获取
返回列表