ARTICLE DETAIL

资讯详情

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

STM32官方Bootloader串口IAP升级:原理、实操与避坑指南

STM32官方Bootloader串口IAP升级:原理、实操与避坑指南 做嵌入式开发的朋友都应该遇到过这种情况产品已经在客户那边跑了大半年突然发现一个逻辑Bug或者客户提了新需求要改功能。这时候最头疼的不是改代码本身而是怎么把手里的新固件刷到设备里去。拆机、接烧录器、连线、下载一套流程下来费时费力不说遇到封装比较小或者打了胶的板子简直想砸东西。我最早做STM32F103C8T6项目的时候也被这个问题卡了很久后来把官方bootloader配合串口做IAP升级这套玩法彻底摸透了才算是真正解脱出来。所谓官方bootloader就是STM32芯片出厂时在系统存储器System Memory里固化的一段引导程序用户既不能修改也不能擦除。它的作用很单纯通过串口、CAN或USB等接口把用户代码直接写入芯片内部的Flash。对于STM32F103C8T6这颗芯片来说最常用的就是USART1串口下载方式。我们不需要额外写任何引导代码只需要把BOOT0引脚拉高让芯片上电后从系统存储器启动再用PC端的下载工具把编译好的HEX文件通过串口发过去就能完成固件更新。对开发阶段快速调板、小批量产线刷机、售后远程协助升级来说这套方案性价比极高硬件成本就是几块钱的USB转TTL模块软件工具则完全免费。这篇文章适合谁看如果你是刚接触STM32的小白想搞明白怎么不借助ST-Link也能给板子烧程序或者你已经在做产品正在纠结如何设计可靠的固件升级方案这篇文章都能提供一个直接可落地的参考。我会把原理、接线、工具配置、操作步骤、中断向量表重映射这些关键细节一次讲透也会把那些资料里很少写、但实际开发中一定会踩的坑全部翻出来。1. 先搞懂原理为什么BOOT0拉高就能下载程序1.1 三种启动模式与官方bootloader的定位STM32F103系列芯片上电后CPU会从哪个地址开始取指是由BOOT0和BOOT1两个引脚的电平状态决定的。具体映射关系如下表所示BOOT0BOOT1启动区域说明0任意主Flash正常运行用户程序也就是我们平时的工作模式10系统存储器运行出厂固化的bootloader用于串口下载程序11SRAM调试用程序在RAM中运行掉电即失很多人第一次看到这张表会有点懵其实理解起来很简单芯片上电后第一件事就是去读取0x00000000地址处的初始栈指针以及0x00000004地址处的复位向量然后跳过去执行代码。这三种启动模式本质上是把不同的物理存储区域映射到了0x00000000这个地址上。当BOOT01、BOOT10时0x00000000映射到System Memory也就是那一段出厂固化的指令区。注意这里有个关键细节STM32F103C8T6的引脚上那个BOOT1其实是PB2引脚在芯片内部已经下拉所以很多时候我们只控制BOOT0一个引脚就够了。1.2 官方bootloader到底做了什么当芯片从系统存储器启动后CPU执行的是ST出厂时写好的引导程序。这段程序会初始化USART1PA9为TXPA10为RX等待PC端下载工具发送特定的命令帧。它以固定的通信协议接收数据将固件数据写入用户Flash区域并支持擦除、校验、读保护设置等操作。整个过程完全由ROM里的代码完成我们在应用层不需要做任何干预。这里要顺带澄清一个概念。严格来说通过官方bootloader下载程序标准术语叫ISPIn-System Programming在系统编程而在应用运行时跳转到自研bootloader再更新固件的做法才叫IAPIn-Application Programming在应用编程。但国内嵌入式圈子里大家交流时经常混用这两个词既然标题用了“IAP升级”我后面就顺着这个叫法但心里要清楚门道。真正的IAP方案需要自己写bootloader我在文章最后会展开讲。1.3 为什么选择官方bootloader而不是自研引导程序STM32F103C8T6的官方bootloader方案最大的优势就是零代码成本。芯片内置的引导程序经过ST官方长期验证稳定性有保障不需要占用用户Flash空间来存放bootloader整个64KB Flash全部可以留给应用程序支持HEX文件直接下载不需要额外的格式转换配合FlyMcu或STM32CubeProgrammer这类工具操作非常直观。它适合的场景包括产品开发阶段频繁更新固件、小批量产线烧录、现场售后通过串口升级等等。而自研bootloader的优势在于可控性更强可以实现应用内升级、加密校验、回滚机制等高级功能但它会占用Flash空间而且一旦bootloader本身有Bug设备变砖的风险也比较大。官方bootloader方案作为第一步能让我们用最少的成本跑通整个升级链路理解原理之后再去设计自研方案就有了一个非常扎实的基础。2. 动手前的准备工作接线、跳线、工具链2.1 需要准备哪些东西硬件方面最核心的就是一块STM32F103C8T6最小系统板我试过市面上的Blue Pill开发板也可以用自己画的板子。一个USB转TTL模块CH340或CP2102都可以注意模块的IO电平必须是3.3V如果买到5V电平的模块轻则通信异常重则烧芯片。还需要几根杜邦线。软件方面推荐准备两个工具FlyMcu和STM32CubeProgrammer。FlyMcu是国内开发者做的老牌工具界面简洁一键下载的体验做得很顺手STM32CubeProgrammer是ST官方出品的编程工具功能更全面支持HEX、BIN、ELF等多种格式后续做批量生产也用它。2.2 进入系统存储器模式的跳线设置拿最常见的Blue Pill开发板举例板上有一个标着BOOT0和BOOT1的跳线帽。要让芯片进入官方bootloader需要把BOOT0跳线拨到1位置高电平BOOT1跳线保持在0位置低电平。然后按一下复位键或者直接重新上电芯片就会运行系统存储器里的引导程序。这里有个经验修改BOOT0跳线之后必须复位或者重新上电才会生效。如果你只是把跳线从0拨到1芯片仍然在跑旧程序很多新手就在这一步卡了很久。2.3 串口接线必须交叉连接官方bootloader使用USART1作为下载接口对应引脚是PA9和PA10。USB转TTL模块和STM32之间需要交叉连接这一点极其重要电脑USB转TTL模块STM32F103C8T6TXDPA10USART1_RXRXDPA9USART1_TXGNDGND请注意GND必须共地否则通信波形不稳定经常会出现能识别芯片但是下载中途失败的情况。3.3V电源线可以接也可以不接如果开发板使用独立供电就只接TXD、RXD和GND三根线如果使用USB转TTL模块直接给板子供电则把3.3V也接上。但务必确认模块的输出电压是3.3V且供电电流足够否则在擦写Flash这种电流较大的操作时电压跌落会导致整片Flash数据损坏。3. 核心实操用FlyMcu完成一次完整固件下载3.1 第一步让Keil生成HEX文件在写下载步骤之前先说一个前置条件。默认情况下Keil MDK编译STM32工程只会生成AXF文件需要额外勾选一个选项才会生成HEX文件。操作路径是Options for Target → Output选项卡 → 勾选Create HEX File。这个选项勾上之后每次编译成功工程目录下的Objects或Listings文件夹里就会多出一个.hex文件FlyMcu下载时选它即可。如果你希望把APP放到偏移地址并在Flash开头放一个跳转引导程序那么还需要在Target选项卡里设置IROM1的起始地址和大小。举例来说如果Flash总共64KB引导程序占16KBAPP就从0x08004000开始那么IROM1的Start设为0x08004000Size设为0xC000。这个设置直接决定了链接器生成的代码地址一旦搞错下载成功也会跑飞。3.2 第二步FlyMcu的连接参数配置把USB转TTL模块插到电脑上打开设备管理器确认串口号。然后打开FlyMcu左侧区域进行如下配置选择串口就是刚才记录的COM号波特率默认115200即可如果下载线质量好、环境稳定可以改到460800提高速度勾选“校验”勾选“编程后执行”下拉框里选择“STM32F10x Low-density/Med-density”或者自动检测FlyMcu会自动识别芯片型号。这个工具已经多年不更新但对付F103系列完全够用。关于“编程后执行”这个选项需要特别解释一下。它表示下载完成后由bootloader跳转到0x08000000处执行用户程序。如果你的APP链接地址就是0x08000000勾选上之后烧完就能直接运行省去手动复位的步骤。但如果你的APP放在偏移地址而0x08000000处没有有效的引导程序那么勾选这个选项没有任何效果甚至可能跑飞。3.3 第三步一键下载流程FlyMcu最方便的一点是支持通过串口的DTR和RTS信号线自动控制复位引脚和BOOT0引脚实现一键下载免去手动跳线的麻烦。前提是你的硬件电路上做了USB转TTL模块的DTR/RTS到NRST/BOOT0的连接很多开发板在设计时已经内置了这个电路。界面上对应的是“DTR的低电平复位RTS高电平进BootLoader”这个复选框如果板子上有一键下载电路就勾上没有就保持不勾选手动操作BOOT0跳线。操作步骤在FlyMcu中点击“...”按钮选择编译生成的HEX文件确认串口、波特率等参数无误如果手动跳线方式先确保BOOT01复位板子点击“开始编程”观察日志输出窗口正常流程会依次显示连接成功、芯片识别、整片擦除、写入Flash、校验成功看到校验成功后把BOOT0跳回0按一下复位键程序开始运行整个过程大概十几秒比接ST-Link还快。实测下来115200波特率最稳定460800虽然快但在某些CH340模块或长线上容易出现偶发校验失败。3.4 备用方案STM32CubeProgrammer下载步骤如果你更喜欢用ST官方工具流程也非常直观打开STM32CubeProgrammer在右上角选择UART模式Port选择对应串口波特率设为115200确保BOOT01并复位板子点击Connect按钮连接成功后在左侧编程区点击Open file选择HEX文件点击Download按钮开始下载下载完成后点击DisconnectBOOT0跳回0复位运行CubeProgrammer的优势在于对ST芯片的支持最权威协议实现完全贴合官方bootloader几乎不会出现兼容性问题。如果你用了FlyMcu怎么都连不上换个CubeProgrammer往往能解决问题。3.5 两种部署方式全量覆盖与分区引导使用官方bootloader下载时HEX文件里其实自带了绝对地址信息。也就是说下载工具会按照HEX里记录的目标地址把数据写到Flash的对应位置。这就衍生出两种不同的部署方式。第一种最简单APP链接地址就是0x08000000下载时直接覆盖整个Flash。这种情况下不需要做中断向量表重映射因为CPU复位后从0x08000000取中断向量本身就是APP自己的向量表完全匹配。对于不少Demo性项目、或者Flash只有64KB的小容量产品这种方式够用。第二种是把Flash分成两个区域0x08000000放一段跳转引导程序偏移地址放真正的APP。两次下载或者生成合并HEX就能让设备启动时先执行引导程序再由引导程序跳到APP。这种部署已经接近真正的IAP架构但牵扯到中断向量表重映射的问题绕不开我单独用一章讲清楚。4. 绕不开的坑中断向量表重映射4.1 APP偏移后为什么必须处理向量表Cortex-M3内核的中断机制决定了任何中断响应时CPU都会从一个固定的地址0x00000000读取向量表。如果APP放在0x08008000这类偏移地址而0x00000000仍然映射到Flash首地址那么中断来了以后CPU去0x08000000查向量表找到的是Flash开头那一小段引导程序的数据根本不是APP的中断服务函数地址自然就进不了中断函数表现就是程序一触发中断就死机、进入HardFault或者干脆复位重启。很多初学者把APP放到偏移地址后发现主循环跑得挺好一按按键或者一收串口数据就死机就是这个原因。4.2 一个关键知识点STM32F103没有VTOR寄存器Cortex-M3内核定义了向量表偏移寄存器VTOR用来重设中断向量表的位置但这是一个可选实现。很遗憾STM32F1系列出于成本考虑没有集成VTOR寄存器所以不能像F4、F3那样直接写一个SCB-VTOR APP_ADDR就完事。网上很多教程拿F4的例子来教F103属于典型的药不对症。F103的正确做法有两种一种是把整个APP的中断向量表复制到SRAM开头然后把SRAM重映射到0x00000000地址另一种是保留Flash开头一段空间通过跳转和查表实现软重定向。实际产品中第一种更简单可靠。4.3 标准的向量表重映射代码实现下面是经过验证的重映射函数在APP的main函数第一行调用#define APP_BASE_ADDR 0x08008000u // 根据实际偏移修改 #define VECTOR_NUM 96u // F103C8T6实际中断向量数量约48个取96足够 static void VectorTableRemap(void) { uint32_t i; uint32_t src APP_BASE_ADDR; uint32_t dst 0x20000000; // 1. 将APP向量表从Flash复制到SRAM起始地址 for (i 0; i VECTOR_NUM; i) { *(__IO uint32_t *)(dst (i 2)) *(__IO uint32_t *)(src (i 2)); } // 2. 设置SYSCFG_CFGR1的MEM_MODE位将SRAM映射到0x00000000 __DMB(); SYSCFG-CFGR1 | SYSCFG_CFGR1_MEM_MODE_0; __DSB(); __ISB(); }使用时要检查一下你的APP工程里栈顶SP初始值也就是0x08008000地址前4字节的内容它会被复制到SRAM的0x20000000处。程序启动时CPU已经通过Flash读取过一次栈指针此时栈区已经初始化完成所以覆盖SRAM开头这384字节96个向量乘以4字节不会影响正在运行的程序只要链接脚本里设置的栈顶地址不在0x20000000到0x20000180之间即可。以F103C8T6的20KB SRAM为例把栈顶设置在0x20005000附近向量表就稳稳地和栈区隔开了。4.4 一个更省事的方案把引导程序也做进Flash向量表重映射确实容易出问题那有没有更省事的方法有一种做法是把跳转引导段做得更聪明一点Flash开头不放简单的跳转指令而是放一个“影子向量表”每个中断向量都对应一个跳转指令跳转到真正的APP中断服务函数。这种方案能避免SRAM的开销但代码复杂度高而且每加一个中断都要同步维护两个表我实际用过一次就不再用了。对于绝大多数产品向量表重映射配合SRAM重映射是最合适的。5. 实战排雷我遇到的7个典型故障5.1 串口连接失败FlyMcu报“无法连接”这是出现频率最高的问题。排查思路按顺序来先确认BOOT0确实拨到1且芯片已复位再确认TXD/RXD接线是否交叉然后用万用表量一下USB转TTL模块的TXD引脚是否有电平跳变最后检查串口号是否被其他软件占用以及波特率是否太高。还有一个容易被忽略的点官方bootloader的USART配置是8位数据、偶校验、1个停止位即8E1而普通串口助手的默认配置往往是8N1。FlyMcu和CubeProgrammer会自动协商这部分一般不用手动管但如果你拿自己的串口调试工具发命令就要注意这个区别。5.2 能识别芯片但擦除或写入时报错这种情况大多是供电问题或线材太长。擦写Flash时芯片电流需求会突然增大如果USB转TTL模块的3.3V输出能力不足电压跌落就会导致写入失败。解决方法是改用独立稳压供电把USB转TTL模块和STM32之间线长控制在15cm以内。另外飞线时杜邦线接触不良也会有同样表现我建议接线后先轻轻晃一晃确认接触牢靠。5.3 烧写成功但按下复位没反应程序没有跑起来。一般原因有三个一是下载的是偏移地址的APP但Flash开头的0x08000000没有引导程序二是Boot0跳线没有拨回0芯片还在系统存储器模式自然跑不了用户程序三是KEIL里没有勾选Create HEX File实际下载的是旧文件。5.4 APP能运行但一进中断就死机这就是上一章讲的问题APP放在偏移地址但没有做中断向量表重映射。解决办法就是调用重映射函数并注意在跳转时进入APP之前关闭全局中断在APP里重新初始化外设。5.5 下载完成后自动执行会死机手动复位就正常FlyMcu的“编程后执行”功能是通过设置PC指针直接跳转到0x08000000实现的。如果你的APP在偏移地址而0x08000000没有有效代码勾选这个选项就会死机。遇到这个情况取消勾选“编程后执行”烧完后手动复位即可。5.6 使用了国产替代芯片下载失败现在很多项目用GD32F103、APM32F103等国产替代芯片。这些芯片的系统存储器bootloader设计大体兼容ST但通信时序和Flash操作细节可能有差异。遇到下载失败优先换STM32CubeProgrammer试试如果仍然失败需要查阅对应芯片手册确认USART1在boot模式下是否有特殊配置。我试过GD32F103C8T6用FlyMcu的STMCU自动检测模式偶尔失败选对型号后正常。5.7 板子被写保护无法擦除之前调试时用ST-Link给芯片设置了读保护或者下载工具设置了选项字节再想用串口下载就会失败。解决办法是用FlyMcu的“解除读保护”功能或使用STM32CubeProgrammer在Option Bytes页面吧Read Out Protection设置为AA并Apply。执行完这个操作后Flash会被整片擦除注意提前备份数据。现象可能原因解决方案无法连接BOOT0未拉高、接线交叉错误、串口被占用检查跳线和接线换串口号写入报错供电不足、线材过长、接触不良独立供电缩短线材重插杜邦线运行无反应Flash开头无引导、BOOT0未复位、HEX未生成检查链接地址BOOT0跳回0勾选HEX中断死机向量表未重映射增加重映射函数自动执行死机勾选了“编程后执行”且Flash开头无有效代码取消勾选手动复位写保护无法擦除选项字节设置了读保护解除读保护注意会格式化Flash6. 进阶思考官方方案之外如何设计真正的IAP升级6.1 官方bootloader方案的边界在哪里官方bootloader方案的局限其实很明显。第一每次升级都需要人手去拨BOOT0跳线或者配合一键下载电路无法做到“设备在应用运行中自行升级”。第二官方bootloader通过串口接收数据时没有加密和校验机制关键产品直接暴露在不安全环境中会引入风险。第三它不具备覆盖保护机制升级断电容易变砖。这些局限决定了它更适合开发调试和小批量维护而不是一个需要远程升级、高可靠性的量产产品方案。6.2 自研bootloader的标准架构如果要做真正意义上的IAP需要自己在Flash中实现一个轻量级引导程序并让APP具备触发跳转的能力。标准分区方式如下0x08000000自研bootloader区存放串口/无线接收、Flash写入、跳转逻辑0x08008000APP区存放应用功能代码0x0800F000参数区存放升级标志、版本号最后一次下载前校验成功后将升级标志写死APP启动后检查是否需要跳转bootloaderAPP接收完新固件后先校验固件完整性CRC或SHA256写入升级标志然后执行系统复位。复位后bootloader检查到升级标志进入固件接收模式接收完成后跳转新APP。这样整个升级触发出自应用本身操作流程变为设备端收到升级指令→APP下载固件→跳转bootloader→bootloader校验后写入Flash→运行新APP。配合双Bank方案还可以实现升级失败自动回滚可靠性远超官方方案。不过如果做远程升级串口方式就不够用了需要给APP加网络模块或者无线透传模块把固件数据通过网口/4G/WiFi接收下来这时候bootloader内部的接收逻辑要改成通过共享内存或串口从APP模块获取数据复杂度又上一个台阶。很多人把这套系统叫OTA升级本质是IAP的传输通道扩展。6.3 一些实际操作体会做自研bootloader时我踩过最深的坑是Flash扇区分配问题。STM32F103C8T6的每一个Flash扇区是1KB擦除必须按扇区来所以APP区起始地址必须对齐扇区边界。如果APP区设为0x08008000这个地址除以0x400刚好是整数对齐没问题如果设为0x08007000对齐就乱了写擦除时很容易误擦掉bootloader代码直接变砖。有几次我把bootloader的启动流程和Flash驱动分开调试刚写完驱动测试时不小心把bootloader区也擦掉了只能上ST-Link救回来。另外一点固件传输协议设计要带帧序号和ACK机制。一开始我把固件分包发送但没做ACK数据稍大就丢包升级成功率惨不忍睹。后来改成每一包都要应答超时重发对端校验通过才发下一包成功率一下就上去了实测一次几万字节的大型固件传输基本是一包都不丢。写在最后的一个小技巧最后分享一个我常用的升级流程闭环。无论是官方bootloader还是自研方案升级完成后一定要做一次版本校验并记录到Flash参数区。对于官方bootloader方案下载工具自带的校验功能能保证写入数据准确性但保证不了写入之后APP的逻辑是否完整所以建议在APP启动时读一次自身Flash区域的版本号和固件CRC值写到日志里。这样现场一旦出问题第一件排查的事就是回看日志里的版本记录能省下大量吹牛归零的时间。我自己在正式产品中都保留了官方bootloader作为“最后一道门”即使自研bootloader写崩了还能通过串口把官方bootloader调出来做强制恢复。这套组合用熟了之后再复杂的固件升级场景心里都有底。
返回列表