3、Flash分区设计:分区表的概念、Bootloader分区、App分区、参数分区、备份分区的布局策略。
说到Bootloader升级,Flash分区设计是绕不开的核心话题。很多刚入行的朋友觉得分区不就是划几个区域嘛,随便分分就行。嗯,我当年也这么想过,直到有一次产品量产了才发现分区不够用,那叫一个尴尬。
分区设计说白了,就是给你的Flash芯片做「房产规划」。哪里放Bootloader,哪里放App,哪里存参数,哪里留备份,都得提前想清楚。今天我就把这块掰开了讲。
3.1 分区表的概念
分区表,本质上是一张「地址-用途」映射表。它告诉Bootloader:哪个区域是干什么的,从哪里开始,到哪里结束。
我个人习惯把分区表放在Flash的最开头,紧挨着中断向量表。这样Bootloader启动后第一件事就是读分区表,知道自己该干什么。
分区表的核心字段:
- 分区名称:比如"bootloader"、"app"、"param"
- 起始地址:该分区在Flash中的偏移量
- 分区大小:该分区占用的字节数
- 分区属性:可读、可写、可执行、是否加密等
- 校验值:分区表自身的CRC,防止被篡改
这里有个坑——分区表本身不能太大。我见过有人把分区表做得比App还复杂,结果Bootloader光解析分区表就花了半秒钟。你想想看,用户按一下开机键,等半秒才亮屏,这体验能好吗?
一个典型的分区表结构大概长这样:
typedef struct {
uint32_t magic; // 魔数,用于校验分区表有效性
uint8_t version; // 分区表版本号
uint8_t partition_cnt; // 分区数量
uint8_t reserved[2]; // 保留字节
uint32_t crc32; // 分区表CRC校验
partition_entry_t entries[PARTITION_MAX_COUNT];
} partition_table_t;
typedef struct {
char name[16]; // 分区名称
uint32_t offset; // 起始偏移
uint32_t size; // 分区大小
uint32_t flags; // 属性标志位
} partition_entry_t;
3.2 Bootloader分区
Bootloader分区是系统的「第一口气」。它负责初始化硬件、校验App、决定是否进入升级模式。
这个分区有几个硬性要求:
- 必须放在Flash起始位置:MCU上电后从0x00000000开始执行,Bootloader必须在这里
- 大小要保守:我个人建议64KB起步,128KB比较稳妥。别贪多,Bootloader功能单一,写不了多少代码
- 写保护:量产之后,Bootloader分区最好加上硬件写保护。我曾经遇到过一例OTA升级把Bootloader自己给覆盖了的惨案……嗯,从那以后我再也不敢不加保护了
避坑指南:
我曾经在一个项目里把Bootloader分区设成32KB,结果功能越加越多,最后硬塞进去,每次升级都提心吊胆。后来我学乖了——Bootloader分区至少留50%的余量。因为你永远不知道产品生命周期里会不会加新功能。
3.3 App分区
App分区是用户代码的驻地。这里存放的就是你写的业务逻辑、协议栈、UI等等。
App分区设计时要注意几个点:
- 起始地址要对齐:很多MCU要求App起始地址按扇区对齐,否则擦写会出问题
- 大小要灵活:如果产品支持OTA升级,App分区最好留出30%-50%的余量。代码只会越写越多,不会越写越少
- 中断向量表重映射:App启动时要把中断向量表从Bootloader分区重映射到App分区自己的位置
我见过一个典型的App分区布局:
| 区域 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| 中断向量表 | 0x08010000 | 512B | App自己的中断入口 |
| 代码段 | 0x08010200 | 192KB | 可执行代码 |
| 只读数据 | 0x08040000 | 64KB | 常量、字符串表 |
| BSS段 | 0x08050000 | 32KB | 未初始化全局变量 |
3.4 参数分区
参数分区是用来存「掉电不能丢」的数据的。比如WiFi密码、设备ID、校准参数、用户设置等等。
这里有个设计要点——参数分区不能和代码分区共用同一个扇区。为什么?因为参数经常要擦写,而代码几乎不写。如果混在一起,每次改参数都要擦一大片Flash,既慢又伤寿命。
我的经验:
参数分区最好用「双备份」策略。也就是同一份参数存两份,写的时候先写备份,再写主份。如果中途掉电,下次启动时Bootloader会发现主份校验失败,自动从备份恢复。这个技巧我用了好多年,从来没丢过参数。
参数分区的典型大小是4KB到64KB,具体看你的参数有多少。我个人习惯用4KB的扇区,因为擦写速度快,而且浪费少。
3.5 备份分区
备份分区是OTA升级的「安全网」。它的作用很简单——在升级App之前,先把当前能用的App完整拷贝到备份分区。如果新App启动失败,Bootloader可以从备份分区恢复。
备份分区的布局策略有两种:
- 全量备份:备份分区大小等于App分区大小。简单粗暴,但Flash占用大
- 差分备份:只备份App分区的关键部分(比如中断向量表和启动代码)。省空间,但恢复逻辑复杂
我个人更推荐全量备份。虽然多占一倍Flash,但恢复逻辑极其简单——直接把备份分区的内容拷回App分区就行。差分备份省下来的那点空间,往往不够你调试恢复逻辑时掉的头发。
注意:
备份分区本身也需要保护。我见过有人把备份分区和参数分区放在同一个扇区,结果参数擦写时把备份也给擦了。升级到一半掉电,两个分区全坏,设备直接变砖。所以,备份分区一定要独立,别跟任何频繁擦写的分区共用扇区。
3.6 分区布局策略总结
说了这么多,我们来画一张图,把整个Flash分区的布局看清楚:
这张图展示的是一个比较经典的布局。Bootloader在最前面,参数分区紧随其后,然后是App分区,最后是备份分区。为什么参数分区要放在Bootloader后面?因为Bootloader启动时需要读参数(比如判断要不要进升级模式),放近了读取速度快。
当然,具体怎么分还得看你的Flash大小和MCU型号。有些MCU的Flash扇区大小不统一,前几个扇区是16KB,后面是64KB,这时候分区就得跟着扇区边界走。
分区设计黄金法则:
- Bootloader分区写保护——这是底线,破了就变砖
- App分区留余量——别卡着尺寸设计,未来会后悔
- 参数分区双备份——掉电不丢数据,用户不会骂娘
- 备份分区独立——别跟参数分区共用扇区,血的教训
好了,Flash分区设计这块就聊到这儿。记住一句话:分区设计是Bootloader升级的地基,地基没打好,上面盖多高的楼都白搭。下次你画分区表的时候,多想想我今天说的这些坑,能省不少事。