4、Flash驱动与分区管理:NOR/NAND Flash操作,坏块管理策略
好,咱们进入第四章。这一章讲的是Flash驱动和分区管理。说白了,就是星载计算机怎么跟Flash芯片打交道。
我个人觉得,这部分是固件升级方案里最容易被低估的一环。很多人觉得Flash驱动嘛,调个库就完事了。但在航天场景下,事情远没那么简单。你想想看,卫星在天上飞着,Flash要是出点问题,地面可没法派人上去修。
4.1 NOR Flash vs NAND Flash:选型背后的考量
先说说这两种Flash的区别。我在项目中遇到过不少工程师,上来就问「哪个更好」。其实没有绝对的好,只有适不适合。
| 特性 | NOR Flash | NAND Flash |
|---|---|---|
| 读取速度 | 快(支持XIP,代码可直接在芯片上运行) | 较慢(需要先拷贝到RAM) |
| 写入速度 | 慢(按字节写) | 快(按页写) |
| 擦除速度 | 慢(按扇区擦) | 快(按块擦) |
| 可靠性 | 高(坏块极少) | 较低(出厂就有坏块) |
| 容量 | 小(通常64MB以下) | 大(可达数GB) |
| 成本 | 高 | 低 |
嗯,这里要注意。在星载计算机里,我通常的做法是:NOR Flash放启动代码和关键固件,NAND Flash放数据和备份镜像。为什么?因为NOR支持XIP(eXecute In Place),上电就能跑,不需要初始化内存控制器。而NAND虽然容量大、便宜,但坏块问题让人头疼。
核心原则:NOR Flash用于「必须可靠运行」的代码,NAND Flash用于「可以容忍一定风险」的数据和备份。
4.2 NOR Flash操作:按字节的精细控制
NOR Flash的操作其实挺直观的。它支持按字节读取,写入前必须先擦除。我习惯把NOR Flash的操作抽象成三个基本函数:
// NOR Flash 基本操作接口
int nor_read(uint32_t addr, uint8_t *buf, uint32_t len);
int nor_write(uint32_t addr, const uint8_t *buf, uint32_t len);
int nor_erase(uint32_t addr, uint32_t size); // 注意:擦除粒度是扇区
写NOR Flash的时候有个坑——写入前必须确保目标区域是擦除状态(全0xFF)。我曾经见过一个同事,直接往已写过的区域写数据,结果数据全乱了。排查了半天才发现是忘了擦除。
警告:NOR Flash的写入操作不能将1变成0以外的状态。如果目标地址不是0xFF,写入结果不可预测。务必先擦除再写入。
还有一个细节:NOR Flash的擦除时间比较长,一个64KB的扇区可能需要几百毫秒。在实时系统中,这个时间窗口要处理好。我一般会在擦除前关中断,或者用DMA方式异步处理。
4.3 NAND Flash操作:页与块的游戏
NAND Flash的操作逻辑跟NOR完全不同。它按页读写,按块擦除。一个典型的NAND芯片,页大小是2KB或4KB,块大小是64页或128页。
为什么这样设计?说白了,NAND追求的是大容量和低成本,所以内部结构更密集,操作粒度也更粗。
// NAND Flash 基本操作接口
int nand_read_page(uint32_t block, uint32_t page, uint8_t *buf);
int nand_write_page(uint32_t block, uint32_t page, const uint8_t *buf);
int nand_erase_block(uint32_t block);
NAND有个特点:读和写都按页进行,不能按字节操作。这意味着如果你想修改某个页里的几个字节,你得先把整个页读到RAM里,修改,再擦除整个块,最后把整个页写回去。嗯,听着就麻烦对吧?
我在项目中遇到过一个问题:NAND的写入次数有限,每个块大约10万次擦写寿命。如果频繁更新同一个块,它很快就报废了。所以后来我引入了磨损均衡(Wear Leveling)策略,把写入操作分散到不同的块上。
4.4 坏块管理策略:NAND的必修课
坏块管理,这是NAND Flash绕不开的话题。你想想看,一块NAND芯片出厂时就可能有1%-2%的坏块,使用过程中还会产生新的坏块。如果不处理,数据随时可能丢失。
我总结了一套坏块管理的策略,分三个层次:
- 出厂坏块标记:NAND芯片出厂时,厂商会在每个坏块的第一个页的特定位置(通常是第2048字节)标记为非0xFF。上电初始化时,扫描所有块,建立坏块表。
- 运行时坏块检测:每次写入或擦除操作后,检查状态寄存器。如果返回失败,标记该块为坏块。
- 坏块替换:使用预留的备用块替换坏块。我一般会预留总块数的2%-3%作为备用池。
个人经验:坏块表一定要保存在NOR Flash里,或者NAND的固定好块上。千万别把坏块表写在NAND的随机位置,否则坏块表本身可能丢失。
我曾经犯过一个错误:把坏块表保存在NAND的最后几个块里。结果那几个块后来也坏了,坏块表读不出来,整个NAND的数据都废了。从那以后,我坚持把坏块表放在NOR Flash里,或者至少做三份冗余备份。
4.5 分区管理:让固件升级有条不紊
分区管理,说白了就是把Flash划分成不同的区域,各司其职。我常用的分区方案是这样的:
| 分区名称 | 起始地址 | 大小 | 用途 |
|---|---|---|---|
| Bootloader | 0x00000000 | 64KB | 启动代码,不可升级 |
| 固件A(主区) | 0x00010000 | 1MB | 当前运行的固件 |
| 固件B(备份区) | 0x00110000 | 1MB | 升级用的备份固件 |
| 配置参数区 | 0x00210000 | 128KB | 系统配置、校准数据 |
| 日志区 | 0x00230000 | 512KB | 运行日志、故障记录 |
| 数据区 | 0x002B0000 | 剩余空间 | 用户数据、文件系统 |
嗯,这里要注意几个关键点:
- Bootloader必须放在NOR Flash里,且写保护。这样即使升级失败,系统也能启动。
- 固件A和固件B互为备份。升级时写入B,校验通过后切换启动指针。如果B启动失败,自动回退到A。
- 配置参数区要预留多个副本。我一般做三份,每次写入时写两份,读取时三份取多数一致。
避坑指南:分区大小要留余量。我曾经因为分区算得太紧,后来固件功能增加,空间不够用了。最后不得不重新分区,整个Flash都得擦除重写。建议至少预留20%的余量。
4.6 实战中的几个坑
最后,分享几个我在项目中踩过的坑:
- 擦除中断问题:NOR Flash擦除时如果掉电或复位,可能导致扇区数据全乱。解决方案是擦除前先保存关键数据,或者使用双缓冲策略。
- NAND的位翻转:NAND Flash在读取时可能出现单个比特翻转。必须使用ECC(纠错码)来检测和纠正。我一般用BCH或LDPC算法。
- 写保护误触发:有些Flash芯片有写保护引脚,如果硬件设计时没处理好,可能导致写入失败。建议在驱动初始化时先检查写保护状态。
好了,这一章的内容就到这里。Flash驱动和分区管理,看似基础,但细节决定成败。下一章我们会讲固件镜像的生成和签名,到时候见。