3、分区规划:Flash分区表设计(Bootloader、App、OTA临时区)

好,咱们接着聊OTA升级。上一节讲了升级流程,这一节咱们得落地了。

落地第一步是什么?分区规划

你想想看,Flash就那么大一块地儿,你得想好哪块放Bootloader,哪块放App,哪块留给OTA下载新固件。分不好,升级就崩。我在项目中见过太多因为分区没规划好,升级到一半把系统搞成砖的案例了。

3.1 为什么分区这么重要?

说白了,分区就是给Flash画格子。每个格子有固定的用途。

没有分区规划,你连从哪里启动都不知道。Bootloader不知道去哪找App,App也不知道该把新固件写到哪里。整个系统就是一团乱麻。

我个人的习惯是:先画分区图,再写一行代码。分区图就是整个OTA方案的骨架。

核心原则:

  • Bootloader区:只读,几乎不改
  • App区:运行主程序
  • OTA临时区:下载新固件,校验通过后再覆盖App区

3.2 典型的分区布局

以我常用的ESP32为例,它的Flash通常是4MB或8MB。咱们来看一个典型的4MB分区表:

# ESP32 4MB Flash 分区表示例
# Name,   Type, SubType, Offset,  Size,   Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x1C0000,
app1,     app,  ota_1,   0x1D0000,0x1C0000,
spiffs,   data, spiffs,  0x390000,0x70000,

嗯,这里要注意:Offset和Size必须对齐Flash的擦除块大小。ESP32是4KB对齐,有些芯片是64KB甚至128KB。不对齐的话,写数据时会出各种诡异问题。

我曾经在一个项目里,因为偏移量没对齐,导致OTA升级后App启动时偶尔崩溃。查了三天,最后发现是分区边界跨了擦除块。从那以后,我每次画分区表都会反复核对对齐。

3.3 三个核心区域详解

3.3.1 Bootloader区

Bootloader是系统的看门人。它负责:

  • 上电后检查App区是否有效
  • 如果App区坏了,进入恢复模式
  • 如果OTA临时区有新固件,执行升级

Bootloader区一般放在Flash的最开头。为什么?因为芯片上电后默认从地址0x00000000开始执行。这是硬件决定的,咱们改不了。

我个人建议Bootloader区留64KB到256KB。太小了功能受限,太大了浪费空间。我一般用128KB,够用且不浪费。

小技巧:Bootloader里不要放太多功能。只做三件事:校验、跳转、升级。功能越多,出bug的概率越大。Bootloader一旦坏了,整个设备就真成砖了。

3.3.2 App区

App区就是你的主程序。它通常占Flash的大部分空间。

在OTA方案里,App区一般有两个副本:

  • App A(当前运行)
  • App B(备份或待升级)

这就是所谓的A/B分区方案。两个分区大小完全一样。当前运行的是A,新固件就下载到B。下次启动时,Bootloader检查B是否有效,有效就切到B运行。

这样做的好处是:升级失败也不怕。系统可以回滚到上一个版本。

我记得有个客户做智能灯,要求升级成功率99.9%以上。我直接推荐了A/B分区方案。虽然多占了一倍Flash空间,但可靠性确实上去了。

3.3.3 OTA临时区

OTA临时区是干嘛用的?

它是新固件的中转站。固件从云端下载下来,先放在这里。校验通过后,再复制到App区。

为什么不能直接下载到App区?

你想想看,如果下载到一半断电了,App区里的程序就被破坏了。下次开机,Bootloader发现App区无效,系统就起不来了。

所以,先下载到临时区,校验通过再复制,这是最稳妥的做法。

注意:OTA临时区的大小必须大于最大固件包的大小。我一般留1.5倍的空间。比如固件最大1MB,临时区就留1.5MB。为什么?因为有些压缩固件解压后体积会膨胀。

3.4 分区大小怎么定?

这个问题没有标准答案。但我可以给你一个参考公式:

区域 建议大小 说明
Bootloader 64KB - 256KB 够用就行,别贪大
App A 固件大小 + 20%余量 留点空间给后续功能扩展
App B 与App A相同 A/B分区必须一样大
OTA临时区 固件大小 × 1.5 考虑解压膨胀
参数存储区 16KB - 64KB 存WiFi密码、设备ID等

嗯,这个表格是我多年经验总结出来的。你直接拿去用,基本不会出大问题。

3.5 避坑指南

我踩过的坑,不希望你再踩一遍。

  • 分区边界要对齐:不对齐,写数据会跨块,轻则性能下降,重则数据损坏。
  • Bootloader要独立编译:不要和App混在一起。Bootloader一旦编译好,就不要轻易改。
  • 预留升级空间:固件只会越来越大。分区时留20%的余量,不然半年后你就得重新规划分区。
  • 参数区要单独分区:不要把WiFi密码、设备ID这些参数放在App区。升级时App区会被覆盖,参数就丢了。

我曾经有个项目,把参数存在了App区的末尾。第一次升级没问题,第二次升级时固件体积变大了,直接把参数区给覆盖了。结果所有设备都得返厂重新配置。嗯,从那以后,参数区我都是单独划出来的。

3.6 小结

分区规划是OTA升级的基石。地基没打好,上面盖的房子再漂亮也没用。

记住三个核心:

  • Bootloader要稳
  • App区要留余量
  • OTA临时区要够大

下一节,咱们聊聊Bootloader的具体实现。到时候我会手把手教你写一个能跑起来的Bootloader。

课后思考:如果你的Flash只有1MB,你会怎么规划分区?A/B方案肯定用不了,那你会用什么方案来保证升级可靠性?