2、内存分区策略:Flash分区规划、Bootloader分区、App分区、参数分区设计
做OTA升级,说白了就是跟Flash打交道。你想想看,Flash就那么大一块地儿,怎么分、分几块、每块干啥用,直接决定了你的升级方案稳不稳。
我刚开始做OTA那会儿,就吃过分区规划的亏。当时图省事,Bootloader和App挤在一起,结果一次升级失败,整个设备变砖了。嗯,从那以后,我再也不敢随便糊弄分区了。
2.1 Flash分区规划的基本原则
分区规划没有标准答案,但有几个原则我建议你死守:
- 独立性原则:每个分区功能单一,互不干扰。Bootloader只管启动,App只管跑业务,参数分区只管存数据。
- 对齐原则:分区起始地址和大小,最好对齐到Flash的擦除块大小(Sector/Page)。我见过有人没对齐,结果擦写操作跨了块,数据全乱套。
- 冗余原则:给关键分区留点余量。比如App分区,别刚好塞满,万一后续功能加几行代码呢?
核心要点:分区规划的核心,就是让Bootloader、App、参数三者之间「老死不相往来」。各管各的,出了问题也好排查。
2.2 Bootloader分区设计
Bootloader分区,是整个系统的「守门员」。它负责校验App、引导启动、执行升级。所以这个分区必须稳如老狗。
我个人习惯,Bootloader分区一般只占一个或两个擦除块。比如STM32的Flash,一个Sector是16KB或64KB,Bootloader放个32KB就够用了。为什么?因为Bootloader代码量不大,功能也固定,没必要贪大。
Bootloader分区里通常包含:
- 启动向量表(Reset Vector)
- Flash驱动(用于擦写App分区)
- 校验逻辑(CRC或SHA)
- 升级协议处理(比如从UART或SPI接收数据)
避坑指南:我曾经把Bootloader做得太大,结果跟App分区重叠了。一启动就死机,查了两天才发现是分区地址算错了。所以,Bootloader分区一定要在链接脚本里硬编码地址,别用相对地址。
2.3 App分区设计
App分区是主战场。这里我重点讲两种常见方案:
方案一:单分区模式
只有一个App分区,升级时直接覆盖写入。优点是简单,缺点是升级失败就变砖。适合对可靠性要求不高的产品。
// 单分区Flash布局示例
0x08000000 - 0x08007FFF: Bootloader (32KB)
0x08008000 - 0x0803FFFF: App (224KB)
0x08040000 - 0x0807FFFF: 参数分区 (256KB)
方案二:双分区模式(A/B分区)
两个App分区,一个运行,一个备份。升级时写入备份区,校验通过后切换启动。这是目前最稳妥的方案。
我参与过一个车载项目,用的就是A/B分区。有一次升级到一半断电了,重启后Bootloader检测到A区校验失败,自动切到B区启动。用户完全没感觉,这就是双分区的价值。
// 双分区Flash布局示例
0x08000000 - 0x08007FFF: Bootloader (32KB)
0x08008000 - 0x0803FFFF: App_A (224KB)
0x08040000 - 0x0807FFFF: App_B (224KB)
0x08080000 - 0x080FFFFF: 参数分区 (512KB)
注意:双分区虽然安全,但Flash占用翻倍。如果你的芯片Flash只有512KB,那就要掂量掂量了。我建议在成本允许的情况下,优先选双分区。
2.4 参数分区设计
参数分区,存的是设备运行时的配置数据。比如WiFi密码、校准参数、用户设置等。这个分区有个特点:频繁读写,但数据量小。
参数分区的设计要点:
- 磨损均衡:Flash有擦写寿命(一般10万次)。如果每次都写同一个地址,很快就报废了。我习惯用「日志式存储」,每次写新数据都追加到下一个空闲位置,满了再擦除整块。
- 校验保护:参数数据必须带CRC或校验和。我遇到过参数被写坏的情况,设备启动后WiFi连不上,查了半天发现是参数校验位被覆盖了。
- 备份机制:至少保留两份参数副本。一份主参数,一份备份。读取时先校验主参数,坏了就切备份。
经验之谈:参数分区的大小,我一般留2-4个擦除块。比如每个块64KB,那就留128KB或256KB。别太小,否则磨损均衡效果出不来。
2.5 分区规划实战示例
下面是一个实际项目的分区表,芯片是STM32F407,Flash 1MB:
| 分区名称 | 起始地址 | 大小 | 用途 |
|---|---|---|---|
| Bootloader | 0x08000000 | 64KB | 启动引导、升级执行 |
| App_A | 0x08010000 | 384KB | 主运行程序 |
| App_B | 0x08070000 | 384KB | 备份程序 |
| 参数分区 | 0x080D0000 | 128KB | 配置数据、校准参数 |
| 日志分区 | 0x080F0000 | 64KB | 运行日志记录 |
你看,这个设计里Bootloader只占64KB,两个App各384KB,参数分区128KB,还留了个日志分区。每个分区都对齐到64KB边界,擦写操作不会跨块。
小技巧:分区规划时,我习惯在链接脚本里用宏定义把地址写死。比如:
#define BOOTLOADER_ADDR 0x08000000
#define APP_A_ADDR 0x08010000
#define APP_B_ADDR 0x08070000
#define PARAM_ADDR 0x080D0000
这样代码里引用地址时一目了然,后期改分区也方便。
2.6 总结一下
内存分区策略,说白了就是给Flash「分家」。Bootloader管启动,App管业务,参数管配置。各司其职,互不越界。
我做了这么多年嵌入式,最大的体会就是:分区规划别怕麻烦。前期多花半小时想清楚,后期能省下好几天的调试时间。你想想看,一次升级失败导致设备变砖,那损失可比分区多占几KB Flash大多了。
嗯,下一章我会讲升级过程中的内存动态管理,包括堆栈规划、DMA缓冲区设计这些实战内容。到时候咱们再细聊。