尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

STM32 BootLoader+App工程开发详解:Flash分区、跳转逻辑与固件升级

发布时间:2026/9/2 10:38:55

资讯中心
01
ARTICLE

STM32 BootLoader+App工程开发详解:Flash分区、跳转逻辑与固件升级

STM32 BootLoader+App工程开发详解:Flash分区、跳转逻辑与固件升级
简介一套针对AtmelGA 32M1微控制器的BootLoader与App源代码合集面向配药柜显示屏、医疗设备人机交互等嵌入式场景适合想掌握Cortex-M1内核启动流程、驱动开发及固件更新的开发者。压缩包共107个文件大小约382KB包含C语言源文件、H头文件、HEX可执行映像、S90和LST等编译过程文件以及IAR工程配置和调试辅助文件可满足从阅读源码、烧录调试到生成固件的完整需求。其中App源程序带有中文注释覆盖显示、CAN通信、EEPROM存储等模块BootLoader部分则细致展示了时钟初始化、内存映射、应用加载与跳转等关键逻辑帮助理解两层程序之间的协作机制。资源包内文件按功能分类工作区与工程配置齐全便于在IAR环境中直接打开对照。已有232人学习浏览适用于希望借助完整范例加速开发、排查启动异常或学习嵌入式工程结构的人群。 打开这个压缩包里面其实就两样东西——一份App应用固件源码和一份BootLoader引导程序源码。这类工程在单片机项目里太常见了尤其当你需要给已经出货的设备做固件升级时BootLoader就是那条绕不开的路。它的作用说白了就一件事在设备启动时决定是直接跑用户App还是留在BootLoader里等待接收新固件收到新固件后把它写到Flash里再跳转过去执行。整个流程拆开看没有哪个环节是玄学但连在一起踩坑的地方不少。这篇文章我会按“为什么这么设计—核心环节怎么实现—上位机怎么做—踩了哪些坑”的顺序把一个典型的STM32 BootLoaderApp工程完整讲透。项目以STM32F103C8T6为例因为它便宜、量大、资料多适合作为底层模板参考。你不需要完全照搬重点是理解每段代码背后的逻辑以及Flash布局、中断向量表这些“看不见但致命”的细节。适合正在做IAP升级、OTA功能或者刚接手带BootLoader项目的开发者阅读。1. 整体设计与思路拆解1.1 为什么要自己写BootLoader而不是直接烧App很多小产品初期是“裸奔”的程序用J-Link或ST-Link直接烧进Flash出货就完事。但产品一旦铺开问题就来了——客户现场发现了BUG或者要增加新功能总不能派工程师带着烧录器上门拆机刷固件。BootLoader的意义就在于此它让固件升级变成“通过串口/蓝牙/网络下发新固件”整个过程由设备自己完成不需要拆壳不需要调试器甚至用户完全无感。说得更直白一点BootLoader就是设备里的“引导员”。上电后第一个跑的是它它检查一下有没有升级请求如果没有直接跳进App如果有就接收新固件、校验、写入、然后跳转。这个“先检后跳”的机制让设备拥有了自我更新的能力。1.2 BootLoader与App的分区策略AB分区还是单备份设计BootLoader第一件事是分区。常见有两种思路单分区方案BootLoader放在Flash开头App放在后面。升级时直接覆盖当前App区。优点是Flash占用少缺点是升级一旦中途断电App区可能处于“半写半擦”的损坏状态设备变砖。双分区AB分区方案Flash里放两份App区A区和B区。当前运行在A区升级时把新固件写入B区写入成功后标记B区为最新下次启动切到B区如果写入失败继续回滚到A区。这个方案的优点是抗断电、可回滚缺点是Flash容量需求翻倍。热搜词里反复出现“bootloader双分区ab分区”说明大家确实在往这个方向做。我的建议是如果芯片Flash富余尽量上AB分区如果Flash紧张至少要做到“升级时保留一份旧固件备份”或者用外部存储如SPI Flash缓存新固件校验通过后再搬移。STM32F103C8T6只有64KB Flash做AB分区非常勉强通常用单分区外置缓存方案C8T6的“高配版”F103RCT6有256KB就能舒服地安排AB分区了。1.3 上位机与下位机如何配合很多新手只盯着单片机端写BootLoader却忽略了一个问题固件怎么从电脑传到设备这就是上位机Host Tool的活。上位机读入.hex或.bin文件解析成原始二进制数据再按自定义协议分包、加校验、发送。下位机每收一包回一帧应答上位机收到应答再发下一包。整个升级过程就是一个“请求-应答-确认”的闭环。协议设计上要事先约定好我建议至少包含这几类帧握手帧进入BootLoader、擦除帧按扇区擦除、数据帧写Flash、校验帧整体校验、跳转帧通知BootLoader启动App。每类帧要有帧头、长度、命令号、数据域、校验域校验至少用CRC16文件可靠性要求高的话用CRC32。帧格式确定之后上位机和下位机各写各的联调时很少扯皮。2. BootLoader核心细节解析与实操要点2.1 Flash分区与存储布局规划拿STM32F103C8T6来说它的主Flash是64KB地址从0x08000000到0x0800FFFF。一个典型的单分区布局可以这样划分区间起始地址大小用途BootLoader区0x0800000016KB引导程序启动后先跑这里App区0x08004000约48KB用户应用程序标志位区域0x0800FC001KB存放升级标志、版本号、校验结果为什么BootLoader要占16KB这么大因为串口驱动、Flash擦写驱动、协议解析、CRC校验这些功能都塞在这里。8KB也能勉强放下但对编译优化要求高不适合调试期。留16KB换来的是从容和可扩展性后期想加个“固件加密”功能空间也够。App区起始地址0x08004000的选择依据是这个地址必须按扇区对齐方便Flash擦除。F103的扇区比较特殊小容量系列是1KB一扇区中容量是4KB一扇区。0x08004000正好是16KB处也就是第4个扇区的起点。BootLoader只占用前4个扇区每个4KBApp从第5个扇区开始这样擦除App区时不会误伤BootLoader。标志位区域单独划分出来存放“是否有待升级固件”这种轻量级状态避免和App代码混在一个扇区里。因为Flash只能按扇区擦除如果标志位和App代码混在一起每次更新标志位都要把整个App区擦了再写代价太大。单独划分一个区域就干净多了。2.2 跳转逻辑和中断向量表的坑App程序正常运行除了要烧录到0x08004000还必须在启动时告诉CPU“我的中断向量表不在0x08000000而在0x08004000”。这行代码是核心SCB-VTOR APP_START_ADDR; // 设置中断向量表偏移STM32F103不写这句CPU收到串口中断时会去0x08000000处找中断服务函数找到的却是BootLoader里的串口ISR而不是App里的程序就乱套了。跳转动作本身并不复杂typedef void (*pFunction)(void); void jump_to_app(uint32_t app_addr) { uint32_t app_stack *(volatile uint32_t *)app_addr; // 从App首地址读出栈顶指针 pFunction app_entry (pFunction)(*(volatile uint32_t *)(app_addr 4)); // 读出复位向量 __disable_irq(); // 进入App前先关掉全局中断 SysTick-CTRL 0; // 停掉系统滴答定时器 HAL_RCC_DeInit(); // 复位外设时钟 __set_MSP(app_stack); // 切换主栈指针 app_entry(); // 跳转到App的Reset_Handler }这里有个极其容易踩的坑跳转前必须把BootLoader用到的外设全部反初始化。很多人的BootLoader里用了串口接收升级数据跳转前如果不关闭串口App初始化串口时会发现串口状态不对导致第一次收发数据异常。我一般会在跳转前把所有用到的外设都DeInit并且把时钟也复位一遍保证App拿到的是干净的外设状态。另外跳转时选择__set_MSP而不是__set_PSP裸机程序一般跑在主栈上而带RTOS的App在启动阶段也是用主栈直到调度器启动才切到进程栈。所以BootLoader跳转时只需要管MSP就够了。2.3 通讯协议与解析流程一帧一应答BootLoader最常见的工作模式是串口透传我以串口为例说明协议解析。帧格式可以这样定义帧头(2字节: 0xAA 0x55) 命令字(1字节) 数据长度(2字节) 数据域(N字节) CRC32校验(4字节)其中命令字至少要有这些命令命令字数据域内容说明握手0x10版本号上位机发起BootLoader应答擦除0x20起始地址长度按扇区擦除App区写入0x30地址数据分包写入App区校验0x40无对App区做整体CRC校验跳转0x50无启动App解析状态机接收数据时先找帧头再收长度和命令最后收数据域和CRC。收完一帧后先算CRC对不上就丢弃回一个NAK对上了就执行命令回一个ACK。这个“一帧一应答”的机制虽然牺牲了一点速度但极大提高了可靠性。实测115200波特率下升级一个48KB的App全程不到半分钟完全可接受。这里补充一个实用细节BootLoader在等待上位机握手时不要无限期等下去。常见的做法是“延时超时”上电后等待1秒如果收到握手帧就留在BootLoader里如果没收到直接跳转App。这样既能随时进入升级模式又不影响正常开机速度。类似“进入BootLoader模式”还可以用按键、特定串口字符等方式触发根据产品自己定。3. App工程配置与上位机实现3.1 App工程改三处起始地址、向量表偏移、编译输出App侧的改动其实比BootLoader小但每一处都关键。以STM32CubeIDE为例第一处链接脚本.ld文件默认脚本里Flash起始是0x08000000长度是64KB要改成FLASH (rx) : ORIGIN 0x08004000, LENGTH 48K改完这个编译出的App的可执行代码地址全部基于0x08004000生成。这里特别提醒如果你用的是MDK Keil对应的是Target选项卡里的IROM1起始地址和大小IAR则对上的是链接配置里的ROM区。不同IDE改的地方不一样但本质都是改“ROM起始地址”。第二处中断向量表偏移在main函数最开头、任何外设初始化之前加上SCB-VTOR 0x08004000;有些库函数也提供HAL_SetVectorTable之类的接口但底层逻辑都是设置VTOR寄存器。注意要在SystemInit()之后、外设初始化之前设置否则中断随时可能从错误的位置取向量而出错。第三处编译输出BootLoader下载的是固件文件Keil默认输出.hex也可以用fromelf命令转出.binfromelf --bin --outputApp.bin Build/App.axfCubeIDE更方便在工程属性里勾选“Convert to binary file”编译后自动生成.bin文件。上位机发送时建议用.bin而不是.hex因为.bin是纯二进制数据解析简单不需要处理hex的地址段和EOF记录。3.2 上位机工具选型LabVIEW、C#、Python各有取舍热搜词里出现了“labview实现bootloader上位机”和“比较好的外围app”说明上位机确实是这个方案的标配需求。选型上我给出一个实际的参考方案优点缺点适用场景C# WinForms/WPF开发快、串口API成熟、UI友好仅限Windows内部工具、产测工具Python pyserial跨平台、脚本化、易维护打包分发麻烦快速原型、自动化测试Qt C跨平台、界面漂亮、性能好开发成本最高商用上位机LabVIEW图形化、军工业/测控领域常用部署依赖运行时代码版本管理差仪器控制、产线测试个人最推荐C#SerialPort类直接可用加上后台线程做数据接收一个和BootLoader对话的上位机几百行代码就搞定。界面里只需要文件选择框选bin文件、串口设置区端口/波特率、开始升级按钮、进度条、日志框。如果图省事用Python写个命令行版也很快import serial def send_frame(ser, cmd, datab): frame b\xAA\x55 bytes([cmd]) len(data).to_bytes(2, little) data crc zlib.crc32(frame) 0xFFFFFFFF frame crc.to_bytes(4, little) ser.write(frame) def main(): ser serial.Serial(COM3, 115200, timeout1) firmware open(App.bin, rb).read() send_frame(ser, 0x10) # 握手 resp ser.read(7) if resp[2] ! 0x10: print(握手失败) return chunks [firmware[i:i2048] for i in range(0, len(firmware), 2048)] for i, chunk in enumerate(chunks): send_frame(ser, 0x30, len(chunk).to_bytes(4, little) chunk) ack ser.read(7) if ack[2] ! 0x06: print(f第{i}包写入失败) return print(f进度: {i1}/{len(chunks)}) send_frame(ser, 0x40) # 校验 print(升级完成)这段代码把一帧的组帧、CRC计算、串口收发都演示了。实际使用中要给每一帧加超时重发机制避免串口丢包后死等。3.3 现场联调的正确步骤拿到新的BootLoader和App工程不要急着直接烧进去。我的习惯是“三步走”第一步把App直接烧到0x08004000不经过BootLoader单独验证App在改完链接脚本后能正常运行。这一步排除了App侧的问题。第二步烧入BootLoader到0x08000000先测试“无操作自动跳转”看BootLoader能否正常让App跑起来。这一步验证了跳转代码和向量表偏移。第三步用上位机通过串口完成一次真正的固件升级。先升一个已知能用的固件再升一个新版本验证协议和Flash写入都正常。三步都过了这套方案才算真正可用。4. 常见问题与排查技巧实录4.1 App跳转过去就死机问题大概率出在哪这是“BootLoader开发”热搜词下最典型的问题。跳转后死机、跑飞、无反应常见的六个原因按概率排序如下现象原因排查方式跳转后直接进HardFaultApp的启动文件里栈指针被覆盖或App地址不对检查链接脚本、确认App首地址确实是栈顶跳转后中断不响应向量表偏移未设置或设置了但被后来的代码覆盖在main最前面设SCB-VTOR别放在初始化之后串口数据异常BootLoader的串口外设没有DeInit跳转前把USART恢复默认时钟也一并复位定时器乱跑SysTick没有停掉跳转前设置SysTick-CTRL 0随机死机但复现困难App编译时优化等级或链接脚本长度设置不对对比App烧录到0x08004000的“直接烧录”行为跳转后能跑但一进中断就挂VTOR寄存器在该芯片上不支持某些地址偏移确认偏移地址是否对齐到中断向量表大小F103一般为0x40的倍数大部分时候死机原因是前三项的组合。尤其是向量表偏移几乎每次都会有人栽在这。代码里写了SCB-VTOR 0x08004000;不假但有人放在了HAL_Init()之后初始化过程中已经有中断发生了于是直接按旧向量表执行虽然最后VTOR设置成功最早那一下已经错了。4.2 升级中途断电设备还能救回来吗这正是BootLoader比在线调试强的地方。单分区方案下如果在擦除App扇区之后、写入完成之前断电App区确实处于“不完整”状态但BootLoader还活着。重新上电后BootLoader检测到App区校验不通过会拒绝跳转停留在接收模式等待上位机重新下发固件。所以“断电变砖”在这里是伪命题——BootLoader本身就是最后一道防线。但这要求BootLoader足够健壮它不能被App的错误拖垮并且它自己的Flash区域不能被擦除过程波及。所以我在擦除App前会专门做一次“地址越界检查”如果待擦除地址落在BootLoader区间直接报错终止。防的就是上位机传参异常时把BootLoader自己擦掉那种情况才是真砖。4.3 上位机收不到应答或传输超时怎么定位这类问题在网络热词“app抓包失败”相关讨论里也很常见本质都是通讯链路出了问题排查思路是分层的物理层确认串口线连接、波特率、地线共地。用串口助手先手动发一个0xAA 0x55看BootLoader有没有回包。没回包说明BootLoader没进串口接收逻辑回包乱码说明波特率或接线有问题。协议层用逻辑分析仪或串口抓包工具看PC发出去的帧数据。确认数据有没有被BootLoader完整收到。这部分我建议在BootLoader里加一个调试打印功能把收到的帧头、长度、CRC结果打出来联调阶段非常管用。稳定性如果“偶尔成功、偶尔超时”多半是流控问题。确认上位机没有开RTS/CTS硬件流控串口线是不是劣质延长线。115200波特率下线长超过2米就建议降波特率到57600或38400稳定优先。4.4 实测中补充的坑与细节再补充几个不上手真的想不到的细节第一个坑是C库函数在BootLoader里的时钟依赖。BootLoader里如果有sprintf这类格式化输出函数要注意F103默认是8MHz内部RC还是外部晶振。串口输出的内容错乱往往不是波特率设置错而是系统时钟和波特率寄存器配置不匹配。第二个坑关于Flash写入函数。STM32的Flash编程必须按“字”32位或者“半字”操作。你通过串口收到的数据是字节流写入前要拼成32位再写。用HAL库的HAL_FLASH_Program(TYPEPROGRAM_WORD, addr, data)地址必须4字节对齐否则返回参数错误。第三个坑是CRC算法不一致。上位机用的是CRC32BootLoader端也用了CRC32但init值、多项式、输出异或值、字节序只要有一个不一样两边算出来的校验结果就永远对不上。建议两边都用同一套标准CRC32如zlib的crc32并且把关键参数写死在协议文档里别写成“CRC32”三个字就完事。第四个技巧App区整体校验前先清除几个字节比如App头部4个字节保证旧固件和新固件之间不会残留脏数据。因为Flash写入只能把1写成0如果新固件某个字节是1而旧固件对应位置是0不擦除的话写入是无效的。所以在写入新固件前必须对整个App区做擦除操作。这个逻辑很多人知道但实战中确实有人因为“只擦了部分扇区没擦全部”导致升级后功能时好时坏。5. 一些额外的体会与扩展方向在实现“appBootLoader程序”这套工程的过程中我的体会是BootLoader代码量不大但它是整个产品稳定性的“地基”。App出了问题用户最多是功能不可用BootLoader设计不当设备可能连复位的自救能力都没有。所以在BootLoader里我宁愿多写几行防御性代码比如地址越界检查、Flash写入状态轮询、超时重传也不愿省那点代码空间。这套方案后续可以做很多扩展。比如把串口替换成蓝牙模块就能用手机App给设备升级对应热词里的“蓝牙app控制esp32”加入加密算法就能实现固件防抄板加上AB分区和版本号回滚机制就具备商用OTA的雏形。BTW如果你用的是STM32F103C8T6这种64KB Flash的芯片升级App时控制好固件体积很关键——一个带RTOS和FatFS的App随随便便就超过48KB留给BootLoader的往往是“压缩再压缩”的窘境。所以我给这款芯片做BootLoader时串口驱动和协议层都做了裁剪尽量控制在8KB左右给App多留空间。最后再分享一个小技巧在BootLoader里加一个“版本号”命令App可以用它读取BootLoader版本上位机也可以用它判断目标设备的BootLoader是否支持某些新协议。这个小功能成本极低但在维护多批次产品时能帮你省掉大量“为什么这台设备升级方式和其他批次不一样”的排查时间。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。