1. QSPI 总线概述与协议基础
咱们先从一个很简单的问题开始:你手上的 MCU 或者 FPGA,想跟外部 Flash 高速交换数据,用 SPI 总觉得带宽不够,怎么办?
我当年第一次碰到这个需求时,硬是拿 SPI 的 50 MHz 去跑一个 1080p 的帧缓冲区更新,结果屏幕刷新率惨不忍睹。后来翻数据手册才发现,原来还有 QSPI 这么个东西。说白了,QSPI 就是 SPI 的升级版——它把单根数据线变成了四根,一下子把吞吐量翻了四倍。
1.1 从 SPI 到 QSPI:直观的变化
先看张表格,对比一下这对“兄弟”最核心的区别。你对着手册选接口时,心里就有数了。
| 特性 | SPI | QSPI |
|---|---|---|
| 数据线数量 | 1 根(MOSI/MISO 分开) | 4 根(IO0~IO3,双向复用) |
| 每时钟传输比特数 | 1 bit(全双工则 2 bit) | 4 bit(半双工常用,DDR 模式可达 8 bit) |
| 典型应用 | 传感器、SD 卡、简单外设 | Flash、PSRAM、高速配置芯片 |
| 时钟频率上限 | 几十 MHz(受限于 I/O 引脚) | 可达 200 MHz(DDR 下) |
| 指令集复杂度 | 简单读写,无命令体系 | 标准命令(读/写/擦除) + 扩展命令 |
嗯,看到这你可能就明白了:QSPI 并不是重新发明轮子,而是在 SPI 的物理层上做了增强。核心变化就是那三根额外的数据线。
一个小经验:QSPI 的 IO0~IO3 在写命令或地址时,往往还兼顾 SPI 的 MOSI 功能;读数据时又相当于 MISO 的扩展。我在调试一块国产 Flash 时就被这“复用关系”坑过一次——忘了切换线方向,结果读回来的全是 0xFF。
1.2 信号线定义与连接
QSPI 接口至少需要 6 根线:
- CS#(片选) – 低电平有效,选中从设备。我在项目里习惯把 CS# 拉高后再发指令,避免毛刺误触发。
- SCK(时钟) – 时钟源由主机提供。SPI 模式(CPOL/CPHA)同样适用 QSPI,不过大多 QSPI Flash 默认工作在 Mode-0(CPOL=0, CPHA=0)。
- IO0 (SI/SIO0) – 传统 SPI 的 MOSI,QSPI 中作为双向数据线 0。
- IO1 (SO/SIO1) – 传统 SPI 的 MISO,QSPI 中作为双向数据线 1。
- IO2 (WP#/SIO2) – 写保护/数据线 2。注意:有些器件在标准 SPI 模式下这个引脚是写保护功能,切换到 QSPI 模式才变成数据线。
- IO3 (HOLD#/SIO3) – 保持/数据线 3。同样需注意模式切换。
警告:我遇到过一块板子把 IO2 直接接地想强制禁止写保护,结果 QSPI 模式下该引脚变成数据线,导致四线模式下信号被强拉低,通信完全失败!所以,IO2/IO3 的复用关系必须在初始化阶段处理好。
1.3 QSPI 协议的基本帧结构
QSPI 的一个典型操作帧由三个阶段组成:
- 指令阶段(Command Cycle) – 8 位操作码,告诉从设备要干什么(读、写、擦除……)。这个阶段通常用单线(IO0)发送,也可以启用“双线/四线指令”模式。
- 地址阶段(Address Cycle) – 24 位或 32 位地址(由器件决定)。可以用单线、双线或四线发送。
- 空周期(Dummy Cycles,可选) – 给从设备预留准备时间,常见于高速读操作。空周期长短由器件手册指定,我一般会把它配置为最小允许值以提升吞吐量。
- 数据阶段(Data Cycle) – 读写数据。数据长度可变,使用四线模式时每时钟传 4 bit,若启用 DDR(双倍数据率)则每个时钟沿都采样,达到 8 bit/时钟。
下面这张图把帧结构画出来了,你可以把它贴在调试台旁边当速查卡用。
1.4 四线模式与 DDR 模式
QSPI 提供两种提升速率的“武器”:
- 四线模式(QIO) – 地址和数据都用四根线传。每时钟 4 bit。
- DDR 模式(Double Data Rate) – 在时钟的上升沿和下降沿都锁存数据。四线 DDR 意味着每时钟 8 bit,实际带宽是普通 SPI 的 8 倍。
不过!DDR 模式对时序约束更严格。我曾经在一块主频 200 MHz 的 FPGA 上跑四线 DDR,结果因为 PCB 走线等长差了 1 mm,眼图就闭上了。所以硬件 layout 时,四根数据线的等长差最好控制在 20 ps 以内。
1.5 库函数调用中的一个细节
很多人用 MCU 的 QSPI 外设库时,会忽略“指令模式”的配置。举个例子:
// 以 STM32H7 的 QSPI 为例(简化版)
QSPI_CommandTypeDef cmd;
cmd.Instruction = 0xEB; // Quad Output Fast Read
cmd.Address = 0x000000;
cmd.AlternateByte= 0; // 快速读不需要备用字节
cmd.DummyCycles = 4; // 4 个空周期
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; // 每次命令都发指令
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.AddressMode = QSPI_ADDRESS_4_LINES;
cmd.DataMode = QSPI_DATA_4_LINES;
HAL_QSPI_Command(&hqspi, &cmd, HAL_QSPI_TIMEOUT_DEFAULT);
HAL_QSPI_Receive(&hqspi, buffer, size, HAL_QSPI_TIMEOUT_DEFAULT);
你看,DummyCycles 这个参数很容易被人忽略。我当初就漏写了空周期,结果读出来的数据前几个字节全是乱的。查手册才发现快速读操作需要 4 个空周期让 Flash 内部准备好数据。
核心要点:QSPI 不是 SPI 换个引脚名字,它有一套独立的命令集和模式控制逻辑。刚开始接触时,别急着写代码,先把目标芯片的 QSPI 时序图看明白。
1.6 避坑指南——来自第一线
- 曾经有一次,我为了省 IO 口,想用 QSPI 的“双线模式(DIO)”去读写一个只支持四线的 Flash,结果芯片毫无反应。不同 Flash 支持的线路模式不一样,一定要看手册里的“Supported Modes”表格。
- 曾经有一次,我把 CS# 拉高太快,导致 Flash 内部状态机还没完成操作就被中断。后来给 CS# 保持时间加了 5 ns 的延迟,问题解决。
- 曾经有一次,板子上 QSPI 走线太长,导致信号振铃严重。我在 IO 线上串了 22 Ω 电阻,波形就干净了。
好了,这一章的内容大致就是这些。你只要把 SPI 到 QSPI 的进化逻辑、信号线复用关系、帧结构四个阶段这三点记牢,后续的时序测量与分析就容易多了。我建议你手边准备一个逻辑分析仪,对着实际波形看一遍,印象最深。