2. Flash存储器详解:NAND与NOR Flash的结构差异与读写特性

Flash存储器,说白了就是咱们单片机系统里最常用的非易失性存储方案。你想想看,掉电不丢数据、可电擦除、可重复编程——这些特性让Flash几乎成了嵌入式系统的标配。但很多人一上来就踩坑:选NAND还是NOR?时序怎么配?功耗怎么控?

我刚开始做嵌入式那会儿,也在这上面栽过跟头。有一次项目急着赶进度,随手选了颗NOR Flash,结果代码量一上去,发现容量根本不够用。后来换NAND,又发现启动加载时序搞不定……嗯,今天咱们就把这两种Flash彻底讲透。

2.1 NOR Flash:随机访问的“内存型”存储

NOR Flash的结构,说白了就是每个存储单元都并联在位线上。这意味着什么?——随机访问。你可以像读SRAM一样,直接给地址,就能读到数据。这也是为什么NOR Flash常被用来存代码,支持XIP(就地执行)。

核心特点:

  • 随机读取快:读时序接近SRAM,适合代码直接运行
  • 写入慢:擦除和编程都需要较长时间(毫秒级)
  • 容量偏小:常见1Mb~256Mb,再大成本就上去了
  • 接口简单:并行或SPI,时序容易理解

我在一个工业控制项目里用过SPI NOR Flash存Bootloader。当时选它的原因很简单——上电就能读,不需要初始化复杂的控制器。你想想看,MCU一复位,直接从NOR Flash取指令执行,多省事。

2.2 NAND Flash:高密度的“块设备”存储

NAND Flash的结构就不一样了。它的存储单元是串联的,像一串链条。这种结构让NAND的存储密度极高,成本也低。但代价是什么?——不能随机访问。你必须按页读取,按块擦除。

我做过一个数据记录仪,每天要存几十MB的传感器数据。NOR Flash根本装不下,只能上NAND。但NAND有个麻烦事——坏块管理。出厂就有坏块,用着用着还会产生新坏块。你得自己维护坏块表,做ECC校验。

注意:NAND Flash的读取是页模式(通常2KB~16KB一页),不能像NOR那样按字节随机读。如果你的MCU不支持NAND控制器,用GPIO模拟时序会非常痛苦——我试过,调试了整整一周。

2.3 结构差异对比:一张表说清楚

对比项 NOR Flash NAND Flash
存储单元连接方式 并联 串联
读取方式 随机访问(字节/字) 页读取(2KB~16KB)
擦除方式 扇区擦除(通常64KB) 块擦除(通常128KB~512KB)
写入速度 慢(~几百μs/字节) 快(~几百μs/页)
读取速度 快(~100ns随机读) 较快(~25μs页读取)
密度/成本 低密度,高成本 高密度,低成本
可靠性 高(无坏块问题) 需坏块管理+ECC
典型应用 代码存储、XIP 大容量数据、文件系统

2.4 读写时序:实战中的关键差异

时序这东西,光看数据手册没用,得动手测。我习惯用逻辑分析仪抓波形,一眼就能看出问题。

NOR Flash SPI读时序

以W25Q系列为例,读操作很简单:发命令0x03,跟3字节地址,然后直接读数据。时钟频率可以跑到104MHz。但要注意——连续读模式下,地址会自动递增,这个特性在做代码搬运时特别好用。

// NOR Flash SPI 读操作示例(伪代码)
void nor_read(uint32_t addr, uint8_t *buf, uint32_t len) {
    CS_LOW();
    spi_send_byte(0x03);        // 读命令
    spi_send_byte(addr >> 16);  // 地址高8位
    spi_send_byte(addr >> 8);   // 地址中8位
    spi_send_byte(addr);        // 地址低8位
    for (uint32_t i = 0; i < len; i++) {
        buf[i] = spi_recv_byte();
    }
    CS_HIGH();
}

NAND Flash页读取时序

NAND的读时序就复杂多了。你得先发命令0x00,再发5字节地址(列地址+行地址),然后发命令0x30启动读。之后要等tR时间(通常25μs),再读数据。我踩过的坑是——忘了等tR,结果读出来的全是0xFF。

// NAND Flash 页读取示例(伪代码)
void nand_page_read(uint32_t row_addr, uint8_t *buf) {
    CLE_HIGH(); ALE_LOW();      // 命令周期
    IOx = 0x00;                 // 读命令1
    CLE_LOW(); ALE_HIGH();      // 地址周期
    IOx = 0x00;                 // 列地址低8位
    IOx = 0x00;                 // 列地址高8位
    IOx = row_addr & 0xFF;      // 行地址低8位
    IOx = (row_addr >> 8) & 0xFF;
    IOx = (row_addr >> 16) & 0xFF;
    CLE_HIGH(); ALE_LOW();      // 命令周期
    IOx = 0x30;                 // 读命令2
    // 等待tR(至少25μs)
    while (RB_PIN == 0);        // 等待就绪
    // 读取页数据(假设页大小2KB)
    for (int i = 0; i < 2048; i++) {
        buf[i] = IOx;
    }
}

经验之谈:NAND的RB引脚一定要用中断或硬件定时器检测,别用软件死等。我曾经在一个RTOS项目里用while轮询RB,结果高优先级任务一直被阻塞,系统直接卡死。

2.5 功耗优化:从选型到代码

功耗优化,说白了就是两件事:少读写、快读写。我总结了几条实战经验:

  • 选型阶段:关注待机电流和读写峰值电流。NOR Flash待机通常1~5μA,NAND会高一些(10~50μA)。
  • 降低时钟频率:不是所有场景都需要最高速。如果数据量不大,把SPI时钟降到10MHz,功耗能降30%。
  • 批量操作:NAND尽量用页写入代替单字节写入。一次写一页(2KB)和写一个字节,功耗差不多,但效率差1000倍。
  • 深度睡眠:很多Flash支持Deep Power-down模式,电流可以降到1μA以下。我习惯在系统空闲时主动进入。

功耗对比参考:

模式 NOR Flash (W25Q64) NAND Flash (MT29F2G)
待机 1~5 μA 10~50 μA
读操作 ~15 mA (104MHz) ~20 mA (页读取)
写操作 ~20 mA (页编程) ~25 mA (页编程)
深度睡眠 ~1 μA ~5 μA

2.6 知识结构图:NAND vs NOR 选型决策

下面这张图是我自己总结的选型逻辑。每次做新项目,我都会先过一遍这个流程。

NAND vs NOR 选型决策流程图 Flash选型决策流程 存储需求分析 是否需要XIP(就地执行)? 是 选NOR Flash 否 容量 > 256Mb? 或需要文件系统? 是 选NAND Flash 否 成本敏感? 是 否 选NOR Flash 选NAND Flash 说明:NOR适合代码存储、快速启动;NAND适合大容量数据、文件系统。 实际项目中,很多系统会同时使用NOR(存Boot)和NAND(存数据)。

2.7 避坑指南:我踩过的那些坑

坑1:NOR Flash的“伪”随机写

NOR虽然可以随机读,但写操作必须按页(通常256字节)进行。我曾经以为可以像SRAM一样单字节写,结果发现写之前必须先擦除整个扇区。嗯,那次调试花了我一整天。

坑2:NAND Flash的“坏块”陷阱

新买的NAND芯片,出厂就有坏块。我第一次用NAND时没做坏块管理,结果存到某个地址数据就丢了。后来老老实实加了坏块表和ECC校验,再也没出过问题。

坑3:时序余量不足

数据手册上的时序参数都是理想值。实际布线、温度变化、电压波动都会影响。我习惯在时序参数上加20%的余量。比如tR要求25μs,我就等30μs再读数据。多等几微秒,换来的是可靠性。