
简介一份针对STM32F091芯片完成openBLT移植的完整嵌入式工程适合正在学习或实施Bootloader开发的嵌入式工程师尤其关注RAM占用优化的场景。工程双区划分清晰Boot区负责固件检测与跳转Prog区为应用程序作者强调真正完成移植并合理规划RAM使用避开了常见内存冲突。资源共522个文件以h头文件、c源代码为主配合uvprojx/uvoptx工程文件、sct链接脚本、hex/srec/axf固件及map/lst辅助分析文件可直接重新编译或烧录验证代码覆盖HAL库中I2C、TIM、UART、CAN等多种外设驱动包含从驱动初始化到中断处理的完整参考帮助理解STM32CubeMX生成的工程结构。整个压缩包约22.76MB目前已有557人学习较高的热度也验证了该工程的实际可用性。对需要在STM32F091等F0系列上快速搭建可靠Bootloader的开发者来说这套工程提供了从源码到二进制的一条完整参考路径移植要点、内存配置与调试思路均可直接复用尤其适合RAM资源受限、需要稳定启动流程的嵌入式项目。 做嵌入式越久越明白一件事只要产品要发货到客户现场bootloader就早晚躲不掉。之前做过一个用STM32F091RCT6做的CAN网关盒子设备发出去几百台固件出了个不大不小的bug如果能远程升级一个下午就能解决没有bootloader就得给客户寄返修件或者工程师亲自飞过去刷机。那之后我认真研究了一圈开源方案最终选了openBLT做移植这篇就把在STM32F091上移植openBLT的完整过程、关键配置和踩过的坑都记录下来给同样准备做CAN远程升级的朋友一份参考。openBLT是一个开源的bootloader项目由Feaser团队维护GPLv3授权。它最吸引我的地方不是“能跳转app”这种基本功而是把整个升级链路都帮你建好了单片机端有bootloader源码PC端有BootCommander命令行工具和BootExplorer图形工具传输层支持CAN、UART、USB、TCP/IP固件传输过程中有CRC校验还支持固件签名和加密。这对一个要快速落地的项目来说价值非常大。下面我从选型逻辑、存储布局、boot工程移植、app工程改造、实机验证这五块来展开。1. 为什么我选了 openBLT 而不是自己写 Bootloader1.1 自研 Bootloader 的代价很多嵌入式工程师包括以前的我遇到升级需求第一反应是自己写一个boot。思路也不复杂上电判断某个标志位或者判断外部输入然后通过串口收数据、写flash、跳转。看起来很简单但真正做起来坑一个接一个。首先是协议设计。你自己定义的协议就得自己写上位机或者让PC端的同事配合你调试。如果只是开发阶段自己用还好一旦要给产线用、给售后用对方能接受的工具形态就完全不同了。其次是异常处理传输到一半掉线怎么办固件校验失败了是停在boot还是继续跑旧app写入过程中突然断电会不会变砖最后还有一个容易忽略的问题——你的boot代码也是写flash的如果boot本身有bug产品就真成砖了。自研boot这套组合拳下来工作量远比表面上看到的“读串口、写flash”大得多。1.2 openBLT 解决了哪些核心问题openBLT把上面这些痛点基本都覆盖了。它在单片机端只负责一件事通过CAN、UART或USB接收固件数据校验通过后写入app区。但它做得很完善它有备份区机制。收到新固件后不是直接擦掉app区而是先存到备份区整体校验通过再搬移到app区最大限度地避免“升级一半断电导致变砖”。它有配套的成熟上位机。BootCommander通过命令行就能完成下载支持SREC、HEX、BIN格式省去自己写上位机的时间。它支持XCP和自定义协议未来如果要做标定或者诊断可以共用一条CAN总线不需要额外增加通信接口。它还支持固件签名和AES加密可以防止别人提取你的固件或者防止非官方固件被刷进去。1.3 和其他方案横向对比对比项自研BootloaderopenBLTSTM32系统内置Bootloader商业收费方案上位机自己写自带开箱即用官方工具体验一般厂商提供通信协议自己定义标准协议文档完善仅UART/USB/DFU一般完善固件校验自己实现CRC32可选加密签名有校验机制有断线保护自己设计备份区机制成熟依赖应用配合有License无限制GPLv3商业注意无限制收费综合下来openBLT在开源免费的前提下覆盖了自研方案最耗时的那几块而且源码全开放出问题能自己查。当时我就决定就用它。2. STM32F091 移植前的准备工作先搞清楚存储布局再动手2.1 我的硬件环境与移植目标我手上的板子是自研的CAN通信控制板主控芯片是STM32F091RCT6Cortex-M0内核最高48MHz256KB Flash32KB SRAM板载一个TJA1050 CAN收发器CAN_RX和CAN_TX分别接到PA11和PA12外部晶振8MHz。移植目标很明确通过CAN总线用openBLT的上位机给板子远程升级固件。这里先提个醒。STM32F091虽然属于STM32F0系列但它的Flash页大小和F030、F050这些低密度型号不一样。F091的Flash按2KB一页组织具体以STM32F091参考手册RM0091为准而F030这类型号是1KB一页。这个差异会直接影响到openBLT里Flash驱动擦除函数的逻辑移植时千万不能拿着F030的demo直接套后面我会专门说这个问题。2.2 Flash 布局设计boot区、app区、备份区怎么划openBLT在下载固件时会用到三个区域bootloader区、app区、备份区。这三种区域都在内部Flash里布局在blt_conf.h和链接脚本里定义。我当时定的布局如下区域起始地址大小用途Bootloader区0x0800000032KBopenBLT bootloaderApp区0x08008000192KB应用固件备份区0x0803800032KB升级时暂存固件配置区含校验信息Flash末尾保留若干字节openBLT记录固件校验状态这里重点说两点。第一bootloader区设32KB。openBLT本身很小精简配置后十几KB就够但考虑到我用了HAL库加上预留一些打印和调试功能32KB比较稳妥。app区留了192KB对大多数应用来说完全够用。备份区我放了32KB因为如果固件超过这个大小备份区就放不下新固件会直接导致升级失败。一个经验做法是备份区大小至少等于app区大小或者不小于你预期最大固件体积的1.5倍。第二升级时Bootloader并不是直接把数据写进app区。它是先把整包固件写到备份区等全部数据收完、CRC校验通过再把备份区的内容整体搬到app区。这样设计的好处是如果在数据传输阶段掉线或者断电app区还是旧固件板子重新上电还能正常跑不会变砖。理解这个流程对后面排查问题很有帮助。2.3 先把官方 Demo 跑通再动自己的板子openBLT官方仓库的Target/Demo目录下有针对各种开发板的demo工程其中就包括STM32F0xx系列的。我的建议是拿到源码后不要上来就改自己的板子先在官方开发板上把整个升级流程跑通一次。哪怕你手上没有一模一样的官方板也先看一下demo工程的目录结构和配置逻辑。openBLT的demo工程分为boot和app两部分boot是bootloader工程app是一个闪烁LED的示例应用。先编译、下载、上位机联调把“boot跳转app、上位机升级固件”这条路走通你才算真正理解了这套机制。我见过太多人上来就改自己板子的config结果boot编译不过或者跳转失败最后连是配置文件的问题还是硬件的问题都分不清楚。3. Bootloader 工程移植实录链接脚本、CAN 驱动与核心配置3.1 链接脚本把 bootloader 关进笼子里boot工程的第一步是改链接脚本让bootloader只使用0x08000000开始的32KB空间。以GCC工具链的stm32f0xx_flash.ld为例核心就是改这两行FLASH (rx) : ORIGIN 0x08000000, LENGTH 32K RAM (xrw) : ORIGIN 0x20000000, LENGTH 32K链接脚本的作用是把bootloader约束在它自己的区域内。很多人忽略这一步直接把demo编译出来就烧结果boot和app的链接地址重叠跳转之后必然跑飞。如果你用的是MDK就改Target页里的IROM1起始地址和大小IAR就改链接配置文件里的ICF。改完以后编译生成的boot.hex起始地址应该是0x08000000大小不超过0x8000。3.2 CAN 驱动的移植与配置openBLT在STM32F0xx的demo里带了CAN驱动但demo默认用的是官方评估板的引脚配置和库函数版本。如果官方demo用的是标准外设库而你自己的工程用的是HAL库驱动就需要自己适配一遍。CAN驱动要改的核心点有三个CAN引脚。STM32F091的CAN外设默认复用引脚是PA11(CAN_RX)和PA12(CAN_TX)如果你的板子把CAN引脚换到了PB8/PB9或者其他带CAN功能的引脚需要改GPIO初始化代码。CAN波特率。openBLT默认demo里CAN波特率常见配置是500kbps或1Mbps这个值必须和上位机BootCommander里设置的一致否则通信握手都完成不了。我在blt_conf.h里配置如下#define BOOT_COM_CAN_BAUDRATE (500000u) #define BOOT_COM_CAN_TX_PORT_PIN (12u) #define BOOT_COM_CAN_RX_PORT_PIN (11u)CAN过滤器和收发ID。openBLT在总线上有自己的一套ID规则boot和上位机按这个ID通信。如果你的板子上CAN总线还有其他节点建议用CAN分析仪先看一下升级时总线上有没有ID冲突。openBLT源码里CAN过滤器初始化后只接收自己关心的ID其他ID一概不理这个逻辑不要乱改。有个硬件上的坑最容易踩TJA1050这类CAN收发器有些型号的STB待机模式控制或RS引脚如果不接对电平收发器会一直处于待机或静音状态。我调试时遇到过BootCommander一直发送失败逻辑分析仪却能看到TXD/RXD有波形最后发现是TJA1050的STB引脚被默认拉高了收发器根本没工作。出现“软件怎么调都连不上”的情况先拿万用表量一下收发器的STB、VREF这些引脚电平别一开始就怀疑代码。3.3 blt_conf.h 里那些决定成败的宏openBLT的配置高度集中在blt_conf.h这个文件里。这个文件相当于整个bootloader的“总开关”你必须逐个搞清楚每个宏的含义而不是拷贝一份就不管了。以下几个宏是我认为移植时必须重点确认的宏名称作用我的建议BOOT_COM_CAN_ENABLE是否启用CAN通信确认置1BOOT_COM_CAN_BAUDRATECAN波特率和上位机保持一致BOOT_FLASH_VECTOR_TABLE_CS_OFFSET固件向量表校验值偏移默认即可BOOT_FLASH_CUSTOM_BACKUP_LOCATION是否自定义备份区地址启用并确保地址在Flash末尾BOOT_COM_CAN_TX_PORT_PIN / RX_PORT_PINCAN收发引脚号根据原理图修改特别说一下BOOT_FLASH_VECTOR_TABLE_CS_OFFSET。openBLT在跳转app之前会校验app向量表的校验和如果校验不过它会认为app不是有效的固件拒绝跳转。这个校验值是编译器在链接阶段自动生成的通常放在向量表末尾偏移量是0x1C也就是中断向量表的最后一个word。如果你的app工程在重定位向量表时把这个校验值弄丢了现象就是boot能烧录固件但烧完永远跳不到app卡在boot里。还有一个被忽略的配置BOOT_COM_CAN_EXTENDED_ID或类似ID相关选项具体名称以你下载的openBLT版本为准。标准帧和扩展帧的选择必须和上位机一致否则你看到的永远是“timeout waiting for response”。4. App 工程改造向量表偏移、握手确认与固件生成4.1 让 App 知道自己在 0x08008000app工程的第一件事就是改链接地址。原来app的Flash起始地址是0x08000000现在要从0x08008000开始。MDK里就是在Target页把IROM1的Start改成0x08008000Size改成0x30000192KB。但这还不够。芯片上电后默认从0x08000000读取向量表现在app在0x08008000如果不去设置向量表偏移中断来了连中断服务函数都找不到程序一跑中断就hardfault。STM32F091是Cortex-M0内核同样有VTOR寄存器地址0xE000ED08在系统初始化早期设置即可。我是在SystemInit函数之后main函数最开始的位置加了一段#define APP_VECTOR_TABLE_ADDRESS 0x08008000u void App_VectorTableRelocate(void) { SCB-VTOR APP_VECTOR_TABLE_ADDRESS; }注意这个操作要在任何外设中断使能之前执行最好放在main最前面或者直接在system_stm32f0xx.c的SystemInit函数末尾追加。如果你用的是RTOS还要确保启动第一个任务之前VTOR已经设置好。另外有的Cortex-M0芯片不支持VTOR那就需要通过修改SYSCFG-CFGR1的MEM_MODE来重映射但STM32F091的VTOR是可用的可以放心用。4.2 加入 openblt lib 并完成握手确认app工程除了改链接地址和向量表还要加入openBLT提供的一个轻量级库文件libopenblt源码在Target/Source/LibOpenblt目录下。很多人不理解“app里为什么还要放一段bootloader的代码”其实这个库不是为了启动bootloader而是为了向bootloader报告app运行状态。具体机制是这样的bootloader下载完固件并跳转app之后bootloader并不知道app是否真的跑起来了。如果app跑飞了产品就变成了“看似升级成功实际无法工作”的状态。为了确认app正常启动app在上电初始化完成后需要主动通过这个库向bootloader“报个到”。如果bootloader在超时时间内没有收到这个“报到”信号它就知道app没起来会重新尝试跳转或者进入bootloader等待升级。所以app工程的集成工作很简单把LibOpenblt的源码文件加入app工程。在app初始化流程中调用对应的握手API。确保编译时包含正确的头文件路径。这个库的代码量非常小不会对app工程造成负担。但它的作用很关键——没有这段握手代码你的app可能每次上电都会被bootloader误判为启动失败最终卡在bootloader里不跳转。4.3 编译生成可用于下载的固件app编译完成后要生成带地址信息的SREC文件或者HEX文件上位机才能正确烧写。我建议用SREC而不是BIN因为SREC每一行都带地址信息BootCommander烧写时能校验目标地址是否在app区出错概率低。不同工具链生成SREC的方式MDKfromelf --srec -o app.srec app.axfIARielftool --srec app.out app.srecGCCarm-none-eabi-objcopy -O srec app.elf app.srec当时我在MDK环境下用过fromelf --bin --output app.bin app.axf用BIN也能烧但只要你的hex/srec里地址信息不全或者用了错误的偏移BootCommander可能把数据写到Flash开头覆盖掉bootloader直接造成假砖。所以强烈建议用SREC作为标准发布格式并且每次发布固件前用十六进制编辑器检查一下SREC里的地址是不是从0x08008000开始。5. 实机验证与问题排查从 BootCommander 到上电跳转5.1 用 BootCommander 跑通第一次升级boot工程和app工程都编译通过后先把bootloader通过SWD烧进板子保持app区为空或者烧一个旧固件然后通过CAN连接上位机。我用的完整命令类似下面这样具体参数以你下载的openBLT版本README为准BootCommander -d can -s openblt -c can0 -b 500000 app.srec解释一下参数-d can表示使用CAN通道-c can0是上位机那边的CAN设备接口-b 500000是CAN波特率。执行后BootCommander会先发送握手指令bootloader回应后开始传输固件。传输过程中能看到进度百分比和校验结果。如果你在Windows下也可以用BootExplorer图形化工具连接配置更直观适合产线使用或者给不熟悉命令行的同事用。第一次升级成功那一刻BootCommander提示“firmware update successful”然后板上app开始运行这个正反馈是很强的。但真正让我记住这次的是后面那次失败排查。5.2 一次“下载超时”的完整排查链路我调试过程中出现过一次典型的失败场景BootCommander能正常发出握手指令但一直等不到bootloader的响应直到超时。我把整个排查过程整理成一条链路供参考。现象BootCommander执行后卡在“synchronization”阶段没有任何进度。第一步排除CAN收发器硬件问题。用示波器测CAN_H和CAN_L之间的差分波形。测下来bootloader复位瞬间能看到总线有活动但很快就安静下来。这说明bootloader的发送侧是通的至少CAN收发器本身工作了。这里很快就排除掉了TJA1050的STB引脚问题。第二步确认上位机CAN设备本身。我换了一个CAN调试工具用上位机自发自收确认电脑到CAN总线的链路没问题。如果这一步不过就要查驱动、查设备接线。第三步回到bootloader源码检查CAN过滤器。最终问题出在CAN过滤器ID配置和上位机不一致。openBLT的CAN过滤逻辑默认只接收特定ID的数据帧我移植的时候复制的旧配置文件里ID值和当前boot源码里定义的ID对不上导致bootloader压根没把上位的握手帧收进来。上位机发出去了boot收到了但是被过滤器扔掉自然不回应。把ID改成一致后重新编译烧录bootloader一次就通了。这个排查过程想表达的是出现“连不上”的问题按硬件链路由底向上排查而不是上来就改代码反复试。嵌入式联调逻辑分析仪和示波器比猜代码高效得多。5.3 移植中容易踩的隐藏坑最后集中说几个我实际遇到、或者身边同行遇到过的坑都比较隐蔽。扇区大小不能想当然。openBLT在擦除Flash时按扇区页来操作。STM32F091的页大小是2KB但如果你从F030的demo改过来或者复制了F1的配置擦除逻辑就会出错。轻则擦不干净重则把相邻区域的数据也抹掉。移植时一定要去芯片参考手册里查自己型号的页大小并在flash驱动里核对。备份区和app区重叠。很多人改链接脚本时只注意了app起始地址没注意备份区。如果备份区起始地址设置到了app区内部升级时bootloader往备份区写数据就会把旧app覆盖掉一旦传输中途失败设备直接变砖。检查方式很简单把blt_conf.h里三个区域的首地址和长度画在纸上确保三个区间互不重叠。跳转app后跑hardfault先查向量表。app跑飞的原因九成是向量表没有正确重定位或者重定位代码在中断使能之后才执行。还有一种情况是app工程里链接脚本没改代码还链接在0x08000000但VTOR指向0x08008000两边对不上。检查方法是查看编译生成的map文件里Reset_Handler的地址如果不在0x08008000附近那链接脚本一定有问题。看门狗没喂。如果你的app里使能了独立看门狗IWDG而bootloader跳转app后app因为某些原因初始化比较慢看门狗可能先复位了芯片。openBLT在boot阶段也会喂狗但跳转前后这个交接点很容易出问题。建议app上电后第一时间关闭看门狗或者尽快重新初始化别等初始化全部完成再喂狗。写在最后的个人体会整个移植过程真正花时间的不是改代码而是理解openBLT的设计思路。备份区机制、固件校验、握手确认这些看似“多此一举”的设计全都是在真实产品环境里踩过无数坑之后沉淀出来的。我最大的体会是拿到一个成熟的开源bootloader第一件事不是猜它怎么用而是把它官方的demo流程完整跑一遍哪怕多花半天也值得。后续如果你还想接着扩展有几个方向可以做一是把传输通道从CAN换到UART或者USBopenBLT支持多通道并存移植思路类似二是给固件加上签名和加密防止固件被提取或篡改三是把openBLT和RTOS配合起来让bootloader和app共用一部分底层驱动减少Flash占用。这些我后面有时间再单独写先把这份基础移植经验分享出来希望能让你少走几步弯路。本文还有配套的精品资源点击获取