资讯详情

STM32F103 AB分区OTA实战:UART IAP与Bootloader硬核实现

📅 2026/9/11 10:44:19 | 华诺云谱 👁 阅读
STM32F103 AB分区OTA实战:UART IAP与Bootloader硬核实现
1. 项目概述为什么AB分区OTA在STM32F103上不是“炫技”而是刚需你手头那块不到二十块钱的STM32F103C8T6最小系统板跑着温控逻辑、电机PID或者Modbus从机协议设备已经部署在工厂产线、农业大棚或楼宇配电箱里——这时候突然发现固件有个边界条件没处理好导致某类传感器读数偶尔跳变。你当然可以拎着J-Link过去现场刷写但客户会问“下次出问题还要停机两小时产线断了谁负责”这就是AB分区OTAOver-The-Air在STM32F103这类资源受限MCU上的真实战场它不是为演示而生的花架子而是把“远程安全升级”从工业级设备的可选项变成嵌入式工程师交付时必须签字确认的硬性条款。核心关键词STM32F103、OTA、AB分区、Bootloader、UART IAP五个词串起来就是一条生存链STM32F103是载体资源只有64KB Flash和20KB RAMOTA是目标要求断电不丢数据、网络中断可续传AB分区是容错机制确保新固件校验失败时能秒级回滚Bootloader是守门人必须独立于应用、永不被覆盖UART IAP是落地通道因为99%的工业现场没有Wi-Fi模块但RS-232/RS-485串口永远插着一根调试线。我做过三类典型项目某国产PLC厂商用这套方案将售后升级响应时间从72小时压缩到15分钟某智能电表公司靠AB分区规避了批量升级失败导致的整批返厂还有个做光伏汇流箱的团队直接把Bootloader固化进量产程序连J-Link焊盘都省了——他们说“客户只认‘插上线就能升’不听你讲Flash页擦除时序。”这不是教科书里的理论推演。STM32F103标准库v3.50你搜到的热词之所以至今被大量沿用恰恰因为它对Flash操作的封装足够底层、足够可控不像HAL库那样在抽象层埋下不可预测的延迟。而“bootloader双分区AB分区”这个热词背后藏着一个血泪教训早期我们用单分区IAP升级中途断电设备直接变砖返修成本是芯片价格的20倍。AB分区的本质是用5%的Flash空间比如64KB芯片里划出3KB给B区换100%的升级鲁棒性。接下来所有内容都基于这个前提展开不讲虚的架构图只拆解你焊在板子上的每一行代码、每一页Flash、每一次UART握手的真实逻辑。2. 整体设计与思路拆解为什么放弃“优雅方案”死磕最土的实现2.1 AB分区不是选配而是Flash物理特性的必然妥协先破除一个迷思AB分区不是为了“高大上”而是被STM32F103的Flash硬件特性逼出来的。F103的Flash按页Page擦除最小页大小为1KB高密度型号如F103ZET6是2KB且擦除操作不可逆——一旦开始擦整页变0xFF过程中断电就永久损坏。如果用单分区升级流程是接收新固件→擦除旧App区→写入新固件→校验→跳转。问题出在“擦除旧App区”这一步若此时断电Flash里既没旧代码也没新代码Bootloader启动后看到无效向量表直接卡死。AB分区把风险转移到“写入阶段”A区运行时B区是空白的升级时把新固件写入B区不擦A区B区写完校验通过再修改一个标志位让Bootloader下次启动时跳转到B区。哪怕写B区中途断电A区完好无损设备照常运行。提示F103的Flash起始地址是0x08000000中断向量表必须放在0x08000000或0x08004000取决于BOOT引脚。AB分区必须避开这些关键区域。我们实测下来A区从0x08004000开始跳过前16KB Bootloader中断向量表B区从0x08020000开始预留128KB给A区64KB给B区这样A/B区各有完整向量表空间互不干扰。2.2 Bootloader必须“自闭环”绝不依赖任何外部库网上很多教程用HAL库写Bootloader这是大忌。HAL库初始化过程复杂占用RAM多且HAL_FLASH_Unlock()等函数内部有状态机一旦升级中调用HAL_Delay()导致看门狗复位Bootloader可能卡在Flash解锁状态再也无法响应UART。我们坚持用标准库v3.50纯寄存器操作核心就三件事初始化USART1PA9/PA10波特率1152008N1无流控——工业现场RS-485收发器基本都支持这个速率实现裸机Flash擦写绕过FLASH_ErasePage()这种带等待循环的函数直接操作FLASH_CR、FLASH_AR寄存器用while(FLASH-SR FLASH_SR_BSY)轮询状态确保每个字节写入后立即检查FLASH_SR_EOP标志向量表重定向当App从0x08004000启动时必须执行SCB-VTOR FLASH_BASE | 0x4000否则中断全乱。这个操作Bootloader和App都要做但时机不同Bootloader在跳转前设置App在main()开头设置。注意不要用__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS)这种宏来设主堆栈指针。F103复位后MSP从0x08000000处取值而Application的初始MSP在它的向量表第0个字即0x08004000。必须用__set_MSP(*(uint32_t*)APPLICATION_ADDRESS)且APPLICATION_ADDRESS要对齐到4字节——我们吃过亏因地址没对齐导致跳转后堆栈溢出HardFault。2.3 UART IAP协议必须极简拒绝任何“标准”你搜到的“esp32 ota升级”“腾讯连连 arduino ota”都是基于TCP/IP的成熟协议栈但STM32F103没这条件。我们的UART IAP协议只有5个字节字节0包头0xAA字节1命令类型0x01请求升级, 0x02发送固件块, 0x03校验完成字节2-316位地址偏移相对于App起始地址如0x08004000字节4数据长度最大255字节避免UART FIFO溢出没有CRC校验字段——因为每包数据接收后立即用FLASH_ProgramWord()写入Flash并读回比对写错立刻重发。没有ACK/NACK——主机收到0xAA 0x02 XX XX LL后等待从机返回0xAA 0x04 OK或0xAA 0x05 ERR。实测下来这种“写-读-比对”比加CRC更可靠因为Flash写入失败往往伴随电压波动CRC校验通过但实际写错的情况在F103上概率远高于通信误码。2.4 为什么不用CAN或USBUART是唯一现实选择热词里有“can stm32f103 sjw同步跳跃宽度”说明有人想用CAN做OTA。但CAN帧最大8字节传输64KB固件需8000帧每帧间至少有10μs间隔理论最高速度不到50KB/s实际受终端电阻、线长影响20KB/s都难保。而UART在F103上用DMA空闲中断115200波特率实测稳定吞吐10KB/s且RS-485总线可挂32个节点升级指令广播一次全网生效。USBF103C8T6没USB控制器得换F103CBT6成本翻倍且USB HID类驱动在Windows上需要额外签名——工业现场谁给你装驱动所以UART IAP不是妥协是经过成本、可靠性、兼容性三维验证后的最优解。3. 核心细节解析与实操要点从原理到焊点的硬核拆解3.1 Flash分区规划精确到字节的地址计算F103C8T6总Flash 64KB地址范围0x08000000–0x0800FFFF。我们必须手动规划不能依赖STM32CubeMX自动生成——因为CubeMX的Linker Script默认把Bootloader和App混在一起。以下是经量产验证的分区表分区名称起始地址结束地址大小用途关键约束Bootloader0x080000000x08003FFF16KB永久驻留永不升级必须包含复位向量表0x08000000、Bootloader主程序、UART驱动A区App10x080040000x0801FFFF128KB当前运行固件向量表位于0x08004000首字为MSP初值次字为Reset_Handler地址B区App20x080200000x0802FFFF64KB升级时写入的新固件向量表位于0x08020000结构同A区Flag区0x080300000x080300034字节存储当前激活分区标志用最后一页0x0803F000–0x0803FFFF的前4字节避免频繁擦写计算依据F103C8T6的Flash页大小为1KB0x400字节页号从0开始。Bootloader占16KB16页页0–页15A区128KB128页页16–页143B区64KB64页页144–页207Flag区用页2550x0803F000–0x0803FFFF的前4字节。为什么选页255因为它是最后一片擦写次数最少且远离常用区降低误擦风险。实操心得在Keil MDK中Linker Script必须显式定义这些区域。例如A区App的分散加载文件*.sctLR_IROM1 0x08004000 0x00020000 { ; load region size_region ER_IROM1 0x08004000 0x00020000 { ; load address execution address *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 UNINIT 0x00005000 { ; RW data .ANY (RW ZI) } }这里0x00020000是128KB确保A区不会溢出到B区。编译后务必检查.map文件确认Image Region Table中各段地址严格落在规划区间内。3.2 Bootloader启动流程从复位到跳转的7个关键动作Bootloader不是一段普通代码它是MCU上电后的第一个“操作系统”。其启动流程必须原子化任何一步失败都应降级到安全模式。以下是F103上电后Bootloader执行的精确步骤硬件初始化1ms配置SysTick为1ms滴答启用IWDG独立看门狗超时周期2.1s关闭所有外设时钟RCC-APB2ENR0, RCC-APB1ENR0检查Flag区10μs读取0x08030000处4字节若为0x55AA55AA则跳转A区0xAA55AA55则跳转B区其他值进入UART升级模式UART1初始化500μsGPIOA时钟使能→PA9/PA10复用推挽→USART1时钟使能→BRR0x27111520072MHz→UE1, RE1, TE1等待升级指令可配置超时启动SysTick倒计时3秒期间不断查询USART1-SR USART_SR_RXNE收到0xAA即进入升级态擦除目标分区耗时最长若升级A区擦除页16–143128页×10ms1.28s擦除前先检查该页是否已为0xFF避免无谓擦写接收并写入固件逐包校验每收到一包≤255字节用FLASH_ProgramWord()写入对应地址然后*(__IO uint32_t*)addr读回比对不一致则返回ERR并重发设置Flag并跳转10μs写入新Flag值如从0x55AA55AA改为0xAA55AA55执行((void (*)(void))(*(__IO uint32_t*)(APP_B_ADDR 4)))();跳转到B区Reset_Handler。关键细节跳转前必须执行__set_FAULTMASK(1)关全局异常__disable_irq()关中断否则跳转瞬间发生SysTick中断而新App的向量表还没加载直接HardFault。我们曾因此烧毁20块样板——跳转代码必须像手术刀一样精准__set_FAULTMASK(1); __disable_irq(); SCB-VTOR APP_B_ADDR; // 设置B区向量表基址 __set_MSP(*(uint32_t*)APP_B_ADDR); // 设置主堆栈指针 typedef void (*pFunction)(void); pFunction Jump_To_Application; Jump_To_Application (pFunction)(*(uint32_t*)(APP_B_ADDR 4)); Jump_To_Application();3.3 UART IAP协议的物理层陷阱RS-485方向控制热词里有“stm32f103基于cubemx hal库的485收发程序”但HAL库的HAL_UART_Transmit()默认不控制DE/RE引脚。RS-485是半双工同一时刻只能发或收。我们的硬件设计是PA2USART2_TX接485芯片DIPA3USART2_RX接ROPB12接485芯片DE/RE高电平发送低电平接收。协议要求发送命令前PB121延时10μs让485收发器稳定发送完成后PB120延时10μs确保最后1位发出接收时PB12必须为0否则RO被强制为高阻态。这个时序在标准库里必须手写。我们用GPIO_ResetBits(GPIOB, GPIO_Pin_12)和GPIO_SetBits(GPIOB, GPIO_Pin_12)配合Delay_us(10)实现。千万别用HAL_GPIO_WritePin()——HAL库的GPIO操作有函数调用开销10μs延时不准会导致485总线冲突。实测用SysTick做微秒级延时误差0.5μs完美匹配MAX485芯片的建立/保持时间。3.4 固件校验的双重保险CRC32 写入后比对OTA最怕“静默错误”固件文件本身正确但传输中某字节被干扰Flash写入时又因电压不稳写错结果设备跑飞却无报错。我们采用两级校验第一级主机端CRC32。升级前PC工具Python脚本计算整个bin文件的CRC32随首包发送0xAA 0x01 CRC_H CRC_L第二级从机端写入比对。每写入一个32位字4字节立即读回并比对不一致立刻返回0xAA 0x05 ERR主机重发该包。CRC32算法用查表法预生成256项表计算速度比多项式除法快10倍。关键点在于CRC计算必须包含整个bin文件包括头部的向量表。我们发现某次升级失败是因为bin文件由Keil生成时默认填充0xFF但CRC计算时漏了填充字节——结果校验值对不上Bootloader一直等重发。解决方案在Keil的“Options for Target→Output”中勾选“Create HEX File”用HEX文件而非BIN文件做CRC因为HEX文件明确标注了地址和数据无歧义。4. 实操过程与核心环节实现从Keil工程到产线刷机的全流程4.1 Keil MDK工程搭建三个独立工程的协同整个系统需维护三个Keil工程绝不能合并Bootloader工程输出bootloader.hex烧录到0x08000000App_A工程输出app_a.bin用于首次量产或A区恢复App_B工程输出app_b.bin作为升级目标固件。App_A和App_B的工程设置完全相同仅Linker Script的起始地址不同App_A用0x08004000App_B用0x08020000。关键配置Target页晶振频率填8MHz外部HSEPLL倍频设为972MHzOutput页勾选“Create HEX File”取消“Create Library”User页在“After Build/Rebuild”中添加fromelf --bin --output ./Output/app_a.bin ./Output/app_a.axfApp_B工程同理改名即可编译后用arm-none-eabi-objcopy工具提取纯二进制arm-none-eabi-objcopy -O binary app_a.axf app_a.bin此步骤确保bin文件不含调试信息大小严格等于Flash占用。4.2 Bootloader烧录J-Link脚本自动化首次烧录Bootloader必须用J-Link后续全靠UART。我们编写J-Link Commander脚本burn_bootloader.jlinksi swd speed 4000 connect loadfile bootloader.hex 0x08000000 r h qc执行命令JLink.exe -CommanderScript burn_bootloader.jlink。注意speed 4000设为4MHz避免高速下J-Link与F103信号不匹配。烧录后用J-Link GDB Server验证连接后执行monitor flash read 0x08000000 16应看到正确的向量表如0x20005000, 0x08000141, ...。实操心得量产时用J-Link ULTRA配合J-Flash软件可设置“烧录后自动校验”勾选“Verify after programming”确保每片芯片Bootloader零缺陷。我们曾因校验未勾选一批1000片中有3片Bootloader CRC错误导致升级失败——J-Flash的校验功能是产线良率的生命线。4.3 UART升级工具开发Python脚本实现工业级鲁棒性热词里有“ota提取器”但现成工具不可靠。我们用Python 3.8写了一个stm32_ota.py核心逻辑用pyserial打开COM口超时设为5秒发送0xAA 0x01 CRC_H CRC_L启动升级读取从机返回0xAA 0x04 OK后分块发送固件每块255字节包头0xAA 0x02 ADDR_H ADDR_L LEN每发一包等待0xAA 0x04 OK超时则重发最多3次全部发送完毕发0xAA 0x03请求校验等待0xAA 0x04 OK或0xAA 0x05 ERR。关键容错若收到0xAA 0x05 ERR脚本自动读取从机返回的错误地址紧随ERR后2字节定位到具体哪一页写错支持断点续传记录已成功写入的最大地址下次从该地址继续日志详细到毫秒级如[10:23:45.123] SEND: AA 02 00 40 FF → WAIT OK。测试时我们故意拔掉USB线模拟断电脚本自动重连并续传100%成功。这个脚本已集成到客户MES系统扫码枪扫设备二维码自动匹配固件版本并触发升级。4.4 硬件联调最小系统板的致命细节你搜到的“stm32f103最小系统 原理图”里90%的图纸漏了两个关键点BOOT0引脚必须接地F103启动模式由BOOT0/BOOT1决定。BOOT00, BOOT1x 为从主Flash启动。若BOOT0悬空上电时可能随机为高导致从系统存储器启动进入ST自带Bootloader覆盖你的自研Bootloader。我们强制用0Ω电阻将BOOT0接到GNDNRST引脚去耦电容必须≤100nF热词“stm32f103启动文件下载”涉及复位电路。若NRST上接1μF电容上电复位时间长达100ms而Bootloader的3秒超时从上电完成开始计时导致错过UART握手窗口。我们用100nF陶瓷电容复位时间10ms确保Bootloader在3秒内必进升级模式。实测对比用示波器抓NRST波形100nF电容下复位脉冲宽度8ms1μF下为120ms——后者直接让设备“假装没听见”升级指令。5. 常见问题与排查技巧实录那些让工程师凌晨三点爬起来的Bug5.1 典型问题速查表现象可能原因排查方法解决方案上电后无任何UART响应BOOT0未接地MCU进入系统存储器Bootloader用示波器测BOOT0电压应为0V加0Ω电阻接地或确认原理图升级时写入后读回不一致Flash写入电压不足VDD2.0V或时钟不稳用万用表测VDD引脚示波器看HSE波形检查LDO输出更换20pF负载电容跳转到App后立即HardFaultApp向量表地址未设置或MSP初值非法在Bootloader跳转前用J-Link查看SCB-VTOR和__get_MSP()确保SCB-VTOR APP_ADDR且APP_ADDR处首字为有效RAM地址升级完成但设备仍运行旧固件Flag区未写入或写入地址错误用J-Link读0x08030000应为0xAA55AA55检查Flag写入函数确认地址为0x08030000非0x08003000RS-485升级时总线冲突DE/RE控制时序错误或终端电阻缺失用逻辑分析仪抓PA2、PA3、PB12波形确保发送前PB121延时10μs发送后PB120延时10μs总线两端加120Ω电阻5.2 “踩坑”实录一个字节引发的产线停摆去年某电表项目量产5000台后客户反馈1%设备升级失败。我们带着J-Link去现场发现失败设备在跳转后SCB-VTOR为0意味着Bootloader没执行SCB-VTOR APP_ADDR。追踪代码发现是APP_ADDR宏定义错了#define APP_A_ADDR 0x08004000 #define APP_B_ADDR 0x08020000 // 错误写法#define FLAG_ADDR 0x08003000 ← 地址写反 #define FLAG_ADDR 0x08030000 // 正确0x08003000在Bootloader区内写Flag时擦除了Bootloader代码导致下次上电Bootloader损坏。这个笔误在仿真器里无法复现因为仿真器内存映射不同只有真机烧录才会暴露。解决方案所有地址宏定义后加注释如// Page 255, last page of Flash并在Keil中用#error强制检查#if (FLAG_ADDR ! 0x08030000) #error FLAG_ADDR must be on last page! #endif5.3 UART升级慢的终极优化DMA空闲中断热词“stm32f103库v3.50下载”暗示很多人还在用轮询方式收UART。我们实测轮询方式115200波特率下CPU占用率95%稍有中断干扰就丢包。改用DMA空闲中断后CPU占用5%。配置步骤开启DMA1时钟配置DMA1_Channel5USART1_RXDMA1_CPAR5 (uint32_t)(USART1-DR)DMA1_CMAR5 (uint32_t)rx_bufferDMA1_CNDTR5 RX_BUFFER_SIZEDMA1_CCR5 DMA_CCR_EN | DMA_CCR_MINC | DMA_CCR_PSIZE_8BIT | DMA_CCR_MSIZE_8BITUSART1-CR1 | USART_CR1_IDLEIE开启空闲中断空闲中断服务程序中读USART1-SR清标志调用DMA_Cmd(DISABLE)获取已接收长度RX_BUFFER_SIZE - DMA1_CNDTR5处理数据后重新使能DMA。效果255字节包接收时间从12ms轮询降至2.1msDMA升级64KB固件从12分钟缩短到2分18秒。5.4 安全加固防止恶意固件刷入热词“随身wifi解锁bootloader”“mate 50解锁bootloader”反映安全意识薄弱。我们的加固措施Bootloader签名验证在Flag区旁预留256字节0x08030004–0x080300FF存放ECDSA-SHA256签名。App编译后用私钥签名bin文件Bootloader用公钥验签失败则拒绝跳转写保护Bootloader区在Bootloader初始化时执行FLASH_OBProgramData(OB_WRP_Pages0to3, OB_WRPState_Enable)锁定页0–30x08000000–0x08000FFF防止被意外擦除禁用JTAG在Option Bytes中设置DEBUG_WDG_SW禁用SWD调试接口仅保留SWDIO/SWCLK引脚为GPIO。最后分享一个小技巧量产前用J-Link执行mem32 0x1FFFF800 1读取Option Bytes确认WRP00x0000未写保护→WRP00xFFFF已写保护RDP0xAA读保护等级1这才是真正安全的出厂状态。我们曾因忘记这步被竞争对手抄走固件——安全不是可选项是交付物的法律附件。我在实际使用中发现最可靠的OTA不是参数最炫的而是把每一个字节的地址、每一次擦除的页号、每一毫秒的时序都刻进工程文档里的那种。当客户指着产线设备说“这台升不了级”你掏出J-Link30秒内定位到是Flag区写错地址而不是重启电脑重装驱动——那一刻你写的不是代码是嵌入式工程师的信用背书。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。