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 等状态 | 立即 |
| 读 ID | 0x9F | 读取厂商与容量信息 | 立即 |
| 读数据 | 0x03 | 任意地址连续读取 | 随长度 |
| 页编程 | 0x02 | 写入一页(≤256B) | 0.7~3ms |
| 扇区擦除 | 0x20 | 擦除 4KB | 45~400ms |
| 块擦除 | 0xD8 | 擦除 64KB | 150~2000ms |
五、写使能与写禁止:所有写操作的前置条件
写使能(0x06)和写禁止(0x04)是两条“无参数、无返回”的命令,但它们决定了芯片是否接受后续的编程和擦除命令。芯片内部有一个 WEL 位,上电默认为 0。每次执行页编程、扇区擦除或块擦除之前,都必须先发送 0x06 把 WEL 置 1;命令执行完成后,WEL 会自动清零。这意味着每写一页、每擦一个扇区,都要重新发一次写使能,不能“一次使能、多次写入”。
六、完整操作流程与状态机
把上述命令串起来,一次典型的“写入并回读”流程如下:读 ID 确认芯片 → 读状态确认空闲 → 写使能 → 扇区擦除 → 轮询 WIP → 写使能 → 页编程 → 轮询 WIP → 读数据校验。整个过程围绕 WEL 和 WIP 两个状态位展开,任何一步跳过状态检查都可能导致数据丢失。
七、案例与排障:从现象反推命令问题
场景一:读 ID 返回 00 00 00 或 FF FF FF。前者通常是 MISO 未接或片选未拉低,后者通常是芯片未供电或时钟模式错误。先确认硬件,再检查 CPOL/CPHA 是否与芯片手册一致。
场景二:页编程后读回数据只有第一页正确。原因几乎总是“只发了一次写使能”。修复方法是在每次页编程函数入口都调用 0x06,并在写完后轮询 WIP。
场景三:擦除后读回仍不是 0xFF。检查是否误用了写禁止 0x04,或者擦除地址未按 4KB 对齐。部分芯片要求擦除地址低 12 位为 0,否则行为未定义。
掌握这些命令后,后续的驱动分层、擦写均衡、文件系统移植都会变得有据可依。建议在调试阶段用逻辑分析仪抓取一次完整的“写使能 + 页编程 + 读状态”波形,对照操作码逐字节确认,这比反复改代码更能快速建立直觉。