2. NAND Flash原理:SLC/MLC/TLC区别、Page/Block/Plane结构、坏块管理

好,咱们进入正题。NAND Flash 这东西,做嵌入式的人躲不开。你想想看,不管是手机、平板,还是咱们做的 PDA,存储核心基本都是它。我最早接触 NAND 是在一个工业级数据采集器项目上,当时选型没经验,吃了不少苦头。今天我把这些坑和经验一并倒给你。

2.1 SLC / MLC / TLC:到底差在哪?

说白了,这三种类型就是「一个存储单元能存几个 bit」的区别。

  • SLC(Single-Level Cell):一个单元存 1 bit。只有 0 和 1 两种状态。
  • MLC(Multi-Level Cell):一个单元存 2 bit。有 00、01、10、11 四种电压状态。
  • TLC(Triple-Level Cell):一个单元存 3 bit。有 8 种电压状态。

你可能会问:「那是不是 TLC 容量最大?」没错,同样大小的晶圆,TLC 容量是 SLC 的三倍。但代价呢?我列个表给你看。

类型 每单元 bit 数 擦写寿命(P/E Cycles) 读取速度 写入速度 典型应用
SLC 1 约 10 万次 最快 最快 工业级、军工、高可靠性
MLC 2 约 3000 - 10000 次 较快 较快 消费级 SSD、高端 U 盘
TLC 3 约 500 - 3000 次 较慢 较慢 大容量存储、普通 U 盘

核心结论:SLC 寿命最长、速度最快,但容量最小、成本最高。TLC 正好反过来。MLC 在中间。

我在一个车载项目里用过 MLC,结果三年后批量返修。后来换成 SLC,问题全没了。嗯,这里要注意:如果你的设备要写日志、频繁掉电,千万别省那点钱,老老实实上 SLC。

2.2 Page / Block / Plane:NAND 的三层结构

NAND 的物理结构,你得理解成「俄罗斯套娃」。从大到小是:Plane → Block → Page

2.2.1 Page(页)

Page 是 NAND 读写的最小单位。常见大小是 2KB、4KB、8KB,甚至 16KB。你读一个字节,也得读一整个 Page 出来。写也一样,只能按 Page 写。

个人经验:我习惯把 Page 想象成「一张纸」。你可以在上面写字(写操作),但想改其中一个字?不行,得把整张纸撕掉重写(擦除)。

2.2.2 Block(块)

Block 是擦除的最小单位。一个 Block 包含多个 Page,通常是 64 个或 128 个。比如一个 128KB 的 Block,里面可能有 64 个 2KB 的 Page。

为什么擦除不能按 Page 来?这是 NAND 的物理特性决定的。你想想看,擦除需要施加高电压,如果按 Page 擦,相邻单元会受影响。所以只能整块擦。

2.2.3 Plane(平面)

Plane 是多个 Block 的集合。一个 Die(芯片)通常有 2 个或 4 个 Plane。Plane 的作用是支持并行操作。比如你可以同时从 Plane 0 和 Plane 1 各读一个 Page,速度翻倍。

避坑指南:我曾经在一个项目里忽略了 Plane 的概念,结果读写速度死活上不去。后来发现是没利用好 Plane 并行。记住:多 Plane 操作是提升性能的关键

2.3 坏块管理:NAND 的「天生缺陷」

NAND Flash 出厂就有坏块,这不是质量问题,是工艺决定的。你想想看,几亿个存储单元,总有几个不合格的。厂家会标记出来,但咱们软件得自己处理。

2.3.1 坏块的分类

  • 出厂坏块(Initial Bad Block):芯片出厂时就有的,厂家会在 Block 的第一个 Page 的 spare area 标记为 0x00。
  • 使用中产生的坏块(Grown Bad Block):擦写次数多了,某个 Block 就废了。这是咱们软件要重点处理的。

2.3.2 坏块管理策略

我常用的方法有两种:

  1. 跳过坏块(Skip):遇到坏块直接跳过,用下一个好块代替。简单粗暴,但会浪费空间。
  2. 坏块替换(BBT - Bad Block Table):维护一张表,把坏块映射到预留的好块上。复杂一些,但空间利用率高。

注意:千万别在坏块上做擦除或写入操作!轻则数据错乱,重则整片芯片报废。我见过有人没做坏块检查,结果把整个文件系统写崩了。

2.3.3 代码示例:坏块检测

下面是我在一个项目里用的坏块检测函数,简化版:

// 检查 Block 是否为坏块
// 返回 1 表示坏块,0 表示好块
int is_bad_block(uint32_t block_addr) {
    uint8_t spare_buf[64];
    // 读取 spare area 的第一个字节
    nand_read_spare(block_addr, 0, spare_buf, 1);
    // 出厂坏块标记为 0x00
    if (spare_buf[0] == 0x00) {
        return 1;  // 坏块
    }
    return 0;  // 好块
}

嗯,这里要注意:不同厂家的坏块标记位置可能不同。有的在 spare area 的第一个字节,有的在第六个字节。一定要看 datasheet。

2.4 总结一下

今天咱们聊了三个核心点:

  • SLC/MLC/TLC:选型时看寿命和速度,别只看容量。
  • Page/Block/Plane:读写按 Page,擦除按 Block,加速靠 Plane。
  • 坏块管理:出厂坏块要跳过,使用中坏块要替换。

说实话,NAND 的水很深。我做了十年嵌入式,每次换新芯片还得重新看 datasheet。但只要你把今天这些基础打牢了,后面学文件系统(比如 YAFFS、UBIFS)就会轻松很多。

下一章咱们聊文件系统,到时候我会拿一个实际项目里的坏块表设计出来讲。敬请期待。