QSPI协议与四线模式

QSPI 这个名字,说白了就是 “Quad SPI” 的缩写。

一开始大家用标准 SPI,一根数据进、一根数据出,简单又可靠。但后来程序越来越大,Flash 里不光要存代码,还要存字库、固件包。标准 SPI 一次只传 1 位,速度就成了瓶颈。

这时候,QSPI 就来了。它保留了 SPI 的框架,但把数据线扩到了 4 根。一次时钟能传 4 位,传输效率直接翻 4 倍。而且它向下兼容 SPI 模式——BootROM 读 Flash 时可以先拿 SPI 发命令,再切到四线模式把数据拉回来。这个小技巧,几乎在每个主流 SoC 里都能看到。

核心要点:QSPI 不是重新发明轮子,而是在标准 SPI 的命令/地址/数据三段结构上,把数据段(有时也包括地址段)换成 4 线并行。这才是它高效且兼容的关键。

从 SPI 到 QSPI:命令集的变化

标准 SPI 的命令就那么几条:写使能、扇区擦除、页编程、读数据。所有操作都通过 SI(Master Out Slave In)和 SO(Master In Slave Out)单线完成。

QSPI 增加了针对四线模式的专用命令,比如 Fast Read Quad I/O(0xEB)、Quad Input Page Program(0x38)。这些命令比普通 Fast Read 多了一个步骤:发送完命令和地址后,IO0-IO3 全部变成数据线。

我习惯把 QSPI 的命令分三类来看,这样设计驱动时思路更清楚:

命令类型 典型指令码 四线参与阶段
标准 SPI 命令 0x06(写使能)、0x20(扇区擦除) 仅 SI/SO,无四线
双线输出命令 0x3B(Fast Read Dual Output) 数据段:IO0+IO1
四线全双工命令 0xEB(Fast Read Quad I/O)、0x38(Quad Page Program) 地址段+数据段:IO0-IO3

你看那个 0xEB 命令,很多 Flash 数据手册把它列为“推荐快速读取方式”。地址用 4 线发,数据用 4 线收,整个交互只有命令码那一个字节是单线。嗯,这里要注意:不是所有 Flash 都支持 0xEB,有些老旧型号只认标准 Fast Read(0x0B)。选择 Flash 型号时,务必确认它支持 Quad I/O 模式。

四线模式的切换与 IO 配置

QSPI 控制器上电默认都是 SPI 模式,也就是 IO1 做 MOSI,IO2 做 MISO,IO3 和 IO4 保持高阻或弱上拉。

怎么切到四线?通常分两步走:

  1. 发一个 “Quad Enable” 命令。比如 Winbond 的 0x35(读状态寄存器 2),把 QE 位置 1。这个位是 volatile 还是 non-volatile,取决于 Flash 型号,我建议初始化时每次都设置一下,避免某些复位状态把它清掉。
  2. 发四线读取命令。比如 0xEB,地址发完立刻把 IO 方向切到输入,CLK 连续拉 4 个周期读一个字节。

设置 QE 位这一步,我曾在一个项目里踩过坑。板子回来怎么调都读不出数据,示波器一看,IO2 和 IO3 全是高阻。查了半天,发现 Flash 上电默认 QE=0,代码里只发了 0xEB 命令,没先发 0x06+0x01 去写状态寄存器。从那以后,我把 “Set Quad Enable” 写成了每个平台 Flash 初始化的固定步骤,不敢省。

踩坑提醒:不同厂商的 QE 位位置可能不同。Winbond 在状态寄存器 2 的 bit1,Macronix 可能在状态寄存器的 bit6,GD 的有些型号甚至需要发 0x81 写扩展状态寄存器。不理解这些差异,直接套用例程极大概率翻车。

数据线的方向控制

四线模式下,IO0-IO3 需要双向三态控制。控制器内一般有个 “IO Direction” 寄存器,用来切换数据线的输入输出方向。

我个人习惯把方向切换逻辑封装成一个内联函数,放在头文件里:

// 假设 io_ctrl 寄存器 bit0-3 对应 IO0-IO3 方向,1=输出,0=输入
static inline void qspi_set_dir(uint8_t mask) {
    *QSPI_IO_CTRL |= (mask & 0x0F);
}

static inline void qspi_clear_dir(uint8_t mask) {
    *QSPI_IO_CTRL &= ~(mask & 0x0F);
}

// 发送地址时,4 线都设为输出
qspi_set_dir(0x0F);
// 发送完地址,切换到输入等数据
qspi_clear_dir(0x0F);

这种写法在 Cortex-M 系列上效率不错。你想想看,如果每次切换都用 if-else 判断,中断里就容易丢时序。

硬件时序:不仅仅是频率

QSPI 通常能跑 100-166 MHz。但频率高起来,信号完整性就成了大问题。

我建议 PCB 布线上注意三点:

  • 等长尽量做。4 条数据线 + CLK 的走线长度差不要超过 200 mil。差太多的话,skew 会把采样窗口吃掉。
  • CLK 单独包地。数据线在 CLK 两侧,中间包一层 GND 过孔墙。这能减少串扰,尤其在四线同时翻转的时候。
  • 片选 CS 别跟数据线平行太长。CS 跳变时如果耦合到数据线上,第一个字节可能就错了。

另外,很多 SoC 的 QSPI 控制器支持 Dual I/O 和 Quad I/O 两种四线模式。Dual I/O 只用 IO0+IO1,Quad I/O 用全 4 根。做低功耗设计时,如果带宽够,优先选 Dual I/O 模式,可以减少 IO 翻转,降低动态功耗。我在一个电池供电的 IoT 模块上就这么干过,整体读取能耗下降了大约 15%。

一张图看懂:QSPI 协议与四线模式的结构

QSPI 协议与四线模式结构图 标准 SPI 框架 1 根 CLK 1 根 CS 1 根 MOSI 1 根 MISO 单线传输,每次 1 位 QSPI 四线模式 1 根 CLK 1 根 CS 4 根数据线 (IO0-IO3) 双向三态 四线并行,每次 4 位 增加数据线 四线读取命令 0xEB 的传输结构 命令 1 线模式 地址 4 线模式 dummy 周期 (可选) 数据 4 线模式 命令码用 1 线发,地址和数据用 4 线发。dummy 周期让内部延迟完成。

从上图你可以清楚看到:标准 SPI 是 1 根数据线干活,其他人看着。QSPI 四线模式让 4 根数据线同时工作,而且地址段也能并行发送。再加上 dummy 周期的设计,Flash 内部准备好的数据可以连续不断流出来,非常适合做代码 XIP(就地执行)。典型的 OS 启动,比如从 QSPI Flash 加载 Linux kernel,就是靠这种模式把带宽推到极限。

个人习惯:做 BootROM 初始化时,我会先发一条 Read ID(0x9F)确认 Flash 型号,然后读状态寄存器检查 QE 位。如果发现没使能,就写状态寄存器使能四线模式。这套流程写在 startup 里,能避免 80% 的 Flash 通讯问题。

小结一下今天的内容

QSPI 协议不是新协议,它是标准 SPI 的强化版。核心就三点:

  • 四线数据线并行,带宽是 SPI 的 4 倍。
  • 命令结构兼容,初期用单线建链,后续切四线传数据。
  • 控制好 QE 位和方向切换,剩下就是硬件时序的精细活。

理解了这些,读 Flash 数据手册里的那些模式表、时序图就不再是云里雾里了。你下次拿到一个新板子,思路自然清晰:查 QE 位 → 配四线模式 → 调等长 → 跑 0xEB 验证。