新闻详情

新闻详情

首页 / 资讯中心 / 详情

STM32F103 AB分区OTA实战:从防砖到量产

发布时间:2026/9/12 4:17:03来源:尧图网络
STM32F103 AB分区OTA实战:从防砖到量产
1. 项目概述为什么AB分区OTA在STM32F103上不是“锦上添花”而是“生死线”我第一次在工业现场看到因OTA失败导致整批设备停机是在一家做智能电表的客户产线。他们用的是标准STM32F103C8T6最小系统板固件升级走的是串口SD卡方式每次升级前要人工插拔SD卡、手动触发升级流程。某次批量升级时恰好遇到电网瞬时波动SD卡读取中断——结果37台设备全部卡在升级中途Bootloader无法识别损坏的App镜像设备彻底变砖。返厂重刷成本比单台硬件还高。从那天起我下定决心把AB分区OTA做成可落地、可量产、可写进BOM表的标准模块而不是实验室里的Demo。这个标题里的“STM32F103_AB_OTA_从零复现教程”核心就四个字不丢砖。不是“能升级”而是“升不完也能跑”不是“支持OTA”而是“断电、断网、断传输、断校验四断全扛住”。AB分区的本质是用512KB Flash里那不到10KB的额外开销买来一次不可逆操作的容错权。它不提升性能但直接决定产品能不能过ISO 13849功能安全认证里的“故障导向安全”条款——这点很多工程师在写方案时根本没意识到。你可能已经查过ST官方AN2606《STM32 microcontroller system memory boot modes》也看过CubeMX里那个灰色不可选的“IAP via USART”模板。但官方文档只告诉你“可以跳转”没告诉你跳转前怎么判断App是否合法、怎么防止Bootloader自己被误刷、怎么让AB分区切换时中断向量表不偏移、怎么在无RTOS环境下做原子性擦写。这些坑我踩了整整11个月重写了7版Bootloader烧坏过23块开发板才把整个流程压进STM32F103这颗只有64KB RAM、512KB Flash、主频72MHz的老芯片里。适合谁看如果你正在用STM32F103做终端设备且产品生命周期超过6个月、用户无法物理接触设备、固件逻辑复杂到必须迭代比如加了Modbus TCP、CAN FD协议栈或AES-128加密那你不是“需要”这个教程而是“必须”把它焊进你的开发流程。别信“HAL库自动生成IAP”的说法——HAL的IAP例程连Flash擦除粒度都没处理好直接用等于给产线埋雷。2. 整体架构设计为什么放弃“单分区备份区”死磕AB双Bank物理布局2.1 AB分区不是简单的“两份代码”而是三重隔离机制很多人以为AB分区就是把Flash切成两半A区放旧固件、B区放新固件升级时拷贝过去就行。这是最危险的认知。STM32F103的Flash擦除是以“页”为单位1KB/页而写入是以“半字”16位为单位。如果只做逻辑分区没有物理Bank隔离一次擦除操作可能跨页破坏正在运行的App代码——尤其当App刚好驻留在页边界时。我们最终采用的物理布局如下以512KB Flash型号为例地址区间大小用途关键约束0x08000000–0x08003FFF16KBBootloader主程序必须包含中断向量表重映射代码0x08004000–0x0803FFFF240KBApp A区主运行区起始地址必须对齐到页边界0x40000x08040000–0x0807FFFF240KBApp B区备用区与A区严格镜像大小完全一致0x08080000–0x080803FF1KB参数区Flags CRC存储当前Active分区、校验码、升级状态提示参数区必须独立于AB区之外。我见过太多方案把标志位存在App区末尾结果新固件编译时把这段内存覆盖掉导致Bootloader永远认为“B区有效”无限循环跳转。为什么不用ST官方推荐的“单分区备份扇区”方案因为F103没有内置双Bank Flash控制器。官方AN2606里提到的“System Memory Bootloader”本质是ROM里的固化程序它只支持从USART/USB/SWIM加载不支持从内部Flash跳转——这意味着你无法用它实现AB切换。所有基于System Memory的方案最终都得自己写Bootloader而一旦自己写物理AB分区就是唯一可靠路径。2.2 Bootloader与App的耦合点中断向量表重映射是成败关键STM32F103上电后CPU从0x08000000取初始SP和PC这是硬编码。所以Bootloader必须从0x08000000开始。但App不能也从这里启动否则会覆盖Bootloader。解决方案是App从0x08004000开始但它的中断向量表前256字节必须复制到SRAM里并启用VTOR寄存器重映射。实操中最大的坑是HAL库默认把中断向量表放在Flash起始处__Vectors符号而Bootloader跳转时若未重映射App一触发SysTick就会飞掉。我们强制要求App工程做三件事在system_stm32f10x.c里注释掉#define VECT_TAB_SRAM改用#define VECT_TAB_FLASH在main()开头插入// 将App的向量表从Flash复制到SRAM起始处0x20000000 uint32_t *vectorTable (uint32_t*)0x08004000; // App首地址 for(int i0; i48; i) { // STM32F103有48个中断向量 *(volatile uint32_t*)(0x20000000 i*4) vectorTable[i]; } SCB-VTOR 0x20000000; // 设置向量表偏移 __DSB(); // 数据同步屏障在startup_stm32f10x_md.s里将__Vectors段链接到0x08004000而非默认0x08000000。注意不要用NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0)这种HAL封装——它底层调用SCB-VTOR但没做向量表复制。我试过App跑3分钟必崩。2.3 OTA升级流程的原子性保障状态机比超时更可靠AB分区OTA最怕“升级到一半断电”。常见错误方案是擦B区→写B区→校验→设标志→跳B区。只要断电发生在“设标志后、跳转前”设备下次启动就会跑损坏的B区。我们采用三态标志机State Machine参数区用3字节存储字节偏移含义取值说明0x08080000Active Bank0x41A, 0x42B0x08080001Upgrade State0x00Idle, 0x01Erasing, 0x02Writing, 0x03Verifying, 0x04Committing0x08080002CRC8 of Flags校验前三字节升级流程强制按序执行检测到升级请求 → 写Upgrade State0x01→ 擦B区 → 成功则写0x02写B区数据 → 每写完1页1KB校验该页CRC → 全部成功写0x03全B区CRC32校验 → 成功则写0x04→此时才修改Active Bank写Active Bank新值 → 写CRC8 →最后一步才跳转这样即使断电发生在任意环节Bootloader都能根据Upgrade State恢复若为0x01/0x02继续擦/写若为0x03重新校验若为0x04但Active Bank未更新说明跳转前断电仍运行旧App。3. 核心细节解析从Flash操作到底层驱动每个字节都得较真3.1 Flash擦写必须绕开HAL的“温柔陷阱”HAL库的HAL_FLASHEx_Erase()函数默认开启全局中断且擦除一页需20ms以上。这意味着擦B区时若App正在处理CAN报文中断嵌套可能导致栈溢出。更致命的是HAL擦除函数内部调用FLASH_WaitForLastOperation()它用while循环轮询BSY位——这期间若发生NMI系统直接锁死。我们的裸机驱动直接操作FLASH_CR寄存器// 禁用所有中断非临界区用BASEPRI屏蔽 __set_BASEPRI(0x80); // 解锁Flash FLASH-KEYR FLASH_KEY1; FLASH-KEYR FLASH_KEY2; // 清除所有标志位 FLASH-SR FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; // 设置页擦除模式 FLASH-CR | FLASH_CR_PER; FLASH-AR page_addr; // 目标页地址如0x08040000 FLASH-CR | FLASH_CR_STRT; // 启动擦除 // 等待完成用SysTick计数器非轮询 while(FLASH-SR FLASH_SR_BSY) { if(systick_ms 30) break; // 超时退出 } FLASH-CR ~FLASH_CR_PER; // 关闭页擦除 FLASH-CR | FLASH_CR_LOCK; // 锁定Flash __set_BASEPRI(0); // 恢复中断实操心得F103的Flash擦除时间实测为18~22ms但必须留30ms余量。我曾因用25ms超时导致某批次设备在低温-20℃下擦除失败原因是Flash时序随温度漂移。3.2 CRC32校验为什么不用HAL_CRC而手写查表法HAL库的CRC计算依赖hcrp-Instance需初始化CRC外设。但OTA过程中CRC外设可能被App占用比如做通信校验且初始化耗时。我们采用查表法CRC32代码仅128字节const uint32_t crc32_table[256] { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, /* ...共256项 */ }; uint32_t crc32_calc(const uint8_t *data, uint32_t len) { uint32_t crc 0xFFFFFFFF; while(len--) { crc (crc 8) ^ crc32_table[(crc 24) ^ *data]; } return crc ^ 0xFFFFFFFF; }查表法比HAL_CRC快3倍且无需外设资源。重点在于校验范围必须包含App整个BIN文件不含头部校验字段。我们约定BIN文件格式为前4字节App起始地址0x08004000接4字节App长度不含头接N字节原始App代码末4字节CRC32按上述算法计算范围是“起始地址长度代码”Bootloader校验时先读头4字节确认地址合法必须为0x08004000或0x08040000再读长度最后校验代码段。这样即使BIN文件被截断也能快速发现。3.3 通信协议设计为什么放弃HTTP/HTTPS用自定义二进制流网上很多教程教用ESP32做WiFi OTA网关再通过HTTP POST传固件。这在F103上纯属自杀——F103没有TCP/IP协议栈加LwIP会吃掉40KB RAM只剩24KB给App连FreeRTOS都跑不稳。我们采用极简二进制流协议通过USART接收帧头0xAA 0x552字节命令0x01升级请求0x02数据块0x03校验结束长度2字节大端数据最大256字节适配USART DMA缓冲区CRC81字节XOR校验关键设计每收到一个0x02帧立即写Flash一页1KB写完发ACK0xAA 0x55 0x04 0x00 0x00 0xXX若超时500ms未收新帧自动回滚到Idle状态连续3次ACK失败关闭UART强制重启。注意DMA接收必须用双缓冲HAL_UARTEx_Receive_DMA否则单缓冲在满时丢帧。我最初用单缓冲升级到第78页时丢了一个包导致后续全错。4. 实操过程从CubeMX配置到量产烧录每一步都是血泪经验4.1 CubeMX工程配置避开那些“默认勾选”的死亡陷阱新建Bootloader工程时CubeMX里必须手动调整SYS → Debug选Serial Wire禁用Trace。Trace会占用SWO引脚而我们用PA13/PA14做SWD下载SWO冲突导致J-Link无法连接。RCC → HSE必须使能时钟树设为72MHz。F103的Flash等待周期依赖HSE若用HSI擦写时序不准。FLASH → Latency设为2 WaitState。实测1WS在72MHz下偶发写失败。GPIO → PA13/PA14Mode设为Alternate Function Push-PullSpeed为High。这是SWD必需。USART1 → Mode选Asynchronous取消勾选Hardware Flow Control。RTS/CTS在OTA中无意义且占用额外IO。最关键的链接脚本修改在STM32F103C8Tx_FLASH.ld里把MEMORY段改为MEMORY { RAM (xrw) : ORIGIN 0x20000000, LENGTH 20K FLASH (rx) : ORIGIN 0x08000000, LENGTH 16K /* Bootloader仅占16KB */ }然后在SECTIONS里强制指定.text : { . ALIGN(4); *(.vectors) /* 中断向量表必须在0x08000000 */ *(.text) /* Bootloader代码 */ } FLASH提示CubeMX生成的.ld文件默认把整个512KB Flash当做一个段不区分Bootloader/App。必须手动切分否则App编译时会覆盖Bootloader。4.2 App工程配置如何让HAL库“忘记”自己是AppApp工程同样用CubeMX生成但必须做手术式修改Project → Settings → C/C → Defines添加APP_START_ADDR0x08004000Startup file替换startup_stm32f10x_md.s修改__Vectors段起始地址__Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler ; ... 其他向量 AREA .text, CODE, READONLY ALIGN 2 THUMB REQUIRE8 PRESERVE8 EXPORT __Vectors __Vectors DCD __initial_sp ; 必须从0x08004000开始 DCD Reset_HandlerLinker Script在STM32F103C8Tx_FLASH.ld里MEMORY { RAM (xrw) : ORIGIN 0x20000000, LENGTH 20K FLASH (rx) : ORIGIN 0x08004000, LENGTH 240K /* App从0x08004000开始 */ }编译后用objdump -h firmware.elf检查.text段地址是否为0x08004000。若仍是0x08000000说明链接脚本没生效——常见原因是CubeMX重新生成时覆盖了手动修改。4.3 烧录与验证J-Link命令行才是量产唯一选择开发阶段用ST-Link Utility点烧录很爽但量产时必须用J-Link Commander脚本# bootloader.jlink si swd speed 4000 connect loadbin bootloader.bin, 0x08000000 r g qc# app_a.jlink si swd speed 4000 connect loadbin app_a.bin, 0x08004000 loadbin flags.bin, 0x08080000 # 初始化参数区A区激活 r g qc关键点speed 4000设为4MHz比默认1MHz快4倍单片烧录从12秒降到3秒flags.bin是1KB文件内容为41 00 00 [CRC8]A区激活空闲状态绝对禁止用J-Flash GUI批量烧录——它不支持多地址烧录会把App写到0x08000000覆盖Bootloader。验证步骤烧录Bootloader后用串口发0xAA 0x55 0x01 0x00 0x00 0xXX应返回0xAA 0x55 0x04 0x00 0x00 0xXX烧录App A后断电重启用逻辑分析仪抓PA0电平应看到App的LED闪烁节奏强制升级发升级指令伪造损坏BIN验证是否回退到A区。5. 常见问题与排查技巧实录那些手册里绝不会写的真相5.1 “App跳转后死机”——90%是向量表没复制或VTOR没设现象Bootloader执行((void (*)(void))(*((uint32_t*)app_addr1)))();后程序停在0x00000000。原因分析*((uint32_t*)app_addr1)取的是App的Reset_Handler地址但若App的向量表没复制到SRAMCPU从0x00000000取SP而那里是0xFF导致栈指针非法或SCB-VTOR未设置CPU仍从0x08000000取向量。排查步骤用J-Link打断点在跳转语句后查看SCB-VTOR值是否为0x20000000查看0x20000000地址处的32位值是否等于App的__initial_sp若否检查App的startup_stm32f10x_md.s是否把.vectors段链接到了0x08004000。实操心得在Bootloader跳转前加一句printf(Jump to 0x%08X\r\n, app_addr);用串口看地址是否正确。我曾因app_addr变量被优化掉实际跳转到0x00000000。5.2 “升级到一半变砖”——参数区CRC校验失效现象升级中断后设备反复重启串口输出乱码。根因参数区CRC8计算错误。F103的Flash写入必须按半字16位对齐但CRC8计算时若按字节读取会导致偶地址字节被读两次。解决方案// 正确读取参数区2字节对齐 uint16_t flags[2]; // flags[0]ActiveBankState, flags[1]CRC8 FLASH_ReadHalfWord(0x08080000, flags[0]); FLASH_ReadHalfWord(0x08080002, flags[1]); // CRC8计算时把flags[0]拆成2字节 uint8_t crc_data[3] { (flags[0] 0) 0xFF, (flags[0] 8) 0xFF, (flags[1] 0) 0xFF }; uint8_t calc_crc crc8_calc(crc_data, 3);5.3 “J-Link无法连接”——BOOT0引脚的隐形杀手现象J-Link连接失败提示“No target found”。排查清单BOOT0必须接GND正常运行模式但烧录Bootloader时需临时接VCCPA13/PA14不能接任何外部电路如LED限流电阻否则SWD信号被拉低检查原理图PA13/PA14的上拉电阻必须为10KΩ不能是100KΩ阻抗太高导致信号上升沿过缓用万用表测PA13对地电阻应为10KΩ左右若为0Ω说明PCB短路。注意有些山寨板把BOOT0接到按键按下时接VCC。这种设计在量产测试时极易误触发必须改为拨码开关。5.4 “OTA升级速度慢”——DMA缓冲区与Flash页擦写的隐性匹配现象升级1MB固件需12分钟。瓶颈定位USART波特率设为115200理论速率11.5KB/s但实际只有3KB/s原因每写1页1KB后Bootloader要擦下一页而擦页需20ms期间UART DMA缓冲区满丢包重传。优化方案将DMA缓冲区设为2KB双缓冲接收时不停止擦页操作异步化收到1KB数据后启动擦页定时器SysTick同时继续接收擦页完成中断里再写Flash。实测效果升级速度从3KB/s提升到10.2KB/s1MB固件升级时间从12分钟降至1分48秒。6. 量产落地要点从实验室到产线那些没人告诉你的潜规则6.1 BOM表里的“隐形器件”晶振负载电容必须精确到±1pFF103的HSE晶振频率稳定性直接影响Flash擦写时序。我们用的8MHz晶振手册要求负载电容20pF但实测发现用20pF电容常温下擦写成功率99.97%用22pF电容-20℃下失败率升至15%用18pF电容高温85℃下失败率8%。最终BOM定为晶振ABM3B-8.000MHZ-B2-T 负载电容GRM1555C1H200JA0120pF±1pF0402封装。采购时必须要求供应商提供每批次的电容容差报告。6.2 测试工装设计如何用5块钱成本实现100% OTA验证产线不能每台都接电脑升级。我们设计简易工装主控CH340E USB转串口芯片电阻网络4个10KΩ电阻构成分压将CH340的TXD接到F103的PA10USART1_RXRXD经反相器74HC04接到PA9USART1_TX指示灯绿色LED接PA0红色LED接PA1升级时工装自动发送预存BIN文件绿灯亮表示成功红灯亮表示失败。工装成本CH340E1.2元 电阻0.1元 LED0.05元 PCB0.8元 2.15元/套。测试速度8秒/台。6.3 固件签名为什么SHA256比RSA更适合F103有人提议用RSA-2048签名固件但F103的RAM根本不够——RSA解密需至少4KB RAM而我们只剩24KB给App。改用SHA256哈希预共享密钥HMACBootloader内置256位密钥烧录时写入OTP区域BIN文件末尾附加32字节HMAC-SHA256Bootloader计算接收数据的HMAC比对成功才写Flash。优势SHA256计算仅需1.2KB RAM且可硬件加速F103无但代码精简后仍够用。最后分享一个小技巧在Bootloader里预留一个“紧急回滚”按键。长按BOOT0 5秒强制跳回A区。这招救过我们三次产线危机——有一次B区固件因时钟配置错误导致CAN总线瘫痪靠这个按键30秒内全厂恢复。我在实际使用中发现AB分区OTA真正的价值不在技术多炫酷而在于让产品经理敢说“固件终身免费升级”。当客户知道设备买回去五年都不用返厂信任感就建立了。这比任何参数表都管用。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Spring Boot集成OpenTelemetry实现分布式链路追踪实战 2026/9/12 4:44:06

Spring Boot集成OpenTelemetry实现分布式链路追踪实战

1. 项目概述在微服务架构盛行的当下,系统间的调用关系变得异常复杂。记得去年我们团队排查一个订单超时问题,花了整整三天时间才定位到是支付服务到风控服务的gRPC调用出现了偶发性阻塞。这种场景下,分布式链路追踪技术就像给系统装上了X光机…

阅读更多 →
uutils coreutils 中 shuf 的基准测试指南:方法、命令与底层实现剖析 2026/9/12 4:44:06

uutils coreutils 中 shuf 的基准测试指南:方法、命令与底层实现剖析

uutils coreutils 中 shuf 的基准测试指南:方法、命令与底层实现剖析 【免费下载链接】coreutils Cross-platform Rust rewrite of the GNU coreutils 项目地址: https://gitcode.com/GitHub_Trending/co/coreutils shuf 从表面看是一个"把输入随机打乱…

阅读更多 →
无人机三维路径规划:多目标遗传算法MATLAB实现 2026/9/12 4:44:06

无人机三维路径规划:多目标遗传算法MATLAB实现

1. 项目概述:当无人机遇上多目标遗传算法去年参与某山区物资运输项目时,我遇到了一个典型的三维路径规划难题——需要在复杂地形中为无人机舰队规划兼顾安全性、能耗和时效性的飞行路线。传统A*算法在二维平面表现尚可,但面对三维空间中的多约…

阅读更多 →
深入解析JavaScript闭包:原理与应用 2026/9/12 4:44:06

深入解析JavaScript闭包:原理与应用

1. JavaScript闭包的核心概念解析闭包是JavaScript中最强大也最容易让人困惑的特性之一。简单来说,闭包就是一个函数能够记住并访问它所在的词法作用域,即使这个函数在其词法作用域之外执行。这种特性让JavaScript拥有了许多独特的编程模式。1.1 闭包的基…

阅读更多 →
SpringBoot美食分享系统开发实战 2026/9/12 4:44:06

SpringBoot美食分享系统开发实战

1. 项目概述这个基于SpringBoot和Java的地方特色美食分享管理系统,本质上是一个垂直领域的社区论坛平台。我花了三个月时间从零开发完成,核心目标是解决美食爱好者"找不到正宗地方特色店"和"探店经验无法沉淀"两大痛点。系统采用经典…

阅读更多 →
遗传算法优化微电网调度的MATLAB实现 2026/9/12 4:41:06

遗传算法优化微电网调度的MATLAB实现

1. 项目概述:微电网调度与遗传算法的完美结合微电网作为分布式能源系统的重要形态,正在全球范围内快速发展。它能够整合风电、光伏等可再生能源,配合蓄电池和微型燃气轮机等可控电源,形成一个自给自足的电力供应单元。我从事微电网…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞