第2章:固件升级核心原理:固件分区表设计
各位同学,今天我们来聊聊固件升级里最核心的东西——分区表设计。说实话,很多刚入行的工程师觉得升级不就是把新固件写进去吗?哪有那么简单。我在项目里见过太多次因为分区设计不合理,导致设备变砖的惨案了。
分区表,说白了就是给Flash存储器画格子。每个格子放什么,多大尺寸,谁先启动,谁做备份,这些都得提前规划好。你想想看,如果连这些都没想清楚,后面做升级方案就是空中楼阁。
2.1 三大核心分区:Bootloader、App、Recovery
一个成熟的物联网设备,至少要有三个分区。我习惯把它们比作一个团队:Bootloader是队长,App是干活的主力,Recovery是替补队员。
2.1.1 Bootloader分区
Bootloader是设备上电后第一个运行的程序。它的任务很简单:检查App分区是否完好,如果完好就跳过去执行;如果坏了,就启动Recovery模式。
我个人习惯把Bootloader做得尽量小,功能单一。为什么?因为Bootloader出问题,设备就彻底变砖了。越小越不容易出错。
2.1.2 App分区
App分区存放主程序固件。这是最常更新的区域。设计时要注意两点:
- 分区大小要留余量——固件只会越来越大,不会越来越小。我建议至少留30%的余量。
- 版本号管理——每个固件都要带版本号,Bootloader靠它判断是否需要升级。
2.1.3 Recovery分区
Recovery分区是个保险。当App分区损坏时,设备可以进入Recovery模式,通过某种方式(比如U盘、网络、串口)重新烧录固件。
嗯,这里要注意:Recovery分区里的程序也要能升级,但频率很低。我一般只在出厂时烧录一次,后续除非重大bug,否则不动它。
2.2 启动流程详解
设备上电后,启动流程是这样的:
- CPU从固定地址读取Bootloader
- Bootloader校验App分区的完整性(CRC或签名验证)
- 如果App完好,跳转到App入口地址
- 如果App损坏,跳转到Recovery分区
- Recovery模式下等待用户干预或自动恢复
为什么会这样设计?因为设备可能在任何时候断电。如果升级过程中突然断电,App分区可能只写了一半。这时候Bootloader就能检测到校验失败,自动进入Recovery模式。
2.3 回滚机制
回滚,就是升级失败后能退回到上一个版本。这个功能在工业设备上尤其重要。你想想看,如果升级后设备全都不工作了,生产线得停多久?
实现回滚有两种常见方案:
方案一:双备份区(A/B分区)
这是最可靠的方式。把Flash分成两个App区:App_A和App_B。当前运行的是App_A,升级时就把新固件写入App_B。写入完成后,Bootloader下次启动时切换到App_B。
如果App_B启动失败,Bootloader自动切回App_A。这就是回滚。
| 分区 | 大小 | 用途 |
|---|---|---|
| Bootloader | 64KB | 启动管理 |
| App_A | 1MB | 当前运行版本 |
| App_B | 1MB | 新版本/备份 |
| Recovery | 256KB | 紧急恢复 |
| 配置区 | 64KB | 存储启动标志、版本号等 |
这种方案的缺点很明显:Flash占用翻倍。对于成本敏感的物联网设备,可能不太现实。
方案二:增量升级 + 备份指针
如果Flash空间有限,可以用这个方案。升级时先把当前固件备份到另一个区域,然后写入新固件。如果新固件启动失败,就从备份区恢复。
我曾经在一个项目里用过这个方案。当时Flash只有2MB,但固件已经占了1.5MB。没办法做双备份,就只能用备份指针的方式。效果还行,但恢复速度比双备份慢一些。
2.4 分区表设计实战建议
说了这么多理论,来点实际的。我总结了几条设计原则:
- 分区边界要对齐Flash的擦除块大小——比如NAND Flash的擦除块是128KB,那分区大小最好是128KB的整数倍。否则会浪费空间,而且擦除操作会变慢。
- 配置区单独放——不要把版本号、启动标志这些信息放在App分区里。否则升级时这些信息也会被覆盖,Bootloader就不知道当前是什么版本了。
- 预留一个"工厂分区"——存放出厂时的原始固件。万一所有分区都坏了,还能从工厂分区恢复。这个分区我一般设为只读。
最后说一句:分区表设计没有标准答案。不同的硬件、不同的应用场景,方案都不一样。但核心思想是一样的——保证设备在任何情况下都能启动,都能恢复。
下一章我们会讲具体的升级协议设计,包括HTTP、MQTT、CoAP这些协议怎么选,怎么保证传输的可靠性。到时候再聊。