3、Flash分区设计:分区表规划原则、双备份(A/B)分区策略、恢复分区与工厂分区设计
Flash分区设计,说白了就是给芯片的存储空间画格子。每个格子放什么,多大,谁先谁后,这些看似简单的问题,往往是整个Bootloader项目里最容易翻车的地方。我见过太多团队,代码写得漂漂亮亮,结果分区表一塌糊涂,最后升级失败、变砖、返厂……嗯,今天我们就来聊聊怎么把这几个格子画明白。
3.1 分区表规划原则
分区表不是随便画几条线就完事的。我个人习惯,在画分区表之前,先想清楚三个问题:
- 芯片容量够不够? 别把分区画得太满,留点余量。我曾经有个项目,分区表算得死死的,结果后来加了个小功能,愣是挤不进去,最后只能重新调整分区,折腾了整整一周。
- 擦除粒度对齐了吗? Flash是按扇区(Sector)擦除的,分区起始地址和大小最好对齐到扇区边界。不对齐的话,擦除操作可能会误伤邻居分区。
- 升级路径考虑了吗? 分区表一旦定下来,后续升级时能不能动?动了会不会导致旧版本固件不兼容?这些都要提前想好。
下面这张图,是我常用的一个分区布局示例:
3.2 双备份(A/B)分区策略
双备份分区,也叫A/B分区,是现在嵌入式设备升级的主流方案。说白了就是准备两份应用程序区,一份在跑,一份等着被升级。
为什么需要A/B分区? 你想想看,如果只有一个App区,升级写到一半突然断电了,或者网络断了,那设备就变砖了。有了A/B分区,就算升级失败,Bootloader还能从另一个分区启动,设备至少还能用。
我参与过一个智能家居项目,客户要求设备必须支持远程升级,而且升级失败后不能变砖。我们当时就用了A/B分区方案。具体流程是这样的:
- 正常启动: Bootloader检查分区状态,选择标记为“健康”的分区启动。假设当前是App A在运行。
- 触发升级: App A下载新固件,写入App B分区。写入完成后,将App B标记为“待验证”。
- 重启验证: Bootloader发现App B是“待验证”状态,尝试从App B启动。如果App B运行正常,它会把自身状态改为“健康”。
- 回滚机制: 如果App B启动失败,Bootloader会把它标记为“失败”,然后回退到App A。设备依然可用。
A/B分区策略的优缺点,我整理了一个表格:
| 对比项 | A/B分区 | 单分区 |
|---|---|---|
| 安全性 | 高,升级失败可回滚 | 低,升级失败即变砖 |
| Flash占用 | 需要2倍App空间 | 只需1倍App空间 |
| 升级速度 | 后台下载,不影响运行 | 必须停机升级 |
| 实现复杂度 | 中等,需要状态管理 | 简单 |
3.3 恢复分区与工厂分区设计
恢复分区和工厂分区,这两个概念容易搞混。我简单解释一下:
- 恢复分区(Recovery): 一个最小化的系统,专门用来修复主系统。比如主系统崩溃了,可以进恢复分区重新刷固件。
- 工厂分区(Factory): 出厂时预置的“黄金版本”固件。当设备彻底变砖时,可以从工厂分区恢复。
我个人习惯,把恢复分区和工厂分区合并成一个分区。为什么呢?因为很多MCU的Flash空间有限,单独划两个分区太奢侈了。合并后,这个分区里放一个最小化的Bootloader+App,既能做恢复,也能做工厂复位。
恢复分区的启动流程,我画了一张图:
恢复分区里一般放什么?我列个清单:
- 最小化Bootloader: 能识别恢复分区,能擦写Flash,能通过UART/USB/网络接收固件。
- 基础驱动: Flash驱动、通信驱动(比如UART、USB、以太网)。不需要复杂的应用层驱动。
- 固件接收协议: 比如YModem、XModem、或者自定义的简单协议。别搞太复杂,稳定第一。
- 状态指示: 比如LED闪烁,告诉用户设备正在恢复模式。
工厂分区设计,还有一个容易被忽略的点——防误擦除。工厂分区是设备的最后一道防线,绝对不能轻易被擦掉。我习惯在工厂分区头部加一个特殊的魔数(Magic Number),Bootloader在擦除前必须校验这个魔数,只有确认是“可擦除”状态才允许操作。否则,就算App发来擦除指令,Bootloader也直接忽略。
嗯,分区设计这块,说复杂也复杂,说简单也简单。核心就是一句话:留足余量,对齐扇区,做好回滚,保护工厂。把这四点记住了,分区设计基本不会出大问题。