2. 升级架构设计:Bootloader与App分区设计,Flash布局规划,双备份区(A/B分区)策略
好,咱们进入第二章。这一章讲的是升级架构的骨架——Flash怎么划分、Bootloader和App怎么共存、双备份区到底怎么玩。说白了,就是决定你的固件升级系统长什么样。
我见过不少团队,一上来就写升级代码,结果Flash布局没想清楚,后面改得痛不欲生。嗯,咱们先把地基打牢。
2.1 Flash布局规划:先画好地皮
MCU的Flash就像一块地皮。你要在上面盖房子(Bootloader)、住人(App)、还要留个临时仓库(升级缓存区)。地皮就这么大,怎么分?
我个人习惯,拿到一个新芯片,第一件事就是看它的Flash总大小和扇区(Sector)大小。不同MCU的扇区大小不一样——有的4KB,有的16KB,有的128KB。这个细节很关键,因为擦除操作是按扇区来的。
举个例子,一个典型的512KB Flash布局:
| 分区 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| Bootloader | 0x08000000 | 64KB | 启动、校验、升级入口 |
| App A(主区) | 0x08010000 | 192KB | 当前运行的应用固件 |
| App B(备份区) | 0x08040000 | 192KB | 新固件下载到这里 |
| 参数区 | 0x08070000 | 64KB | 升级标志、版本号、配置 |
你看,每个分区都对齐到了扇区边界。Bootloader放在最前面,因为MCU上电默认从0x08000000开始执行。参数区我习惯放在最后,这样擦写App时不会影响到它。
2.2 Bootloader与App分区设计:谁管谁?
Bootloader和App的关系,有点像机场的塔台和飞机。塔台(Bootloader)负责指挥,飞机(App)负责执行任务。飞机起飞后,塔台就不管了,除非飞机要降落(升级)。
Bootloader的核心职责就三件事:
- 校验App的完整性——启动前检查CRC或签名,确保App没坏。
- 决定启动哪个分区——根据升级标志,选A区还是B区。
- 执行升级流程——接收新固件,写入备份区,切换标志。
App这边,需要做两件事:
- 正常运行时,接收升级包并写入备份区。
- 写完后,设置升级标志,然后软复位。
这里有个容易踩的坑——中断向量表重映射。App的起始地址不是0x08000000,所以它的中断向量表要偏移。以STM32为例,需要在App的启动代码里设置:
// App 起始地址为 0x08010000
#define APP_BASE 0x08010000
// 在 main 函数最前面设置向量表偏移
SCB->VTOR = APP_BASE;
如果不做这一步,中断来了会跑到Bootloader的向量表里去,系统直接崩溃。我记得有个项目,同事调试了三天,最后发现是忘了设VTOR。
2.3 双备份区(A/B分区)策略:升级失败的保险
双备份区,也叫A/B分区。说白了就是准备两份App空间,一份在跑,一份等着被覆盖。
为什么需要这个?你想想看,如果只有一个App区,升级写到一半断电了,或者写进去的固件是坏的,设备就变砖了。双备份区就是为了解决这个问题。
工作流程是这样的:
- 设备当前在A区运行。
- 收到新固件,写入B区。
- 校验B区的完整性(CRC/SHA256)。
- 校验通过,设置标志位,下次启动从B区启动。
- 如果校验失败,继续从A区启动,升级失败但设备不坏。
下面这张图展示了A/B分区的切换逻辑:
2.4 升级标志与状态机
升级标志放在参数区,我一般用一个结构体来管理:
typedef struct {
uint32_t magic; // 魔数,用于校验参数区是否有效
uint32_t boot_flag; // 0xAA: 从B区启动, 0x55: 从A区启动
uint32_t update_status; // 0: 空闲, 1: 下载中, 2: 校验中, 3: 完成
uint32_t version_a; // A区版本号
uint32_t version_b; // B区版本号
uint32_t crc32; // 整个结构体的CRC校验
} UpgradeParam_t;
升级状态机其实很简单,就四个状态:
- 空闲——正常跑App,啥也不干。
- 下载中——App在接收升级包,写入备份区。
- 校验中——下载完成后,Bootloader或App对备份区做完整性校验。
- 完成——校验通过,设置标志,准备切换。
2.5 实际项目中的取舍
双备份区不是所有场景都适用。我列个对比表,帮你判断:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| Flash > 512KB,产品价值高 | A/B分区 | 安全第一,不怕变砖 |
| Flash < 256KB,成本敏感 | 单分区 + 外部备份 | Flash不够分,用外部Flash或EEPROM存备份 |
| OTA升级频繁 | A/B分区 | 升级失败可回滚,用户体验好 |
| 出厂后几乎不升级 | 单分区 | 没必要浪费Flash空间 |
嗯,总结一下。Flash布局是升级架构的根基,Bootloader是守门员,A/B分区是安。这三样东西想清楚了,后面的升级代码写起来就顺了。
我个人建议,不管项目大小,至少留一个升级标志位和版本号字段。哪怕现在用不上,以后要加OTA功能时,你会发现当初这个决定有多明智。