4、Flash驱动与分区设计:Flash物理特性、扇区/页擦除写入、分区表设计
做MCU Bootloader,绕不开Flash。说句实话,我见过太多项目栽在Flash操作上——不是擦写时序搞错,就是分区表设计不合理,导致升级失败后板子变砖。这一章,咱们就把Flash的脾气摸透,把分区表设计得明明白白。
4.1 Flash物理特性——你得知道它有多“倔”
Flash不是RAM,你不能像写内存那样随便往里头写数据。它有三大特性,你必须刻在脑子里:
- 写前必擦:Flash只能将1写成0,但不能将0写成1。想改数据?得先把整个扇区擦成0xFF,再写新值。
- 擦除有寿命:每个扇区擦写次数有限,一般是1万到10万次。Boot区擦得少,App区频繁升级,你得心里有数。
- 写入要按页:大多数MCU支持按页写入,一页通常是256字节或512字节。你不能写半个字节,得凑够一页。
核心结论:Flash操作的最小单位是“扇区擦除”和“页写入”。扇区大小因芯片而异,从1KB到128KB都有。设计驱动前,先翻芯片手册,把扇区大小、页大小、擦写时间记下来。
4.2 扇区与页——擦除和写入的“砖块”
我习惯把Flash想象成一栋楼:扇区是楼层,页是房间。你要装修(写数据),得先清空整个楼层(擦除扇区),再逐个房间布置(页写入)。
| 概念 | 类比 | 典型大小 | 操作 |
|---|---|---|---|
| 扇区 (Sector) | 楼层 | 4KB / 8KB / 64KB | 擦除(整层清空) |
| 页 (Page) | 房间 | 256B / 512B / 1KB | 写入(逐个房间放家具) |
举个例子,STM32F4系列,扇区大小不统一:前4个扇区各16KB,后面是64KB和128KB。我刚开始做时没注意这个,结果擦除时算错地址,把Boot代码给抹了……嗯,从那以后我写驱动前一定会画一张Flash地址映射图。
4.3 Flash驱动实现——擦、写、读三板斧
驱动函数不需要多,三个就够了:擦除扇区、写入页、读取数据。我给你看一个精简的驱动框架:
/* 擦除一个扇区 */
int flash_sector_erase(uint32_t sector_addr) {
// 1. 检查地址是否在Flash范围内
// 2. 解锁Flash控制器
// 3. 等待上次操作完成
// 4. 设置扇区地址,发起擦除
// 5. 等待擦除完成(轮询或中断)
// 6. 加锁Flash控制器
// 7. 返回状态
}
/* 写入一页数据 */
int flash_page_write(uint32_t page_addr, uint8_t *data, uint32_t len) {
// 1. 检查地址对齐(页边界)
// 2. 检查长度不超过页大小
// 3. 解锁Flash控制器
// 4. 按字或半字写入
// 5. 等待写入完成
// 6. 加锁Flash控制器
// 7. 回读校验(可选,我强烈建议加上)
}
/* 读取数据(其实就是内存访问) */
void flash_read(uint32_t addr, uint8_t *buf, uint32_t len) {
memcpy(buf, (uint8_t*)addr, len);
}
我的习惯:每次写入后都做一次回读校验。别信硬件说“写入成功”,我踩过坑——电源不稳时,Flash写入会静默失败。回读能第一时间发现问题。
4.4 分区表设计——Bootloader的“城市规划”
分区表是Bootloader的骨架。设计不好,升级失败连回退的机会都没有。我个人习惯把Flash至少分成四个区:
- Boot区:存放Bootloader代码,一般32KB~128KB。这个区几乎不擦写,寿命最长。
- App区:存放用户应用程序。升级时擦写这个区。
- 备份区:存放上一个稳定版本的App。升级失败时回滚用。
- 配置区:存放设备参数、升级标志、版本号等。频繁读写,选小扇区。
下面这张图是我常用的分区结构,你一看就明白:
4.5 分区设计要点——避坑指南
分区不是随便划几块就完事。我总结了几条实战经验:
- Boot区放最前面:MCU上电从0x08000000开始执行,Boot区必须从起始地址开始。
- App区对齐扇区边界:App起始地址必须是扇区大小的整数倍。否则擦除时会误伤Boot区。
- 备份区和App区大小一致:这样升级时可以直接整区复制,逻辑简单。
- 配置区用最小的扇区:配置数据经常改,用大扇区擦一次要等很久,还浪费寿命。
我曾经犯过的错:有一次我把配置区和App区放在同一个大扇区里。每次升级擦除App区,配置数据也跟着没了。后来我专门给配置区划了一个独立的小扇区,再也没出过问题。
4.6 分区表示例——以STM32F407为例
给你看一个实际项目的分区表。STM32F407有1MB Flash,扇区大小不统一,设计时要特别小心:
| 分区 | 起始地址 | 大小 | 扇区编号 | 用途 |
|---|---|---|---|---|
| Boot区 | 0x08000000 | 64KB | Sector 0~3 (各16KB) | Bootloader代码 |
| App区 | 0x08010000 | 384KB | Sector 4~6 (128KB+128KB+128KB) | 用户应用程序 |
| 备份区 | 0x08070000 | 384KB | Sector 7~9 (128KB+128KB+128KB) | 回滚备份 |
| 配置区 | 0x080D0000 | 16KB | Sector 10 (16KB) | 升级标志、版本号 |
| 剩余 | 0x080D4000 | 176KB | Sector 11 (16KB) + 剩余 | 预留或日志 |
小技巧:配置区最后两个字节放一个“校验魔数”,比如0x5A5A。Boot启动时检查这个魔数,如果不对,说明配置区被破坏了,直接进入恢复模式。这个做法救过我很多次。
4.7 分区设计检查清单
每次设计新板子的分区,我都会过一遍这个清单:
- ☐ Boot区起始地址 = Flash起始地址?
- ☐ 所有分区起始地址对齐扇区边界?
- ☐ 备份区大小 ≥ App区大小?
- ☐ 配置区使用最小扇区?
- ☐ 配置区有校验机制(魔数或CRC)?
- ☐ 升级过程中断电,能保证回滚?
嗯,Flash驱动和分区设计就这些。说白了,就是摸清Flash的脾气,然后给它画好格子。格子画好了,后面的升级、回滚、配置管理都顺风顺水。要是格子画歪了……你想想看,升级到一半发现擦错了区,那滋味可不好受。