2026/7/31 5:12:38

深入解析NandFlash:从工作原理到嵌入式实战应用

深入解析NandFlash:从工作原理到嵌入式实战应用 1. 从一块“坏”掉的U盘说起为什么需要了解NandFlash几年前我手头一个用了很久的U盘突然“罢工”了。插上电脑系统能识别到盘符但容量显示异常拷贝文件时要么报错要么速度奇慢最后直接提示“需要格式化”。格式化后容量倒是恢复了但里面存了多年的资料全都没了。当时只觉得是U盘质量不行寿命到了。后来随着工作深入接触了越来越多的存储设备从手机、固态硬盘到各种嵌入式设备我发现一个共同点它们的心脏大多是一块叫做NandFlash的芯片。而我那个“坏”掉的U盘其根本原因很可能就是内部的NandFlash芯片达到了其物理寿命的某个临界点或者出现了无法通过内部机制纠正的坏块。这让我意识到无论是作为开发者选型存储方案还是作为普通用户理解手中设备的“脾气”对NandFlash有一个基础而系统的认识都至关重要。它不像我们熟悉的机械硬盘或内存DRAM其工作原理、使用限制和寿命管理都独树一帜。简单地把文件往里一存了事可能会遇到各种意想不到的问题比如数据突然丢失、写入速度断崖式下跌或者设备“早衰”。NandFlash这个看似藏在黑盒子里的技术实际上决定了我们每天接触的无数电子设备的存储可靠性、性能和成本。今天我就结合这些年的踩坑经验把NandFlash那点事儿掰开揉碎了讲清楚让你不仅知道它是什么更能明白它为什么这样工作以及在实际项目中如何与它“和平共处”。2. NandFlash的本质一种“非易失”且“不能覆盖”的存储介质要理解NandFlash首先要抓住它的两个核心特性非易失性和擦除前编程。这两个特性是后续所有技术细节和操作约束的根源。非易失性意味着断电后数据不会丢失。这一点和U盘、SD卡给我们的直观感受一致也是它广泛应用于外部存储的原因。其物理基础是浮栅晶体管。你可以把它想象成一个带有“水坝”的水库。晶体管的浮栅Floating Gate被绝缘层包围与外界隔绝。当我们写入数据编程时是通过高电压向浮栅注入电荷电子相当于往“水库”里蓄水。由于绝缘层的存在电荷在断电后也无法逃逸从而长期保存了“1”或“0”的状态具体是1还是0取决于架构。读取时通过检测浮栅是否有电荷以及电荷量多少来判断存储的数据。擦除前编程是NandFlash最特殊、也最容易让人困惑的一点。它指的是NandFlash的最小写入单位页Page在写入前必须处于“已擦除”状态通常所有位为‘1’。你不能像在内存RAM或硬盘的某个扇区那样直接修改某一位从0变成1。如果你想将某个已经写入数据的页中的某一位从0改为1唯一的办法是先将整个擦除块Block由多个页组成擦除使其所有位恢复为1然后再重新写入整个页的新数据。注意这里有个关键点擦除的最小单位是块Block而写入/读取的最小单位是页Page。一个块通常包含64、128或256个页。这种不对称性带来了巨大的管理开销。举个例子假设一个NandFlash芯片每页大小4KB每个块包含128个页。你想修改某个块中第10页的某个字节。你不能直接修改这个字节。你必须将整个块128页 * 4KB 512KB的数据读出来暂存到其他地方比如内存。在内存中修改第10页的那个字节。将这个块整体擦除耗时较长通常是毫秒级。将修改后的512KB数据按页重新写入这个块。这个过程被称为“写放大Write Amplification”。实际写入NandFlash物理介质的数据量远大于主机要求写入的逻辑数据量。写放大会加速NandFlash的磨损影响性能和寿命。因此所有基于NandFlash的系统如SSD、U盘控制器、嵌入式文件系统都必须引入一个重要的中间层闪存转换层FTL或损耗均衡Wear Leveling算法来智能地管理这些擦写操作尽量降低写放大并让所有存储单元均匀磨损。这是理解NandFlash应用的关键。3. NandFlash的两种主要架构SLC、MLC、TLC与QLC的演进根据每个存储单元Cell能够存储的比特bit数NandFlash主要分为以下几类这也是决定其成本、性能和寿命的核心因素SLCSingle-Level Cell每个存储单元只存储1个比特bit的数据即0或1。通过判断浮栅上有无电荷高于或低于某个参考电压来区分状态。因为只需要区分两种状态电压窗口宽容错性高。优点读写速度快功耗低擦写寿命长通常可达10万次以上。缺点成本最高存储密度最低。应用场景对可靠性、速度和寿命要求极高的工业、军事、企业级存储。MLCMulti-Level Cell每个存储单元存储2个比特00, 01, 10, 11。需要精确控制电荷量以区分4种不同的电压状态。优点成本低于SLC存储密度翻倍。缺点读写速度较慢功耗较高擦写寿命显著降低典型值在3000到10000次。应用场景消费级固态硬盘、高性能U盘、高端嵌入式设备的主流选择。TLCTriple-Level Cell每个存储单元存储3个比特8种状态。QLCQuad-Level Cell则存储4个比特16种状态。优点存储密度极大提升成本进一步降低使得大容量固态硬盘普及成为可能。缺点读写速度更慢尤其是写入寿命更短TLC典型值在500-1500次QLC可能只有100-300次对错误校正ECC的要求也急剧增加。应用场景主流消费级大容量SSD、存储卡、低成本消费电子产品。这里有一个非常重要的实操心得不要只看芯片型号一定要查阅其数据手册Datasheet中的“Endurance”参数它明确给出了块Block的典型擦写次数P/E Cycles。例如一个标称3000次P/E Cycles的MLC芯片并不意味着整个芯片只能写3000次而是指每个物理块可以承受3000次完整的擦写循环。优秀的FTL算法会让写入均匀分布到所有块上从而延长整体设备寿命。但对于没有FTL的原始NandFlash芯片在嵌入式系统中直接操作时你必须自己在软件层面实现或选择带有损耗均衡的文件系统如LittleFS、SPIFFS否则频繁写入同一区域会使其迅速报废。4. NandFlash的物理结构与访问接口理解了基本特性后我们来看看一块NandFlash芯片内部是如何组织的以及我们如何与它通信。4.1 内部物理结构从晶体管到阵列NandFlash的内部是一个巨大的存储单元阵列。这些单元以“串String”和“页Page”的形式组织。串String多个存储单元如32个或64个串联在一起共享位线Bitline。这是“NAND”与非门结构的由来因为其读取逻辑类似于与非门。页Page这是读写操作的基本单位。一个页包含一定数量的存储单元通常为4KB、8KB、16KB等另外还会附带一部分备用区Spare Area/OOB通常是页大小的1/32或1/16如4KB页对应128B OOB。OOB区域用于存放ECC校验码、坏块标记、文件系统元数据等关键信息。块Block多个页如64、128、256个组成一个块。块是擦除操作的最小单位。一个块的大小可能是256KB、512KB、1MB、2MB甚至4MB。平面Plane与逻辑单元LUN为了提升并行性和性能大容量NandFlash内部会划分为多个平面Plane每个平面可以独立执行某些操作如缓存读取。多个平面组成一个逻辑单元LUN是能独立执行命令的最小实体。4.2 外部访问接口引脚与命令原始NandFlash芯片通过并行或串行接口与主控制器如MCU、SoC连接。传统并行接口Async NAND正在被更高速、引脚更少的串行接口如SPI NAND取代。并行NandFlashAsync NAND常见引脚I/O[7:0]8位数据/命令/地址总线复用。CLE命令锁存使能。高电平时I/O上的数据被解释为命令。ALE地址锁存使能。高电平时I/O上的数据被解释为地址。CE#芯片使能低有效。RE#读使能低有效。WE#写使能低有效。R/B#就绪/忙信号低电平表示忙。这是关键信号任何编程、擦除或读取操作后芯片都需要时间处理此时R/B#为低主机必须等待其变高后才能发送下一条命令。基本操作序列以读取一页为例拉低CE#选中芯片。拉高CLE通过I/O口发送“读命令”如0x00。拉高ALE分多个周期发送要读取的页地址列地址和行地址。发送第二个读命令如0x30启动内部读取。等待R/B#信号从低变高表示数据已从存储阵列加载到页缓存。通过连续触发RE#时钟从I/O口依次读出该页的数据主数据区OOB区。SPI NAND则使用标准的SPI总线CLK, CS#, MOSI, MISO通过发送特定的指令帧来执行读、写、擦除等操作大大简化了硬件连接但需要主控支持其指令集。提示在嵌入式开发中直接驱动原始NandFlash最常踩的坑就是忽略R/B#等待。在发送编程0x80…0x10、擦除0x60…0xD0或读取命令后必须通过轮询R/B#引脚或读取状态寄存器Status Register的方式确认操作完成。盲目进行下一步操作会导致数据错误或命令失效。许多MCU的NandFlash控制器如FSMC/FMC会硬件处理这部分等待但若使用GPIO模拟时序则必须自己实现。5. NandFlash的“阿喀琉斯之踵”坏块管理与ECC纠错由于物理特性和制造工艺NandFlash出厂时就会存在一定比例的初始坏块Initial Bad Block。在使用过程中随着擦写次数的增加也会产生后天坏块Grown Bad Block。这是NandFlash的固有特性而非缺陷。一个合格的产品不是没有坏块而是能有效地管理坏块。5.1 坏块标记与识别坏块信息通常记录在OOB区域的特定位置。对于一块NandFlash其第一个页通常是每个块的第一个页或最后一个页的OOB区域会有固定的字节用于标记坏块。常见的标记方法是该OOB字节的非0xFF值如0x00表示此块为坏块。出厂坏块由制造商在出厂测试后标记。后天坏块在擦除或编程操作失败后由系统软件或控制器进行标记。在嵌入式系统中直接使用原始NandFlash你必须在上电初始化时执行坏块扫描Bad Block Scan。流程大致如下遍历所有块Block。读取每个块中用于标记坏块的特定页如Block 0的Page 0的OOB区域。检查标记字节。如果发现坏块标记就将该块的逻辑地址加入一个“坏块表BBT: Bad Block Table”中。后续所有读写擦操作都需要查询BBT跳过这些坏块。5.2 ECC纠错数据的“保险丝”随着存储密度提高MLC-TLC-QLC存储单元间的电荷干扰读干扰、写干扰、保持干扰加剧以及芯片老化位翻转Bit Error的概率显著上升。可能读出来的某一位是0而当初存进去的是1。这就需要强大的错误检查和纠正ECC机制。ECC原理在写入数据时根据数据内容计算出一组校验码如汉明码、BCH码、LDPC码并将其存入OOB区域。读取时用同样的算法根据读出的数据计算校验码并与OOB中存储的原始校验码比较。如果发现错误则尝试纠正。纠错能力不同的ECC算法纠错能力不同。早期SLC可能只需要1-bit ECC汉明码而TLC/QLC可能需要能纠正40-bit甚至80-bit错误的强大LDPC码。硬件与软件现代SoC或专用的NandFlash控制器通常内置硬件ECC引擎性能高且不占用CPU。若MCU没有硬件支持则需要在软件中实现但这会严重影响性能仅适用于小数据量或低速场景。一个血泪教训我曾经在一个使用MLC NandFlash的项目中为了节省OOB空间使用了较弱的ECC算法。在设备运行一段时间后随机出现了零星的数据损坏。排查到最后发现是某些页的位错误超出了ECC的纠错能力导致数据无法恢复。务必根据芯片数据手册推荐的ECC强度来配置你的系统并预留足够的OOB空间。对于TLC/QLC强烈建议使用带硬件LDPC引擎的主控。6. 嵌入式系统中的NandFlash实战文件系统选型与直接操作在资源受限的嵌入式系统中你可能需要直接面对原始NandFlash芯片编程而不是使用带有完整FTL的eMMC或SD卡。这里有两个主要方向使用专为NandFlash设计的文件系统或者在特定区域进行原始读写。6.1 文件系统选型YAFFS2, SPIFFS, LittleFS对于需要存储大量文件且结构复杂的应用一个带有损耗均衡和坏块管理的文件系统是必不可少的。YAFFS2 (Yet Another Flash File System 2)特点专为NandFlash设计日志结构文件系统。直接操作OOB区域进行坏块标记和ECC对NandFlash特性支持最原生。优点性能好可靠性高特别适合大容量NandFlash。缺点代码相对复杂内存占用较大需要维护块状态和文件结构在RAM中许可证可能需要注意GPL。适用场景Linux等嵌入式操作系统下的NandFlash存储。SPIFFS (SPI Flash File System)特点为SPI NOR Flash设计但也可用于SPI NAND。它不是日志系统而是类似FAT的链表结构。优点极其轻量RAM和ROM占用非常小适用于MCU无RTOS环境。缺点没有真正的损耗均衡长时间使用可能磨损不均掉电安全性一般随着文件系统变满性能下降明显。适用场景资源极度紧张存储量不大几MB到几十MB且写入不频繁的SPI NAND场景。LittleFS特点由ARM mbed团队开发专为小型嵌入式系统设计。采用写时复制Copy-on-Write和日志结构具有强大的掉电安全性和动态损耗均衡。优点掉电安全是最大亮点损耗均衡算法优秀目录支持好内存占用可预测。缺点相对于SPIFFS代码量和RAM占用稍大。适用场景目前嵌入式MCU项目首推的文件系统。无论是SPI NAND还是并行NAND只要有对应的底层驱动LittleFS都能提供良好的可靠性和性能。它已集成到许多RT-Thread、FreeRTOS的组件中。选型建议对于新的嵌入式项目如果资源不是极端紧张优先考虑LittleFS。它的掉电安全特性可以避免很多因意外断电导致文件系统损坏的灾难性问题。如果只有几KB的RAM和Flash且存储需求很简单再考虑SPIFFS。6.2 原始分区操作Bootloader与参数存储在很多情况下我们并不需要完整的文件系统。例如Bootloader存储将Bootloader代码直接烧录到NandFlash的前几个块。固件存储将应用程序固件镜像存储在一个固定的、连续的区域。参数区存储设备序列号、校准参数、运行日志等。这时我们可以将NandFlash划分为几个固定的逻辑分区每个分区由一系列连续的好块组成。我们需要实现一个简单的分区管理器和坏块跳过逻辑。操作步骤示例写入一个固件镜像到固定分区初始化与坏块表建立系统启动扫描全片建立BBT。地址映射定义分区起始逻辑块地址LBA。例如固件分区从LBA 0开始。写入操作 a. 从LBA 0对应的物理块开始。 b. 检查BBT如果当前物理块是坏块则LBA不变物理块地址跳到下一个好块。这就是坏块跳过Bad Block Skip的核心逻辑地址连续物理地址可能因坏块而“跳跃”。 c. 擦除这个好块。 d. 按页顺序写入数据。每写完一页计算ECC并写入该页的OOB区域。 e. 一个块写满后逻辑块地址LBA加1寻找下一个好块继续写入。读取操作读取时也需要根据同样的逻辑地址到物理地址的映射关系通常需要保存一个简单的映射表找到正确的物理块和页进行读取并使用OOB中的ECC校验数据。关键技巧对于参数存储这类需要频繁更新少量数据的情况切忌原地更新。应采用“日志式”或“双备份”策略。例如预留多个页作为一个循环队列。每次更新参数时写到下一个干净的页并标记旧页无效。这样可以避免因频繁擦写同一个块而使其过早损坏。LittleFS内部就是采用了类似的策略。7. 性能优化与寿命延长实战指南了解了基本原理和操作后如何用好NandFlash让它更快、更持久以下是一些实战经验。7.1 提升读写性能启用缓存编程Cache Program许多NandFlash支持Cache Read/Write命令。在写入时可以先将数据加载到芯片内部的页缓存然后主机可以立即开始准备下一笔数据而无需等待实际的编程操作完成实现了流水线操作。务必仔细阅读数据手册的时序图正确处理缓存命令和R/B#信号的关系。多平面操作Multi-Plane Operation对于支持多平面的芯片可以对两个或多个平面并发执行相同的操作如擦除、编程从而几乎成倍提升吞吐量。这需要主控驱动和文件系统的支持。交错操作Interleave当使用多片NandFlash芯片时可以在芯片间交错访问隐藏单个芯片的延迟。DMA传输使用MCU的DMA控制器来搬运NandFlash的数据解放CPU。7.2 延长芯片寿命实现高效的损耗均衡Wear Leveling这是延长寿命最关键的一环。确保写操作均匀分布到所有物理块上。动态损耗均衡在写入时选择擦写次数最少的块效果比静态损耗均衡定期移动冷数据更好。LittleFS等现代文件系统已内置了不错的算法。预留空间Over-Provisioning不要在NandFlash上塞满数据。例如一块标称128GB的芯片实际只给用户120GB的可用空间剩下的8GB约6.25%就是OP空间。这部分空间不暴露给用户但FTL可以用它来进行垃圾回收GC、坏块替换和磨损均衡大幅改善性能和寿命。在自研系统中也应考虑保留一部分物理空间不参与逻辑地址映射。启用TRIM或Discard命令在文件系统中删除文件时及时通知底层FTL哪些逻辑块的数据已经无效FTL可以在后台垃圾回收时直接跳过这些块减少不必要的数据搬运降低写放大。这在Linux MTD层或一些高级文件系统中支持。控制工作温度高温会加速NandFlash中电荷的泄漏导致数据保持能力下降并可能增加位错误率。保持良好的散热。7.3 数据安全与可靠性定期刷新Data Refresh/Scrubbing对于长期不通电存储的冷数据由于电荷泄漏可能随时间产生位错误。可以定期如每三个月或半年读取这些数据利用ECC进行纠错然后将纠正后的数据写回写到新的位置并触发损耗均衡。这对于档案存储非常重要。RAID-like 策略在极端可靠性的应用中可以在软件层面实现类似RAID 1镜像或RAID 5奇偶校验的机制将数据分散存储在多个NandFlash芯片上即使单个芯片完全失效数据也能恢复。选择高质量的电源不稳定的电源电压尤其是在编程和擦除所需的高压阶段可能导致写入失败或甚至损坏存储单元。NandFlash是现代数字世界的基石之一它的特性决定了我们使用存储设备的方式。从原理上理解它的“脾气”在实践上尊重它的“规则”如擦除前写、坏块管理、ECC保护我们就能构建出既高效又可靠的数据存储系统。无论是选择一颗合适的芯片还是编写一段稳健的驱动亦或是调优一个文件系统这些基础知识都将是你最有力的工具。下次当你手中的设备存储文件时或许能对背后那片小小的、忙碌的NandFlash芯片多一份理解与敬意。