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 坏块管理策略
我常用的方法有两种:
- 跳过坏块(Skip):遇到坏块直接跳过,用下一个好块代替。简单粗暴,但会浪费空间。
- 坏块替换(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)就会轻松很多。
下一章咱们聊文件系统,到时候我会拿一个实际项目里的坏块表设计出来讲。敬请期待。