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%的坏块,使用过程中还会产生新的坏块。如果不处理,数据随时可能丢失。

我总结了一套坏块管理的策略,分三个层次:

  1. 出厂坏块标记:NAND芯片出厂时,厂商会在每个坏块的第一个页的特定位置(通常是第2048字节)标记为非0xFF。上电初始化时,扫描所有块,建立坏块表。
  2. 运行时坏块检测:每次写入或擦除操作后,检查状态寄存器。如果返回失败,标记该块为坏块。
  3. 坏块替换:使用预留的备用块替换坏块。我一般会预留总块数的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驱动和分区管理,看似基础,但细节决定成败。下一章我们会讲固件镜像的生成和签名,到时候见。