3、Flash分区设计:分区表规划原则、双备份(A/B)分区策略、恢复分区与工厂分区设计

Flash分区设计,说白了就是给芯片的存储空间画格子。每个格子放什么,多大,谁先谁后,这些看似简单的问题,往往是整个Bootloader项目里最容易翻车的地方。我见过太多团队,代码写得漂漂亮亮,结果分区表一塌糊涂,最后升级失败、变砖、返厂……嗯,今天我们就来聊聊怎么把这几个格子画明白。

3.1 分区表规划原则

分区表不是随便画几条线就完事的。我个人习惯,在画分区表之前,先想清楚三个问题:

  • 芯片容量够不够? 别把分区画得太满,留点余量。我曾经有个项目,分区表算得死死的,结果后来加了个小功能,愣是挤不进去,最后只能重新调整分区,折腾了整整一周。
  • 擦除粒度对齐了吗? Flash是按扇区(Sector)擦除的,分区起始地址和大小最好对齐到扇区边界。不对齐的话,擦除操作可能会误伤邻居分区。
  • 升级路径考虑了吗? 分区表一旦定下来,后续升级时能不能动?动了会不会导致旧版本固件不兼容?这些都要提前想好。
我的小技巧: 分区表最好放在Flash的固定位置,比如最开头或者最末尾。这样即使其他分区内容变了,Bootloader也能准确找到分区表。我习惯把分区表放在Flash的0地址,紧跟着就是Bootloader本身。

下面这张图,是我常用的一个分区布局示例:

典型Flash分区布局 Bootloader 区 (128KB) 参数/配置区 (64KB) App A (1MB) App B (1MB) 恢复/工厂分区 (512KB) 数据/日志区 (剩余空间) Flash地址增长方向

3.2 双备份(A/B)分区策略

双备份分区,也叫A/B分区,是现在嵌入式设备升级的主流方案。说白了就是准备两份应用程序区,一份在跑,一份等着被升级。

为什么需要A/B分区? 你想想看,如果只有一个App区,升级写到一半突然断电了,或者网络断了,那设备就变砖了。有了A/B分区,就算升级失败,Bootloader还能从另一个分区启动,设备至少还能用。

核心思想: 永远保证至少有一个可用的App分区。Bootloader启动时,检查两个分区的状态,选一个健康的启动。

我参与过一个智能家居项目,客户要求设备必须支持远程升级,而且升级失败后不能变砖。我们当时就用了A/B分区方案。具体流程是这样的:

  1. 正常启动: Bootloader检查分区状态,选择标记为“健康”的分区启动。假设当前是App A在运行。
  2. 触发升级: App A下载新固件,写入App B分区。写入完成后,将App B标记为“待验证”。
  3. 重启验证: Bootloader发现App B是“待验证”状态,尝试从App B启动。如果App B运行正常,它会把自身状态改为“健康”。
  4. 回滚机制: 如果App B启动失败,Bootloader会把它标记为“失败”,然后回退到App A。设备依然可用。
避坑指南: 我曾经遇到过一个坑——两个分区的固件版本号没有做兼容性检查。结果App A是v1.0,App B升级到v2.0,但v2.0的参数格式变了,导致App B启动后读取旧参数直接崩溃。后来我们在分区头部加了版本号字段,Bootloader启动前先校验版本兼容性,不兼容就直接回滚。

A/B分区策略的优缺点,我整理了一个表格:

对比项 A/B分区 单分区
安全性 高,升级失败可回滚 低,升级失败即变砖
Flash占用 需要2倍App空间 只需1倍App空间
升级速度 后台下载,不影响运行 必须停机升级
实现复杂度 中等,需要状态管理 简单

3.3 恢复分区与工厂分区设计

恢复分区和工厂分区,这两个概念容易搞混。我简单解释一下:

  • 恢复分区(Recovery): 一个最小化的系统,专门用来修复主系统。比如主系统崩溃了,可以进恢复分区重新刷固件。
  • 工厂分区(Factory): 出厂时预置的“黄金版本”固件。当设备彻底变砖时,可以从工厂分区恢复。

我个人习惯,把恢复分区和工厂分区合并成一个分区。为什么呢?因为很多MCU的Flash空间有限,单独划两个分区太奢侈了。合并后,这个分区里放一个最小化的Bootloader+App,既能做恢复,也能做工厂复位。

设计要点: 恢复分区里的固件,必须是经过充分测试的稳定版本。我见过有人把开发中的版本放进恢复分区,结果恢复时直接翻车……嗯,这个教训挺深刻的。

恢复分区的启动流程,我画了一张图:

恢复分区启动流程 上电启动 Bootloader检查 App正常 → 启动App App异常 → 进入恢复 恢复分区启动

恢复分区里一般放什么?我列个清单:

  • 最小化Bootloader: 能识别恢复分区,能擦写Flash,能通过UART/USB/网络接收固件。
  • 基础驱动: Flash驱动、通信驱动(比如UART、USB、以太网)。不需要复杂的应用层驱动。
  • 固件接收协议: 比如YModem、XModem、或者自定义的简单协议。别搞太复杂,稳定第一。
  • 状态指示: 比如LED闪烁,告诉用户设备正在恢复模式。
避坑指南: 恢复分区的大小一定要留够。我曾经有个项目,恢复分区只留了256KB,结果后来Bootloader功能增加,塞不进去了。最后只能压缩代码,去掉一些调试信息,才勉强塞下。建议恢复分区至少留512KB,如果Flash够大,1MB更稳妥。

工厂分区设计,还有一个容易被忽略的点——防误擦除。工厂分区是设备的最后一道防线,绝对不能轻易被擦掉。我习惯在工厂分区头部加一个特殊的魔数(Magic Number),Bootloader在擦除前必须校验这个魔数,只有确认是“可擦除”状态才允许操作。否则,就算App发来擦除指令,Bootloader也直接忽略。

嗯,分区设计这块,说复杂也复杂,说简单也简单。核心就是一句话:留足余量,对齐扇区,做好回滚,保护工厂。把这四点记住了,分区设计基本不会出大问题。