2、Flash存储器详解:NOR Flash vs NAND Flash、内部结构(浮栅晶体管)、编程/擦除原理、读/写/擦除时序、磨损均衡与寿命管理

好,咱们今天来啃一块硬骨头——Flash存储器。说实话,Flash是嵌入式系统里最让人又爱又恨的器件。爱它,是因为它掉电不丢数据;恨它,是因为它的写入和擦除机制实在太“拧巴”了。我刚开始做产品时,就因为没搞懂NOR和NAND的区别,导致一个批量项目差点翻车。嗯,今天咱们就把这事彻底捋清楚。

2.1 NOR Flash vs NAND Flash:核心差异

先问一个问题:你选Flash时,第一反应是什么?很多人直接看容量和价格。但我觉得,你得先搞清楚你的CPU是怎么“接”这个Flash的。

NOR Flash和NAND Flash,名字听起来像兄弟,但脾气完全不同。我习惯用一个比喻:NOR像你书桌上的内存条,可以随机访问任意地址;NAND像仓库里的货架,你得先找到货架号,再一箱一箱翻。

对比项 NOR Flash NAND Flash
读取方式 随机访问,类似SRAM 页读取,先发命令再读
写入单位 字节/字 页(通常2KB/4KB)
擦除单位 扇区(64KB~128KB) 块(128KB~512KB)
典型容量 1MB~64MB 128MB~8GB+
读速度 快(~100ns随机访问) 较快(页读取~25μs)
写速度 慢(~10μs/字节) 快(~200μs/页)
擦除速度 慢(~1s/扇区) 快(~2ms/块)
坏块管理 无(出厂无坏块) 必须有(出厂可能有坏块)
典型用途 代码就地执行(XIP) 大容量数据存储

你看,NOR最大的优势是“随机读快”,所以很多MCU直接把程序放在NOR里跑,这叫XIP(eXecute In Place)。而NAND呢,它天生就是为“大容量顺序读写”设计的,比如存音频、视频、文件系统。

关键选型原则:如果你的代码需要直接在Flash里运行,选NOR;如果你要存几百兆的数据,选NAND。别想着用NAND跑代码,那延迟会让你崩溃的。

2.2 内部结构:浮栅晶体管

Flash能存数据,靠的是一个叫“浮栅晶体管”的东西。说白了,就是在普通MOS管的栅极下面,又加了一层“浮空的栅极”。这层浮栅被二氧化硅绝缘层包裹着,电荷一旦进去,就很难跑出来。

为什么会这样?你想想看,普通MOS管断电后,栅极上的电荷很快就泄放了。但浮栅不一样,它被绝缘层“关”在里面,电荷可以保持10年以上。这就是Flash“非易失性”的物理基础。

我画了一张图,帮你理解这个结构:

浮栅晶体管结构示意图 P型衬底 (Substrate) 源极 漏极 沟道 (Channel) 隧穿氧化层 浮栅 (Floating Gate) 绝缘层 (Inter-poly Dielectric) 控制栅 (Control Gate) 字线 (Word Line) 浮栅被绝缘层包裹,注入的电荷可保持10年以上

这张图里,控制栅接字线,源极和漏极接位线。当你在控制栅上加高压,电子会穿过隧穿氧化层,注入到浮栅里。这个过程叫“编程”。反过来,把浮栅里的电子拉出来,叫“擦除”。

我的经验:浮栅里的电子数量决定了晶体管的阈值电压。有电子时,阈值电压升高,读出来是“0”;没电子时,阈值电压低,读出来是“1”。所以Flash的初始状态(擦除后)是“1”,写入是往“0”写。这个逻辑跟很多人的直觉相反,我第一次用的时候也搞反了。

2.3 编程/擦除原理

编程和擦除,说白了就是“注入电子”和“拉出电子”两个动作。但这两个动作都需要高压,通常是8V~20V。所以Flash芯片内部都有电荷泵,把VCC升上去。

编程(Program):Fowler-Nordheim隧穿

在控制栅上加高压(比如+12V),源极和漏极接地。电子从沟道穿过隧穿氧化层,注入浮栅。这个过程很慢,但很精确。NOR可以按字节编程,NAND只能按页编程。

擦除(Erase):也是Fowler-Nordheim隧穿,但方向相反

在控制栅上加负高压(比如-12V),或者把源极接正高压。电子从浮栅被拉回沟道。擦除必须按扇区(NOR)或块(NAND)进行,不能只擦一个字节。

注意:每次编程和擦除都会对隧穿氧化层造成损伤。氧化层一旦击穿,这个存储单元就废了。NOR的擦写寿命通常是10万次,NAND是1万~10万次(取决于工艺)。

2.4 读/写/擦除时序

搞嵌入式,光懂原理不够,还得会看时序图。我挑几个关键点说说:

NOR Flash读取时序

NOR的读操作跟SRAM很像:给地址,等一段时间(比如70ns),数据就出来了。所以很多MCU可以直接把NOR映射到地址空间里,用普通的读指令就能访问。

  • CE#(片选):拉低,选中芯片
  • OE#(输出使能):拉低,打开数据输出缓冲
  • WE#(写使能):读操作时保持高电平
  • 地址建立时间:地址必须在CE#和OE#之前稳定
  • 数据输出时间:从OE#拉低到数据有效,通常几十纳秒

NAND Flash读取时序

NAND的读操作就复杂多了。你得先发命令(0x00),再发地址(5个字节),再发命令(0x30),然后等芯片准备好(RB#引脚变高),最后读数据。

// NAND Flash页读取伪代码
void nand_read_page(uint32_t page_addr, uint8_t *buffer) {
    // 1. 发送读命令
    nand_send_cmd(0x00);
    // 2. 发送5字节地址(列地址+行地址)
    nand_send_addr(page_addr);
    // 3. 发送确认命令
    nand_send_cmd(0x30);
    // 4. 等待芯片就绪(RB#引脚)
    while (!nand_is_ready());
    // 5. 读取一页数据(通常2KB)
    for (int i = 0; i < PAGE_SIZE; i++) {
        buffer[i] = nand_read_byte();
    }
}

你看,NAND的读操作需要“命令-地址-等待-数据”四个步骤,比NOR麻烦多了。但NAND的页读取速度很快,一页2KB的数据,大约25μs就能读完。

2.5 磨损均衡与寿命管理

这是Flash开发里最容易被忽视,但也是最致命的问题。Flash的擦写次数是有限的,如果你总是往同一个地址写数据,那个块很快就报废了。

我遇到过最惨的一次:一个数据采集设备,每秒钟往Flash里写一次日志。用了三个月,Flash就坏了。后来一查,发现日志文件一直写在同一个块上,那个块被擦了100多万次,远超NAND的寿命极限。

磨损均衡(Wear Leveling)

说白了,就是把写操作均匀地分布到所有块上。有两种策略:

  • 动态磨损均衡:每次写数据时,选一个擦除次数最少的块来写。适合小数据量的频繁写入。
  • 静态磨损均衡:连那些长期不动的“冷数据”块,也定期搬走数据,把擦除机会让给它们。适合大容量存储。

坏块管理(Bad Block Management)

NAND出厂时就可能有坏块,而且使用过程中还会产生新的坏块。所以必须用坏块表来记录哪些块不能用。我习惯在系统初始化时扫描所有块,把坏块标记出来,以后读写都跳过它们。

寿命估算公式:

总写入量 = 容量 × 擦写次数 × 磨损均衡效率

举个例子:一块1GB的NAND,擦写次数1万次,磨损均衡效率90%。那么总写入量 = 1GB × 10000 × 0.9 = 9TB。如果你的系统每天写100MB,那这块Flash能用9TB / (100MB/天) ≈ 90,000天 ≈ 246年。嗯,实际上因为坏块和损耗,实际寿命会短一些,但足够用了。

我的建议:如果你的系统需要频繁写小数据(比如日志、配置参数),别直接操作Flash。用一个小型文件系统,比如LittleFS或SPIFFS,它们内置了磨损均衡和坏块管理。我自己在IoT项目里一直用LittleFS,省心很多。