十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32 BootLoader串口升级实战:从Flash分区到IAP完整实现

STM32 BootLoader串口升级实战:从Flash分区到IAP完整实现 简介一套可直接参考的STM32 BootLoader串口UART升级工程源码基于STM32L4系列单片机与HAL库开发适合为现有产品增加IAP在线升级功能的嵌入式工程师。工程完整覆盖项目落地所需的核心环节串口初始化与数据分包接收自定义帧格式的协议解析CRC校验保证固件完整性FLASH按扇区擦除与写入以及Bootloader固件加载完成后的系统复位与APP中断向量重映射。源码中Flash地址划分、跳转条件判断和超时重传等处理均有清晰示例可帮助读者避开常见的升级变砖问题。压缩包包含109个文件其中C源文件30个、头文件72个辅以STM32CubeMX配置、Makefile编译脚本、链接脚本、启动文件和J-Flash烧录配置目录结构规范可直接导入工程编译或在现有项目中对照修改。整个资源包仅833KB轻量高效。目前已有1949人学习下载适合需要深入理解串口IAP机制与FLASH管理的中高阶开发者收藏实践。 做过量产产品的朋友应该都有过这种经历产品已经铺出去了突然发现固件有bug或者客户提了新功能要加进去。这时候你要是还扛着仿真器跑到现场拆机烧录客户不骂你才怪。解决这个问题最成熟的手段就是给STM32配一套BootLoader引导程序——让固件通过串口UART就能完成远程升级。这篇文章就围绕一套完整的STM32 BootLoader工程源码来拆解讲讲BootLoader的启动流程、Flash分区、串口协议设计、APP端配合改造以及我在实际调试中踩过的坑和对应的解决办法。不管你是刚接触IAP升级的新手还是正在做量产维护方案的老手这篇内容都应该能帮你少走不少弯路。1. BootLoader整体设计与思路拆解1.1 为什么工程师需要BootLoader先说个最简单的场景。你们研发部做了一块基于STM32的控制板批量生产了两千套发往全国各地。结果第一批货出去没两周客户反馈某个判断逻辑不对需要改固件。如果你没有预留BootLoader唯一的办法就是召回设备、打开外壳、接上ST-Link重新烧录。这一套流程下来差旅费、人工费、客户抱怨成本远超那几颗单片机的价格。有了BootLoader之后事情就变成现场人员只需要一根USB转串口线连接设备的调试串口在电脑上点一下发送固件几秒钟后设备自动完成Flash擦写和重启固件就更新好了。更重要的是这个升级通道从出厂到产品退役全程可用后续加功能、修bug、改参数都不需要动硬件。从技术架构上看BootLoader本身也是一个跑在单片机上的裸机程序但它和普通应用固件APP有一个本质区别BootLoader运行在Flash的起始区域负责管理和更新应用区代码。芯片上电后先执行BootLoaderBootLoader判断当前有没有升级请求有就接收新固件并写入APP区没有就跳转去执行已有的APP。这样整个芯片被划分为不同权限和功能的两个空间各司其职。1.2 BootLoader的启动与跳转流程让我用一个实际的启动流程来说明。芯片上电复位之后CPU从0x08000000取中断向量表开始执行BootLoader代码。BootLoader先完成这几件事初始化时钟树把系统时钟配到最高频率初始化串口UART波特率固定等待上位机下发数据初始化一个GPIO通常接一个按键或者跳线帽用来判断是否进入升级模式读取Flash上一个APP有效标志位我习惯放在Backup Register或Flash末尾一个固定地址之后BootLoader进入流程判断如果按键被按下或者上位机通过串口发来了特定的握手命令就进入升级模式否则检查APP的有效标志为真就直接跳转到APP的复位向量执行应用代码为假则留在BootLoader等待升级。升级完成后BootLoader会跳转到APP或者软复位让新固件跑起来。这段流程看起来简单但有一个非常关键的细节BootLoader的代码量要严格控制不能把Flash前段的空间全部吃光。因为APP的起始地址要靠后如果BootLoader写得太肥APP能用的空间就少了。按照我平时的经验BootLoader用标准外设库或者HAL库都能做但尽量裁剪掉不用的模块只保留启动代码、时钟、串口驱动、Flash驱动和协议解析这几样核心部分。1.3 方案选型UART、协议与Flash分区串口方案之所以是BootLoader里最普遍的选择原因很朴素UART是每颗MCU都有的外设三根线TX、RX、GND就能通信上位机端用任何USB转串口芯片都能解决。相比之下SPI和I2C都是板内总线不适合长距离和现场连接CAN虽然抗干扰能力强、支持多节点但在电脑端没有现成的接口需要额外的CAN卡成本高出一截。在传输协议上我见过不少工程师上了项目之后自己发明协议结果调了一个月还没稳定。其实行业内已经有非常成熟的选择方案优点缺点适用场景自定义协议灵活可随时扩展指令需要自己处理帧同步、CRC、超时重传学习IAP原理、特定私有协议Ymodem协议成熟稳定有现成上位机SecureCRT协议相对复杂帧类型多大多数量产项目首选Xmodem协议简单容易实现无文件名传输效率低嵌入式BootLoader入门我个人实际项目的选择是如果客户有现成的上位机管理软件配合他们的私有协议最省事如果是给标准产品做升级工具直接用Ymodem协议加上SecureCRT或者开源工具就能跑通根本不用自己写上位机。Flash分区的方案也需要提前想清楚。基础的双分区方案把Flash分成BootLoader区和APP区最经济实用进阶一点的做法是三分区在APP区后面再留一个备份区升级前先把当前运行的固件备份过去新固件写坏了还能回滚这个方案有效避免了升级失败直接变砖的生产事故。具体地址规划下一节细讲。2. 核心细节解析与实操要点2.1 Flash分区规划与地址映射以一颗常见的STM32F103RCT6为例它的Flash总容量是256KB地址范围从0x08000000到0x0803FFFF。我的分区规划是这样的区域起始地址大小用途BootLoader区0x0800000064KBIAP引导程序APP区0x08010000192KB应用固件这里为什么要留64KB给BootLoader其实一个精简的BootLoader编译出来只有10KB左右但多留一点空间有几个好处一是后面想加加密固件、校验算法、日志功能都有地方放二是Flash的前几个扇区在F1系列上大小不均匀分区对齐扇区边界可以避免擦除时误伤BootLoader代码。有一个特别容易踩的坑我必须提醒STM32F1系列的Flash页大小在中小容量和大容量型号上不一样。F103RCT6属于大容量Flash页大小是2KB而F103C8T6是中容量Flash页大小只有1KB。你如果直接套用网上某个工程的擦除地址很可能因为页越界导致HardFault。分区之前务必打开参考手册的Flash接口章节确认当前型号的页大小和扇区布局。2.2 中断向量表重映射的两条路径BootLoader跳转APP之前必须把中断向量表切到APP的起始地址。如果APP启动时中断向量表还指向0x08000000那么任何中断触发都会跑到BootLoader的向量表里系统立刻崩溃。F1系列的HAL库工程通常这么处理#define APP_START_ADDR 0x08010000 /* 跳转前关闭全部中断防止中断状态残留 */ __disable_irq(); /* 检查栈顶地址是否在RAM范围内防止误跳转 */ if (((*(volatile uint32_t *)APP_START_ADDR) 0x2FFE0000) 0x20000000) { /* 设置主堆栈指针为APP的栈顶值 */ __set_MSP(*(volatile uint32_t *)APP_START_ADDR); /* 取APP复位向量作为跳转目标 */ void (*jump_to_app)(void); jump_to_app (void (*)(void))(*(volatile uint32_t *)(APP_START_ADDR 4)); /* 跳转 */ jump_to_app(); }这段代码的关键有两个第一个if判断是在验证栈顶地址的值是不是落在SRAM范围内如果Flash里是空的或者数据损坏跳转前就能拦截下来第二个关键点是跳转之前把全局中断disable掉因为APP自己会在初始化流程里重新配置中断如果BootLoader留下来一个中断状态很有可能会卡死在新固件的启动阶段。而APP工程的修改同样重要。在Keil的Target选项卡里把IROM1的Start改成0x08010000Size改成0x30000也就是192KB。再在main函数的开头加上SCB-VTOR 0x08010000;这一步的意思是说告诉CPU新的中断向量表不在默认位置了请去0x08010000这里找。如果不做这一步APP的串口中断、定时器中断全都不会工作。2.3 APP工程必须配合的三件事很多人在自己的板子上写好了BootLoader却发现下载APP之后程序跑不起来检查半天发现是APP工程少了几个关键配置。我总结下来一份能在BootLoader环境下运行的APP工程必须做到以下三件事第一修改ROM起始地址。默认情况下Keil把IROM1配置成0x08000000这正好和BootLoader重叠。APP一旦下载直接覆盖掉了BootLoader的代码根本不可能实现引导。改成0x08010000后APP的链接地址和实际存放地址才能对上。第二设置中断向量表偏移。HAL库工程可以在stm32f1xx_hal_conf.h里找到VECT_TAB_OFFSET这个宏把它设置成0x10000即可。如果用的是标准外设库那就在系统初始化函数的末尾手动执行SCB-VTOR 0x08010000;。注意这两个只做一样就行做过或者漏做都会出问题。第三工程编译要生成bin文件。hex文件虽然也能传输但解析起来比bin稍显麻烦通用的IAP工具和Ymodem协议大多数都直接吃bin。Keil里可以在User选项卡的After Build/Rebuild下面加上一句fromelf --bin --outputL.bin L.axf这样每次编译完成目录下就会自动生成一个.axf同名.bin文件直接丢给上位机发送即可。3. 实操过程与核心环节实现3.1 串口通信协议设计丢包、超时与校验协议是整个BootLoader的通信骨架。这一节我给出一个我在实际项目中验证过的自定义协议帧格式既简单又可靠非常适合初学者理解和改动。帧格式采用定长帧头 变长数据的方式帧头2字节(0xAA 0x55) | 命令1字节 | 数据长度2字节(小端) | 数据区0~1024字节 | CRC32校验4字节命令字定义如下命令码含义数据区内容0x01握手请求版本号、芯片型号0x02开始传输固件总长度、CRC320x03数据帧固件分包数据0x04结束传输无0x05复位跳转无上位机每发一帧BootLoader收到后先做帧头匹配和CRC32校验通过后处理命令并回一个ACK字节0x06或者NACK字节0x15。上位机如果以500ms为超时周期没收到ACK就自动重发当前帧最多重发3次超过3次则判为通信失败。这个设计为什么靠谱因为它把数据完整性的责任压到了CRC32身上把超时重传的责任交给了上位机BootLoader本身只需要按状态机逐帧处理。实际测试下来波特率115200、每帧1KB数据的情况下一个64KB的固件大概10~12秒传完稳定性很好。3.2 固件写入Flash的关键函数实现数据帧解析通过后接下来就是把固件写入Flash。擦写Flash之前必须先解锁擦除对应扇区再编程最后上锁。这里给出核心代码void firmware_write(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t word_data; uint32_t aligned_addr addr ~0x3; HAL_FLASH_Unlock(); /* 判断是否需要擦除新扇区 */ if ((addr 0x7FF) 0) { FLASH_EraseInitTypeDef erase_cfg; uint32_t page_error 0; erase_cfg.TypeErase FLASH_TYPEERASE_PAGES; erase_cfg.PageAddress addr; erase_cfg.NbPages 1; HAL_FLASHEx_Erase(erase_cfg, page_error); } for (uint32_t i 0; i len; i 4) { word_data *(uint32_t *)(buf i); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, aligned_addr i, word_data); } HAL_FLASH_Lock(); }这个函数的处理思路是每写一页数据之前按页边界判断是否需要擦除当前页。这里有一个实际踩坑的细节如果上位机发过来的数据帧长度不是4字节对齐的直接强转成uint32_t读取会越界读到内存垃圾。解决方法是BootLoader里维护一个4字节对齐的缓冲区先填满再整帧写入Flash。还有个容易被忽略的地方Flash编程必须按字Word写入并且地址要对齐到4字节。如果你收到的一帧数据只有1KB的整数倍那没问题但最后一帧数据往往不满1024字节必须填充到4的倍数否则HAL_FLASH_Program会返回错误。3.3 从烧录到远程升级完整实测流程我把一套完整的升级流程串一遍大家对照着操作基本不会迷路。第一步先用ST-Link通过SWD接口把编译好的BootLoader工程烧录到STM32里烧录完成后拔掉ST-Link。这里注意不要让BootLoader和APP同时下载我们先用串口来升级APP正好可以验证整个链路是否打通。第二步修改APP工程的ROM起始地址、设置中断向量表并配置生成bin文件编译得到一个app.bin。第三步目标板上电在BootLoader检测按键的GPIO上短接到GND让设备进入升级模式。设备会通过串口打印一条IAP Ready或者LED快速闪烁。第四步打开上位机工具。如果是自研的上位机直接加载app.bin后会按照前面定义的协议自动发送如果手头没有工具用SecureCRT的Ymodem协议或者STM32CubeProgrammer的UART模式也能完成发送。第五步观察串口日志。每一帧数据发送完后BootLoader会打印帧序号和CRC结果最后打印Firmware Upgrade Success后自动复位。如果日志停在某帧上反复打印NACK就说明链路有问题。第六步复位之后如果APP正常运行LED节奏变化、串口打印APP版本号就说明整个BootLoader APP升级链路已经完全打通。之后你再改任何功能只需要生成新的bin文件通过串口发送即可再也不用拆机了。4. 常见问题与排查技巧实录4.1 连接不上仿真器与驱动问题这类问题在我刚开始做STM32项目时也踩过不少。最常见的就是ST-Link报Error: No STM32 target found!这种情况的原因通常集中在几个点上。先检查ST-Link和目标板的接线SWDIO、SWCLK、GND三根线必须确认无误SWDIO和SWCLK不要接反。然后确认目标板有没有独立供电并且供电稳定。ST-Link本身可以从USB取电但驱动能力有限如果目标板上还有其他外设建议单独供电。还有一个容易被忽略的是复位引脚上的电容过大导致目标板一直被复位联不上调试器这种情况可以尝试在复位脚串联一个100Ω左右的电阻。另一个高频报错是设备管理器里FT232R或者FT231X的USB转串口设备带着黄色感叹号。这是驱动没装好或者被系统识别成其他设备导致的。处理方法是在设备管理器里右键更新驱动手动指定到驱动目录强制安装如果系统提示驱动数字签名报错进启动设置禁用驱动强制签名之后重新安装。4.2 跳转失败程序哑火如何排查跳转失败的表现通常是BootLoader日志显示Jump to App打印出来了但是设备没有任何反应LED不闪、APP也不打印。我排查这类问题有一套固定的顺序。第一步用调试器连上看PC指针和SP指针的值。如果SP的值不是0x2000开头的RAM地址说明APP区第一个字就不是合法的栈顶地址要么是APP没烧进去要么是烧错位置了。第二步检查APP工程的IROM1起始地址确认是不是0x08010000而不是0x08000000。第三步确认APP工程中的中断向量表设置是否生效可以在APP的main函数最前面打断点如果断点能命中说明跳转本身没问题问题出在后续初始化那就检查串口、时钟配置是否有冲突。还有一个比较阴的坑BootLoader跳转之前把系统时钟配置到了72MHz但你在APP工程里用SystemInit再次初始化时钟。如果APP的SystemInit代码在向量表重映射之前执行了并且里面调用了依赖中断的外设就有概率卡死。我习惯把中断向量表重映射放到SystemInit之后、所有外设初始化之前避免这个时序问题。4.3 传输失败、丢帧与死锁串口升级过程中最让人上火的就是传输到一半反复重传甚至死锁。这一类问题我总结了几个高概率原因。波特率设置不匹配首当其冲。BootLoader端和上位机端必须保持完全一致的波特率115200是常用标准但某些USB转串口芯片在115200下的波形质量一般如果是长线传输可以降到57600或者38400稳定性会明显提升。其次是没有共地。USB转串口模块和目标板之间不能只接TX、RX一定要挂着GND否则串口的电平参考地不一致大概率出现乱码或者间歇性丢帧。这个问题在很多DIY玩家和刚入门的初学者身上出现率极高你看着线接对了实际上一端是USB供电另一端是独立电源两边地电位不相等通信根本不可靠。还有一个和Flash擦写相关的死锁点如果BootLoader在擦除Flash扇区的时候串口中断向量恰好也在Flash里面擦除过程中CPU会跳去执行被擦除的代码直接HardFault。这种情况下升级永远会在同一帧卡死。解决办法是在擦除Flash期间关闭串口中断等擦写完成后再打开。最后聊聊变砖的兜底方案。升级过程中断电、串口线松动都会让Flash里的APP区处于半写半空状态。纯双分区方案这时候只能重新用仿真器救非常麻烦。我的建议是如果产品需要长期在现场维护一定要做一个双映像方案Flash里留一份出厂固件作为备份区每次新固件先写入运行区写完校验通过后再把运行区拷贝到备份区下次升级如果校验失败还能从备份区恢复运行。这个方案虽然要多占Flash空间但救回来的可不是一块板子而是客户的信任。我个人在实际项目中还有一个习惯把BootLoader做成一款最小可用产品给它打上固件版本号、编译时间和启动日志。因为每次现场出了诡异问题第一件事就是问现场人员设备起来没有、BootLoader版本是多少、有没有打印信息。有了这些基础信息远程排查故障会轻松很多。这个习惯已经不止一次帮我避免了亲自跑现场也推荐各位在搭建自己的BootLoader工程时把这个设计考虑进去。本文还有配套的精品资源点击获取
返回列表