4、Flash驱动开发:读写擦除操作、分区规划与磨损均衡
Flash 驱动,说白了就是固件升级系统的地基。地基没打好,上面盖的房子再漂亮也得塌。我在这个领域摸爬滚打十几年,见过太多因为 Flash 操作不当导致的“变砖”事故。今天咱们就把这块硬骨头啃下来。
4.1 Flash 的物理特性与基本操作
先聊聊 Flash 的脾气。它有三个核心操作:读、写、擦除。但有个坑你必须知道——写操作只能把 1 变成 0,不能把 0 变成 1。想把 0 变回 1?只能擦除。而擦除的最小单位是扇区(Sector),通常是 4KB 或 64KB。
来看一个标准的 Flash 驱动接口设计:
/* Flash 驱动接口定义 */
typedef struct {
int (*init)(void);
int (*read)(uint32_t addr, uint8_t *buf, uint32_t len);
int (*write)(uint32_t addr, const uint8_t *buf, uint32_t len);
int (*erase)(uint32_t addr, uint32_t len);
int (*sync)(void);
} flash_drv_t;
我个人习惯把接口设计成这种结构体形式。好处很明显:换 Flash 芯片时,只需要换一套驱动函数指针,上层代码完全不用动。
4.2 分区规划:给固件安个家
分区规划是门学问。你想想看,一个固件升级系统,至少需要这几个区域:
| 分区名称 | 用途 | 大小建议 | 说明 |
|---|---|---|---|
| Bootloader | 启动引导 | 64KB | 不可升级,出厂固化 |
| App Primary | 主固件运行区 | 512KB | 正常运行时执行 |
| App Backup | 备份固件区 | 512KB | 升级失败时回滚 |
| Download | 下载暂存区 | 512KB | 接收新固件数据 |
| Config | 配置参数区 | 64KB | 设备参数、升级标志 |
| Log | 日志记录区 | 128KB | 运行日志、错误记录 |
嗯,这里要注意:分区大小必须是擦除扇区大小的整数倍。否则你擦除时会擦到隔壁分区的数据,那画面太美我不敢看。
4.3 磨损均衡:让 Flash 活得更久
Flash 有擦写寿命限制——MLC 大约 3000-10000 次,SLC 能到 10 万次。如果频繁往同一个扇区写数据,那个扇区会先“累死”。磨损均衡就是让所有扇区“轮流上班”。
为什么会这样?因为固件升级时,配置参数、升级标志这些数据会频繁更新。如果不做均衡,Config 分区可能几个月就报废了。
我常用的策略是动态映射 + 日志结构:
/* 磨损均衡实现思路 */
typedef struct {
uint32_t logical_addr; /* 逻辑地址 */
uint32_t physical_addr; /* 物理地址 */
uint32_t erase_count; /* 擦除次数 */
uint8_t valid; /* 是否有效 */
} wear_level_entry_t;
/* 写操作流程 */
int wear_level_write(uint32_t logic_addr, uint8_t *data, uint32_t len) {
/* 1. 找一个擦除次数最少的空闲扇区 */
uint32_t free_phy = find_min_erase_sector();
/* 2. 擦除该扇区 */
flash_erase(free_phy);
/* 3. 写入数据 */
flash_write(free_phy, data, len);
/* 4. 更新映射表 */
update_mapping(logic_addr, free_phy);
/* 5. 标记旧扇区为无效 */
invalidate_old_sector(logic_addr);
return 0;
}
说白了,就是每次写数据都换个新地方,旧的标记为“垃圾”,等垃圾攒够了再统一回收。这思路跟 SSD 的 FTL 层有点像,只不过咱们在 MCU 上实现个简化版。
- 磨损均衡的核心是避免热点——不让某个扇区被频繁擦写
- 映射表要保存在 Flash 里,掉电不丢失
- 垃圾回收要在系统空闲时做,不能阻塞升级流程
4.4 整体架构与流程
把上面这些串起来,就是一套完整的 Flash 管理层。我画了张图帮你理解:
从图里能看出来,上层应用只管调用接口,不用关心底层怎么擦、怎么均衡。这就是分层设计的好处。
4.5 避坑指南与实战建议
最后,分享几个我这些年用真金白银换来的教训:
- 掉电保护必须做:升级过程中突然断电,数据写到一半怎么办?我的做法是写前先写“升级中”标志,写完后改为“完成”。Bootloader 启动时检查这个标志,如果发现是“升级中”,就回滚到备份区。
- 校验不能省:每次写完数据,读回来做 CRC 校验。别信 Flash 的“写入成功”返回值,我遇到过写成功但数据不对的奇葩情况。
- 磨损均衡的映射表要备份:映射表坏了,整个 Flash 的数据都找不到了。我一般存两份,交叉校验。
- 别在中断里做擦除操作:擦除一个扇区可能要几十毫秒,在中断里做会阻塞系统。放到任务里做,或者用 DMA。
Flash 驱动开发,说白了就是“小心驶得万年船”。把基础打牢了,后面的升级系统才能跑得稳。记住:分区规划决定上限,磨损均衡决定寿命,驱动质量决定可靠性。这三样都做好了,你的固件升级系统就成功了一半。