##

2. Flash芯片内部结构:存储阵列、页缓冲区、状态寄存器、命令集概述

好,咱们先把 Flash 芯片的「内脏」扒开看看。你写驱动也好,搞多片级联也好,不清楚内部长啥样,调起通来就是瞎子摸象。我自己刚入行那会儿就吃过这亏——照着 datasheet 一顿写操作,结果读回来的数据全是乱的,后来才发现是状态寄存器判断时机搞错了。

所以这一节,我带你梳理四个核心模块:存储阵列、页缓冲区、状态寄存器和命令集。搞懂这些,后面你做级联时序分析和多片选通设计就有了底。

2.1 存储阵列——真正存数据的地方

Flash 的存储单元就是一个个 MOS 管,带浮栅层。咱们常用的 Nor Flash 和 NAND Flash 在阵列结构上差别很大,但咱们课程重点讲 QSPI Nor Flash,所以后面的讨论都基于 Nor 架构。

  • 扇区(Sector):最小的擦除单位。一般 4KB 或 64KB。我记得有一次项目里用了 4KB 扇区的芯片,擦除一个扇区大约 40ms,写数据前必须保证扇区是干净的。
  • 页(Page):最小的写入单位。通常是 256 字节或 512 字节。我建议你仔细查芯片手册,有些型号的页大小会影响页缓冲区设计。
  • 块(Block):多个扇区组成块,某些擦除命令可以按块操作。

你想想看,存储阵列就像一个巨大的棋盘,每个格子里存 1 bit。读出时通过字线选中行,位线读出电流大小判断是 0 还是 1。Nor Flash 能字节随机读取,这点比 NAND 方便很多,适合代码就地执行(XIP)。

核心要点:多片级联时,每片的存储阵列是完全独立的。擦除和写入操作只影响本芯片,不会串扰到其他片。但读操作会通过共享的 QSPI 总线同时发命令,所以每片必须靠片选 CS# 区分。

2.2 页缓冲区——写入前的临时缓冲区

写 Flash 不是直接往阵列里怼数据。芯片内部有一个 SRAM 缓冲区,大小一般等于一页。比如页大小 256 字节,页缓冲区也是 256 字节。

  1. 先通过 QSPI 总线把要写的数据灌进页缓冲区。
  2. 然后发「页编程」命令,芯片会把缓冲区内容一次性写入阵列。
  3. 写入期间芯片的 BUSY 位会拉高,你只能等它完事。

说白了,页缓冲区就是仓库门口的「暂存区」。我曾在调试多片级联时遇到一个问题:两片 Flash 共用 QSPI 总线,往第一片写数据时不小心把第二片的 CS# 也拉低了,结果两个页缓冲区同时接收数据,最后数据全乱套了。嗯,这里要注意:每个芯片的页缓冲区是独立的,但写命令地址必须严格对应本片。

设计建议:页缓冲区大小决定了你一次能写入的最大字节数。如果数据超过一页,必须分多次写,每次之间都要检查状态寄存器。你可以在驱动里封装一个 flash_page_write() 函数,自动处理缓冲区大小限制。

2.3 状态寄存器——芯片的健康名片

状态寄存器通常是一个 8 位或 16 位的只读寄存器(部分芯片可写个别位)。我习惯先把它的位定义画成一张表贴在工作台上,调试时一眼就能看出问题。

位序号名称功能描述
bit 0BUSY1 = 芯片正忙(擦除/写入进行中),0 = 空闲
bit 1WEL写使能锁存,1 = 允许写入,0 = 禁止
bit 2BP0块保护位,与 BP1、BP2 配合设置保护区域
bit 3BP1同上
bit 4BP2同上
bit 5TB顶部/底部保护选择
bit 6SEC扇区保护指示
bit 7SRP0状态寄存器保护位,写保护状态寄存器自身

为什么要关注状态寄存器?因为多片级联时,每片的工作状态可能不同。你发完一个 Page Program 命令,如果不检查 BUSY 位直接发下一个命令,可能会覆盖前一次操作。我曾经做一个升级固件的项目,就是没等第一片写完就去操作第二片,结果第一片的数据校验老出错,排查了半天才发现是状态寄存器轮询没做好。

重要提醒:读状态寄存器命令(0x05)在所有芯片上都相同,但每片 CS# 必须独立选通。也就是说,你得分别拉低每片的 CS# 来读自己的状态,不能一次读所有片。级联设计中,如果用了多片共享 CS# 的菊花链方式,那状态寄存器就变成「串行回环」读取了,后文会详细讲。

2.4 命令集——和芯片对话的指令手册

每个 QSPI Flash 芯片都有一张命令表。常用的命令就那么十几个。我建议你初学时先背熟这 5 个核心命令:

  1. 读数据(0x03):单线 SPI 模式,速度较慢。
  2. 快速读(0x0B):带 dummy 周期,可切换到四线模式。
  3. 写使能(0x06):任何写操作前必须先发此命令,把 WEL 位置 1。
  4. 页编程(0x02):把缓冲区数据写入指定地址。
  5. 扇区擦除(0x20 或 0xD8):擦除一个扇区或块。

一个小技巧:我习惯把命令码做成 #define 宏,像这样:

#define CMD_READ        0x03
#define CMD_FAST_READ   0x0B
#define CMD_WRITE_ENABLE 0x06
#define CMD_PAGE_PROGRAM 0x02
#define CMD_SECTOR_ERASE 0x20
#define CMD_RDSR         0x05

这样代码可读性高,也方便适配不同厂商的芯片——毕竟命令码差异很大,比如 Winbond 和 Micron 的擦除命令就有点出入。

多片级联时,命令发送和响应接收有一个关键差异:地址域需要包含片选信息吗?答案是:分情况。如果采用多片独立 CS# 方式,地址就是每个芯片内部的偏移地址;如果采用菊花链(daisy chain)方式,地址需要加上高位标志位来区分目标芯片。具体怎么处理,我们到后面「级联寻址」章节再展开。

2.5 本章知识结构图

下面这张 SVG 图把刚才讲的四个模块串起来了,你一眼就能看出它们之间的关系:

Flash 芯片内部核心模块 存储阵列 扇区 / 页 / 块 页缓冲区 (SRAM) 状态寄存器 命令集 QSPI 总线接口 CS# 片选 注意:多片级联时,每片拥有独立的存储阵列、页缓冲区和状态寄存器,命令集相同但 CS# 必须独立管理。

这张图你应该能看出来:存储阵列是核心,页缓冲区是写入缓存,状态寄存器反馈工作状态,命令集则是操作指令。四者通过内部总线连接,并与外部的 QSPI 接口和 CS# 片选信号交互。级联设计里,每一片芯片都是这样的独立实体,只是总线被多片共享了。

2.6 小结一下