第3章:NAND Flash操作原理:读、写、擦的基本流程与时序

好,咱们今天聊聊NAND Flash最核心的三个操作——读、写(编程)、擦除。这三个操作,说白了就是NAND Flash的命根子。你搞懂了它们,后面坏块管理、ECC纠错什么的,理解起来就顺了。

我记得刚入行那会儿,第一次看NAND Flash的datasheet,满眼都是时序图,看得我头皮发麻。后来做项目踩了几次坑,才真正明白这些时序参数背后意味着什么。今天我就把这些经验掰开揉碎了讲给你听。

3.1 读操作:从存储单元中取数据

读操作,就是把数据从存储单元里取出来。听起来简单,但内部流程其实挺讲究的。

3.1.1 读操作的基本流程

读操作大致分三步走:

  1. 发送命令和地址:先告诉芯片“我要读数据”,再告诉它“从哪个地址读”。
  2. 等待数据就绪:芯片内部开始干活,把数据从存储阵列搬到页寄存器里。这段时间叫tR(Read Latency)。
  3. 读取数据:数据准备好了,你从页寄存器里把数据取出来。

我习惯把读操作比作去图书馆借书。你告诉管理员书名(命令),再告诉他书架位置(地址),管理员去书架上找(tR时间),找到后把书递给你(数据输出)。

3.1.2 读操作的命令序列

以常见的NAND Flash为例,读一页数据的命令序列是这样的:

// 读一页数据(典型命令序列)
// 假设页大小是2KB,列地址用2个字节,行地址用3个字节

CLE = 1, CE = 0, WE = 0  // 进入命令模式
发送命令 0x00             // 读操作起始命令

CLE = 0, ALE = 1         // 进入地址模式
发送列地址低字节 (CA0-CA7)
发送列地址高字节 (CA8-CA15)
发送行地址低字节 (RA0-RA7)
发送行地址中字节 (RA8-RA15)
发送行地址高字节 (RA16-RA23)

CLE = 1, ALE = 0         // 再次进入命令模式
发送命令 0x30             // 读操作确认命令

// 等待 tR 时间(通常 25-50μs)
// 检查 R/B# 引脚,等待其变高

// 数据输出阶段
CLE = 0, ALE = 0, RE = 0 // 进入数据读取模式
循环读取数据,每次RE下降沿输出一个字节

嗯,这里要注意:不同厂商、不同型号的NAND Flash,命令码可能不一样。比如有些芯片用0x00/0x30,有些用0x05/0xE0。做项目时一定要先看datasheet,别想当然。

3.1.3 读操作的时序参数

读操作有几个关键时序参数,我列个表给你看:

参数 说明 典型值 我的经验
tR 读操作延迟,从命令发出到数据就绪 25-50μs 这个时间跟工艺有关,越新的工艺tR越短
tRC 读周期时间,两次连续读操作的最小间隔 20-30ns 别卡太紧,留点余量,我吃过亏
tREA RE#下降沿到数据输出的延迟 15-20ns 这个参数决定了你的读速度上限
tRP RE#脉冲宽度 10-15ns 太短了数据不稳定,太长了影响速度
我的小技巧:实际项目中,我一般把tRC设成datasheet推荐值的1.2倍。为什么?因为PCB走线、温度变化都会影响时序。留点余量,系统更稳定。

3.2 写操作(编程):把数据写入存储单元

写操作,也叫编程操作。跟读操作不一样,写操作只能把1变成0,不能把0变成1。想恢复成1?那得靠擦除操作。

3.2.1 写操作的基本流程

写操作分四步:

  1. 发送命令和地址:告诉芯片“我要写数据”和“写到哪里”。
  2. 写入数据:把要写的数据一字节一字节地送进页寄存器。
  3. 发送编程确认命令:告诉芯片“数据准备好了,开始写吧”。
  4. 等待编程完成:芯片内部开始编程,这段时间叫tPROG(Program Latency)。

你想想看,写操作比读操作多了一步——你得先把数据送进去,再让芯片去写。这就像你写作业,得先想好答案(数据),再写到本子上(存储单元)。

3.2.2 写操作的命令序列

// 写一页数据(典型命令序列)

CLE = 1, CE = 0, WE = 0  // 进入命令模式
发送命令 0x80             // 写操作起始命令

CLE = 0, ALE = 1         // 进入地址模式
发送列地址低字节
发送列地址高字节
发送行地址低字节
发送行地址中字节
发送行地址高字节

CLE = 0, ALE = 0         // 进入数据输入模式
循环写入数据,每次WE上升沿锁存一个字节

CLE = 1, ALE = 0         // 再次进入命令模式
发送命令 0x10             // 写操作确认命令

// 等待 tPROG 时间(通常 200-800μs)
// 检查 R/B# 引脚,等待其变高

// 检查状态寄存器,确认编程是否成功
发送命令 0x70
读取状态寄存器
检查 bit6(编程成功标志位)
注意:写操作完成后,一定要检查状态寄存器!我曾经在一个项目中偷懒没检查,结果数据写进去一半就失败了,排查了两天才找到原因。从那以后,我每次写操作后必查状态寄存器。

3.2.3 写操作的时序参数

参数 说明 典型值 我的经验
tPROG 编程时间,从确认命令到编程完成 200-800μs 这个时间跟页大小和工艺有关,MLC比SLC慢很多
tWC 写周期时间,两次连续写操作的最小间隔 20-30ns 跟tRC类似,别卡太紧
tWP WE#脉冲宽度 10-15ns 太窄了数据可能锁存不上
tADL 地址到数据输入的延迟 70-100ns 地址发完后,别急着发数据,等一会儿

3.3 擦除操作:把存储单元恢复成1

擦除操作,就是把存储单元里的数据全部变成1。注意,擦除是按块(Block)进行的,不是按页。一个块通常包含64页或128页。

3.3.1 擦除操作的基本流程

擦除操作分三步:

  1. 发送擦除命令和块地址:告诉芯片“我要擦除哪个块”。
  2. 发送擦除确认命令:确认你要擦除。
  3. 等待擦除完成:芯片内部开始擦除,这段时间叫tBERS(Block Erase Latency)。

说白了,擦除操作就是给整个块“格式化”。我经常跟团队说,擦除操作是NAND Flash里最耗时的操作,能少擦就少擦。

3.3.2 擦除操作的命令序列

// 擦除一个块(典型命令序列)

CLE = 1, CE = 0, WE = 0  // 进入命令模式
发送命令 0x60             // 擦除操作起始命令

CLE = 0, ALE = 1         // 进入地址模式
发送行地址低字节 (RA0-RA7)
发送行地址中字节 (RA8-RA15)
发送行地址高字节 (RA16-RA23)
// 注意:擦除只需要行地址,不需要列地址

CLE = 1, ALE = 0         // 再次进入命令模式
发送命令 0xD0             // 擦除操作确认命令

// 等待 tBERS 时间(通常 1-3ms)
// 检查 R/B# 引脚,等待其变高

// 检查状态寄存器,确认擦除是否成功
发送命令 0x70
读取状态寄存器
检查 bit6(擦除成功标志位)
关键点:擦除操作只接受行地址,不接受列地址。因为擦除的最小单位是块,不是页。你给列地址也没用,芯片会忽略它。

3.3.3 擦除操作的时序参数

参数 说明 典型值 我的经验
tBERS 块擦除时间 1-3ms 这是三个操作里最慢的,所以尽量少擦
tWB 写缓冲时间,命令发出到R/B#变低 50-100ns 命令发完后,别急着查状态,等一会儿

3.4 三个操作的对比与避坑指南

我把三个操作放在一起对比一下,方便你理解:

操作 最小单位 典型耗时 数据变化 注意事项
25-50μs 不变 注意tR时间,别提前读数据
写(编程) 200-800μs 1→0 必须检查状态寄存器
擦除 1-3ms 全部变1 只能按块擦除,不能按页
避坑指南:我曾经在一个项目中,为了追求速度,把写操作的tPROG时间设得太短。结果数据写进去后,读出来全是错的。后来排查发现,是芯片还没完成编程,我就开始读数据了。记住:时序参数不能随便改,尤其是等待时间

另外,我建议你在做驱动时,把这三个操作封装成独立的函数。比如:

// 伪代码示例
int nand_read_page(uint32_t page_addr, uint8_t *buffer);
int nand_write_page(uint32_t page_addr, uint8_t *buffer);
int nand_erase_block(uint32_t block_addr);

这样上层代码调用起来就清爽多了。而且每个函数里都加上超时处理,防止芯片卡死。嗯,这个习惯我保持了很多年,帮我省了不少调试时间。

好了,这一章的内容就到这里。读、写、擦这三个操作,是NAND Flash的基石。你把这章吃透了,后面讲坏块管理、磨损均衡什么的,就轻松多了。