4. SPI Flash指令集

SPI Flash 的所有操作,本质上都是“主机先发一条指令,再根据指令决定后续是收数据还是发数据”。指令集就是驱动与芯片之间的“语言”。如果只记住 HAL 库函数而不知道底层发了什么命令,一旦遇到 ID 读不到、写不进、擦除失败,就很难定位问题。本节把常用命令按功能分组,结合时序与状态机讲清楚每条命令的作用、参数和典型返回值。

一、指令的基本结构:一字节命令 + 地址 + 数据

绝大多数 SPI Flash 命令的第一个字节是操作码(Opcode),例如 0x9F 表示读 JEDEC ID,0x03 表示读数据,0x02 表示页编程。命令之后是否跟地址、跟几个字节地址、是否跟数据,由命令本身决定。常见格式如下:

读ID:     0x9F  + 接收 3 字节 (Manufacturer + Memory Type + Capacity)
读状态:   0x05  + 接收 1 字节状态寄存器
读数据:   0x03  + 3字节地址 + 接收 N 字节
页编程:   0x02  + 3字节地址 + 发送 N 字节 (N ≤ 256)
扇区擦除: 0x20  + 3字节地址
块擦除:   0xD8  + 3字节地址
写使能:   0x06  (无参数)
写禁止:   0x04  (无参数)

理解这个结构后,任何一条新命令都可以用“操作码 + 地址长度 + 数据方向”三个维度去查手册,而不需要死记硬背。

二、识别类命令:读 ID 与读状态

读 JEDEC ID(0x9F)是上电后第一条应该执行的命令。它返回 3 个字节:厂商 ID、存储类型、容量代码。例如 Winbond W25Q64 返回 EF 40 17,其中 0x17 表示 8MB(64Mbit)。驱动初始化时读取并校验这 3 个字节,可以确认 SPI 模式、引脚连接和时钟极性配置是否正确。

读状态寄存器(0x05)返回 1 个字节,其中最关键的是 bit0(WIP,Write In Progress)和 bit1(WEL,Write Enable Latch)。WIP=1 表示芯片正在执行擦除或编程,此时除读状态外不接受其他命令;WEL=1 表示写使能已置位,允许执行编程或擦除。驱动中所有写操作前都要检查 WEL,写操作后都要轮询 WIP 直到为 0。

读状态命令可以连续发送,芯片会在每个时钟周期返回当前状态字节,因此轮询时无需反复拉低片选,效率更高。

三、数据读写类命令:读数据与页编程

读数据(0x03)是最常用的命令:发送 0x03 和 3 字节起始地址后,芯片从该地址开始连续输出数据,地址自动递增,可以一直读到片选拉高为止。它支持任意地址、任意长度,没有对齐限制,因此适合随机读取和小块读取。

页编程(0x02)用于写入数据。Flash 的写入单位是“页”,典型页大小为 256 字节。命令格式为 0x02 + 3 字节地址 + N 字节数据(N ≤ 256)。关键限制是:一次页编程不能跨页。如果起始地址是 0x0000FF,最多只能写 1 字节到页尾,再写就会回卷到本页开头,覆盖之前的数据。因此驱动中必须实现“跨页拆分”逻辑。

// 页编程前必须写使能
spi_flash_write_enable();
// 发送 0x02 + 地址 + 数据
spi_flash_page_program(addr, buf, len);
// 轮询 WIP 直到完成
while (spi_flash_read_status() & 0x01);

四、擦除类命令:扇区擦除与块擦除

Flash 的物理特性是“只能把 1 写成 0,不能把 0 写成 1”。因此写入前必须先擦除,把目标区域恢复为 0xFF。擦除的最小单位是扇区(Sector),典型 4KB,命令 0x20;更大的单位是块(Block),常见 32KB(0x52)和 64KB(0xD8)。擦除命令只需要操作码 + 3 字节地址,不需要数据。

命令操作码作用典型耗时
写使能0x06置位 WEL,允许写/擦立即
写禁止0x04清除 WEL,禁止写/擦立即
读状态0x05读取 WIP/WEL 等状态立即
读 ID0x9F读取厂商与容量信息立即
读数据0x03任意地址连续读取随长度
页编程0x02写入一页(≤256B)0.7~3ms
扇区擦除0x20擦除 4KB45~400ms
块擦除0xD8擦除 64KB150~2000ms

五、写使能与写禁止:所有写操作的前置条件

写使能(0x06)和写禁止(0x04)是两条“无参数、无返回”的命令,但它们决定了芯片是否接受后续的编程和擦除命令。芯片内部有一个 WEL 位,上电默认为 0。每次执行页编程、扇区擦除或块擦除之前,都必须先发送 0x06 把 WEL 置 1;命令执行完成后,WEL 会自动清零。这意味着每写一页、每擦一个扇区,都要重新发一次写使能,不能“一次使能、多次写入”。

常见误区:连续写多页时只在开头发一次 0x06。实际上第一页写完后 WEL 已自动清零,第二页编程会被芯片忽略,表现为“只有第一页写成功”。正确做法是每次页编程前都调用写使能。

六、完整操作流程与状态机

把上述命令串起来,一次典型的“写入并回读”流程如下:读 ID 确认芯片 → 读状态确认空闲 → 写使能 → 扇区擦除 → 轮询 WIP → 写使能 → 页编程 → 轮询 WIP → 读数据校验。整个过程围绕 WEL 和 WIP 两个状态位展开,任何一步跳过状态检查都可能导致数据丢失。

SPI Flash 常用指令操作流程与状态位 主机 (MCU) 0x9F 读ID 0x06 写使能 0x20 扇区擦除 0x02 页编程 0x03 读数据 SPI Flash 芯片 返回 EF 40 17 WEL = 1 WIP = 1 → 0 WIP = 1 → 0 输出数据流 轮询 0x05 轮询 0x05 关键约束 • 写/擦前必须 0x06 • 每页/每扇区独立使能 • 页编程不跨 256B 页 • 擦除最小 4KB 扇区 • 写前必须擦除为 0xFF • 操作后轮询 WIP=0 • WEL 完成后自动清零

七、案例与排障:从现象反推命令问题

场景一:读 ID 返回 00 00 00 或 FF FF FF。前者通常是 MISO 未接或片选未拉低,后者通常是芯片未供电或时钟模式错误。先确认硬件,再检查 CPOL/CPHA 是否与芯片手册一致。

场景二:页编程后读回数据只有第一页正确。原因几乎总是“只发了一次写使能”。修复方法是在每次页编程函数入口都调用 0x06,并在写完后轮询 WIP。

场景三:擦除后读回仍不是 0xFF。检查是否误用了写禁止 0x04,或者擦除地址未按 4KB 对齐。部分芯片要求擦除地址低 12 位为 0,否则行为未定义。

复盘要点:指令集学习的核心不是背操作码,而是建立“命令 → 状态位 → 时序约束”的联动认知。读 ID 验证链路,读状态判断忙闲,写使能打开写权限,擦除恢复 0xFF,页编程写入数据,读数据校验结果——这六步构成了 SPI Flash 驱动最稳定的操作骨架。

掌握这些命令后,后续的驱动分层、擦写均衡、文件系统移植都会变得有据可依。建议在调试阶段用逻辑分析仪抓取一次完整的“写使能 + 页编程 + 读状态”波形,对照操作码逐字节确认,这比反复改代码更能快速建立直觉。