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方案肯定用不了,那你会用什么方案来保证升级可靠性?