2、固件升级的硬件基础:MCU/SoC的存储架构

做固件升级,说白了你得先搞清楚芯片里那点存储空间是怎么安排的。我见过不少工程师,上来就写升级逻辑,结果发现Flash不够用,或者Bootloader把自己给覆盖了——嗯,那场面挺尴尬的。

这一节,咱们就把MCU/SoC的存储架构掰开揉碎讲清楚。你理解了这些,后面讲升级流程的时候,才能明白为什么要有那么多“小心翼翼”的设计。

2.1 三大存储元件:Flash、RAM、BootROM

一个典型的MCU或应用级SoC,内部通常有三个关键的存储角色。它们各司其职,缺一不可。

元件 特性 主要用途 掉电后数据
Flash 非易失、可擦写、读快写慢 存放固件代码、参数、文件系统 保留
RAM 易失、读写极快、容量通常较小 运行时堆栈、变量、数据缓存 丢失
BootROM 非易失、只读、出厂固化 存放一级启动代码(不可更改) 保留

核心认知:固件升级的本质,就是把新固件写入Flash的指定区域。而BootROM和RAM,则是保证这个写入过程能安全、可靠执行的基础。

2.1.1 Flash:固件的“家”

Flash是存储固件的主体。我个人习惯把它想象成一个“格子本”,每个格子(Sector/Page)可以单独擦除和写入。但有个坑:Flash写入前必须先擦除,而且擦除操作很慢,通常需要几十到几百毫秒。

我在项目中遇到过一个问题:升级过程中突然断电,导致Flash里一个Sector只擦了一半。重新上电后,那片区域读出来的数据全是乱的。所以后来我设计升级逻辑时,一定会加“擦除校验”和“写后回读”的步骤。

⚠️ 注意:Flash的擦写寿命是有限的(通常1万到10万次)。频繁升级或日志写入,会加速Flash老化。设计参数存储区时,建议使用磨损均衡算法。

2.1.2 RAM:升级的“工作台”

RAM是临时存储区。升级过程中,它主要干三件事:

  • 缓存接收到的固件包:从通信接口(UART、SPI、Wi-Fi)收来的数据,先放到RAM里攒够一包,再写入Flash。
  • 运行升级程序:Bootloader或升级代理程序本身就在RAM里运行(或者从Flash拷贝到RAM里执行)。
  • 存放临时变量和状态机:升级进度、校验结果、错误码等。

你想想看,如果RAM太小,一次只能缓存512字节,那升级一个大固件就得频繁擦写Flash,效率极低。所以我建议,RAM至少要有2KB以上的空闲空间给升级流程用,否则很容易出现“内存不足”的尴尬。

2.1.3 BootROM:芯片的“第一口气”

BootROM是芯片出厂时固化的一段只读代码。芯片上电后,CPU第一条指令就是从BootROM里取的。它的任务很简单:初始化硬件,然后决定从哪个地址启动。

BootROM通常支持多种启动模式:从Flash启动、从UART下载、从USB下载等。这给固件升级提供了“最后一道防线”——如果Flash里的Bootloader坏了,你还可以通过BootROM强制进入下载模式,重新烧录。

💡 经验之谈:我习惯在BootROM阶段就开启看门狗,防止升级过程中程序跑飞。虽然BootROM代码改不了,但你可以通过配置字(Option Bytes)来设置看门狗的行为。

2.2 存储分区规划:谁该待在哪?

有了Flash、RAM、BootROM,接下来就是怎么划分它们。分区规划做得好,升级就成功了一半。做不好?嗯,你可能得经常拿着编程器去现场救火。

一个典型的固件升级分区结构如下:

MCU存储分区规划示意图 MCU/SoC 存储分区规划(典型结构) BootROM(只读,出厂固化) Flash 存储区 Bootloader 区 存放二级启动代码、升级代理程序 App 区(应用程序区) 存放主固件代码,升级时被覆盖 参数区(Parameter Area) 存放配置参数、校准数据、升级标志 💡 说明: 分区大小需根据Flash总容量和固件大小灵活调整。Bootloader区通常预留 32KB~256KB。 App区至少占Flash总容量的 60% 以上。

2.2.1 Bootloader区:升级的“守门员”

Bootloader区存放的是二级启动代码。芯片上电后,BootROM会跳转到Bootloader区执行。Bootloader的任务是:

  1. 检查升级标志:看看有没有“需要升级”的标记。
  2. 如果有升级请求:进入升级模式,接收新固件,写入App区。
  3. 如果没有升级请求:校验App区的完整性,然后跳转过去执行。

我曾经犯过一个错误:把Bootloader做得太大,占用了128KB。结果App区只剩256KB,客户要加新功能,死活塞不进去。最后只能重新分区,把Bootloader压缩到48KB。所以我的建议是:Bootloader能小就小,够用就行。通常32KB~64KB就足够了。

⚠️ 关键:Bootloader区在升级过程中绝对不能写!如果升级程序把自己所在的Flash区域给擦了,那芯片就变砖了。所以Bootloader区必须写保护,或者通过硬件引脚来锁定。

2.2.2 App区:主固件的“家”

App区就是存放你写的业务代码的地方。升级时,新固件会直接覆盖这个区域。

这里有个设计要点:App区起始地址必须固定。Bootloader跳转到App区时,是直接跳到一个绝对地址。如果你改了App区的起始地址,Bootloader也得跟着改,那升级就变成“先升级Bootloader,再升级App”的套娃问题了。

我个人习惯在App区的头部预留一个“向量表重定位”区域。这样即使App区起始地址变了,也能通过修改向量表来适配。嗯,这个技巧在Cortex-M系列芯片上特别有用。

2.2.3 参数区:升级的“记事本”

参数区虽然小,但作用很大。它通常存放:

  • 升级标志:0xAA55 表示“请求升级”,0x0000 表示“正常启动”。
  • 固件版本号:用于判断是否需要升级。
  • 校验值:App区的CRC或哈希值,用于启动时校验完整性。
  • 校准数据:设备出厂时的校准参数,升级时不能丢。

💡 避坑指南:我曾经遇到过一个问题:升级过程中参数区被意外写入了错误数据,导致设备重启后一直认为“需要升级”,陷入了死循环。后来我加了一个“参数区备份”机制——在Flash里存两份参数,一份主本,一份副本。启动时比较两者,如果一致才使用,不一致则用默认值恢复。

2.3 分区规划实战建议

说了这么多,到底怎么规划才合理?我根据经验总结了一个参考方案:

Flash总容量 Bootloader区 App区 参数区 备注
256KB 32KB 216KB 8KB 适合简单IoT设备
512KB 64KB 432KB 16KB 适合中等复杂度设备
1MB 64KB 944KB 16KB 适合带文件系统的设备
2MB 128KB 1.8MB 32KB 适合带双备份(A/B升级)的设备

你可能会问:“为什么参数区只给这么点?” 嗯,因为参数区通常只需要存几个标志和校验值,8KB已经绰绰有余了。而且参数区擦写频繁,给它太大反而浪费Flash寿命。

好了,存储架构和分区规划就讲到这里。你理解了Flash、RAM、BootROM各自的作用,也知道了Bootloader区、App区、参数区该怎么划分,后面讲升级流程的时候,就能明白每一步操作背后的硬件逻辑了。