4. 存储器架构:Flash分区策略与磨损均衡
做OTA Bootloader,说白了就是跟Flash打交道。
我刚开始接触这块时,觉得不就是读写Flash嘛,有啥难的?结果第一次做产品升级,分区没规划好,差点把整个产线搞崩。嗯,今天咱们就把Flash分区这件事彻底聊透。
4.1 Flash分区策略
一个典型的MCU OTA系统,Flash至少要分成四个区:Boot区、App区、备份区、参数区。我习惯这么划分:
| 分区名称 | 起始地址 | 大小 | 作用 |
|---|---|---|---|
| Boot区 | 0x08000000 | 64KB | 存放Bootloader代码,上电先执行 |
| 参数区 | 0x08010000 | 4KB | 存储升级标志、版本号等配置 |
| App区 | 0x08011000 | 384KB | 主应用程序运行区 |
| 备份区 | 0x08071000 | 384KB | 接收新固件,校验后覆盖App区 |
为什么参数区要单独拎出来?
你想想看,如果参数和App混在一起,每次升级App,参数就被擦掉了。我之前有个项目,设备校准数据存在App区末尾,升级一次就得重新校准,客户差点骂娘。
核心原则:Boot区只负责启动和升级,App区只跑业务逻辑,参数区存持久化数据,备份区做升级缓冲。各司其职,互不干扰。
4.2 Flash读写特性
Flash这东西,写之前必须先擦除。而且擦除是按扇区来的,不是按字节。
我记得第一次用STM32时,想改一个字节,直接调了写函数,结果啥也没发生。查了半天手册才发现——擦除是前提。
几个关键点:
- 写操作:只能将1写成0,不能把0写成1。想恢复1?必须擦除整个扇区。
- 擦除粒度:不同MCU不一样。STM32F1是2KB一个扇区,F4是128KB。选MCU时一定要看手册。
- 写入粒度:通常是16位或32位。别想着按字节写,很多MCU不支持。
// 典型Flash写入流程(伪代码)
void flash_write(uint32_t addr, uint32_t *data, uint32_t len) {
// 1. 解锁Flash
flash_unlock();
// 2. 擦除目标扇区
flash_erase_sector(addr);
// 3. 按32位写入
for (int i = 0; i < len; i++) {
flash_program_word(addr + i*4, data[i]);
}
// 4. 锁定Flash
flash_lock();
}
注意:擦除操作很耗时。一个128KB的扇区,擦除可能要几百毫秒。升级过程中如果断电,数据就丢了。所以备份区的作用就在这里——先收完整固件,再擦除App区。
4.3 擦除寿命与磨损均衡
Flash有擦写寿命,一般是1万到10万次。听起来很多?
我做过一个数据采集设备,每5秒存一次参数。按1万次寿命算,不到14小时就挂了。这就是典型的「擦写寿命陷阱」。
怎么解决?磨损均衡。
说白了,就是不要把数据总写在一个地方。比如参数区有4KB,但每次只写一个扇区,写满了再换下一个。这样寿命就翻了N倍。
我常用的策略:
- 环形缓冲区:参数区划分成多个槽位,写满一个槽就换下一个。
- 版本号标记:每个槽位头部存一个递增的版本号,启动时读版本号最高的那个。
- 垃圾回收:当所有槽位都用完,擦除最旧的那个,重新开始。
// 磨损均衡示例:参数存储
#define SLOT_COUNT 8
#define SLOT_SIZE 512
typedef struct {
uint32_t version; // 递增版本号
uint8_t data[SLOT_SIZE - 4];
} param_slot_t;
uint32_t find_latest_slot(void) {
uint32_t max_ver = 0;
uint32_t best_slot = 0;
for (int i = 0; i < SLOT_COUNT; i++) {
param_slot_t *slot = (param_slot_t *)(PARAM_BASE + i * SLOT_SIZE);
if (slot->version > max_ver) {
max_ver = slot->version;
best_slot = i;
}
}
return best_slot;
}
避坑指南:我曾经在磨损均衡里忘了处理「所有槽位版本号溢出」的情况。版本号从0xFFFFFFFF回滚到0,结果系统认为新数据是旧数据,直接用了垃圾数据。解决方案:版本号用64位,或者检测到溢出时强制擦除所有槽位。
4.4 实际项目中的分区考量
分区大小不是拍脑袋定的。我一般这么算:
- Boot区:够放Bootloader代码就行,通常64KB~128KB。别贪大,浪费Flash。
- App区:根据实际固件大小,留20%余量。比如固件300KB,App区给384KB。
- 备份区:和App区一样大。因为要存完整的新固件。
- 参数区:4KB~16KB。配合磨损均衡,够用很久。
嗯,分区这事,前期多花点时间,后期少掉很多头发。我见过太多项目,分区不合理,最后只能改硬件、换MCU,成本翻倍。
记住一句话:Flash分区是OTA的骨架,骨架歪了,肉长再好也没用。
公众号:蓝海资料掘金营,微信deep3321