手把手教你掌握QSPI通信协议:从原理到实战的完整指南
你有没有遇到过这样的场景?系统功能越来越复杂,固件体积早已突破2MB,而MCU内置Flash却只有512KB。传统的做法是把代码加载进RAM再运行——可RAM也有限,搬移过程还慢得让启动时间翻了好几倍。
这时候,QSPI(Quad SPI)就成了那个“破局者”。
它不是什么黑科技,但却是现代高性能嵌入式系统的标配技能。今天,我们就以STM32H7为蓝本,彻底讲清楚QSPI到底怎么用、为什么强,以及如何在真实项目中稳稳落地。
为什么SPI不够用了?
先别急着学QSPI,我们得明白它解决的是什么问题。
标准SPI使用四根线:SCLK、MOSI、MISO、CS,数据传输是单向双工——MOSI发,MISO收。带宽呢?假设主频100MHz,实际有效速率也就12.5MB/s左右(考虑CPHA/CPOL和空闲周期)。对于读个配置、控制传感器绰绰有余,但一旦涉及图形界面、音频播放或直接执行代码,这就成了瓶颈。
更麻烦的是,如果你要用外部Flash存程序,传统方式必须先把整段代码搬运到内部SRAM才能运行。这不仅吃内存,还拖慢启动速度,用户体验大打折扣。
于是,QSPI登场了。
它本质上是对SPI的“升级版”,核心改进在于:
- 数据线从2条扩展到4条(IO0~IO3),支持单线、双线、四线传输模式
- 支持内存映射模式(Memory-Mapped Mode),实现XIP(eXecute In Place)
- 硬件自动处理命令、地址、数据序列,CPU几乎不用干预
- 配合DMA和FIFO,轻松实现高速连续读写
一句话总结:
SPI适合“小打小闹”,QSPI专治“大容量+高速度”需求。
QSPI到底是怎么工作的?
主从结构 + 三阶段事务
QSPI依然是主从架构,由MCU发起通信。一个完整的操作通常分为三个阶段:
命令阶段(Command Phase)
发送一个8位指令码,比如0xEB表示“快速四线读取”。地址阶段(Address Phase)
指定要访问的存储单元地址,可以是24位或32位,通过IO0~IO3并行发送。数据阶段(Data Phase)
实际的数据传输,支持单线、双线或四线模式,大幅提升吞吐率。
关键点来了:这三个阶段可以独立配置使用的数据线数量!
举个例子:
- 使用指令0xEB(Fast Read Quad Output)时:
- 命令:单线发送
- 地址:四线输出
- 空周期:插入6个dummy cycles(等待Flash准备)
- 数据:四线接收
这种灵活性让它能兼容各种Flash芯片,像Winbond W25Q系列、Micron MT25QL等主流型号都能完美适配。
两种工作模式:间接 vs 内存映射
这是QSPI最实用的两个模式,用途完全不同。
1. 间接模式(Indirect Mode)
这是最常见的操作方式,类似普通外设访问。你需要调用API函数,手动发送命令、读写数据。
HAL_QSPI_Command(&hqspi, &cmd, timeout); HAL_QSPI_Transmit(&hqspi, data, timeout);适用于:
- 固件烧录
- Flash擦除/写入
- OTA升级
- 随机读写小块数据
优点是控制精细,缺点是每次都要走软件流程,效率较低。
2. 内存映射模式(Memory-Mapped Mode)
这才是QSPI的“杀手锏”。
启用后,外部Flash被映射到MCU的一段地址空间(例如0x90000000),你可以像访问数组一样直接读取其中的内容,甚至从中执行代码!
typedef void (*func_ptr)(void); func_ptr app_start = (func_ptr)0x90000000; app_start(); // 直接跳转执行外部Flash中的代码这意味着什么?
意味着你的应用程序可以直接放在QSPI Flash里运行,无需搬移到RAM!节省下来的SRAM可以用来做动态资源加载、缓存处理、网络缓冲……系统整体性能提升一大截。
性能对比:QSPI到底快多少?
| 特性 | 标准SPI | QSPI(四线SDR) | QSPI(四线DDR) |
|---|---|---|---|
| 数据线数 | 2 | 4 | 4 |
| 时钟频率 | 最高约50MHz | 可达133MHz | 可达80MHz |
| 理论带宽 | ~12.5 MB/s | ~50 MB/s | ~80 MB/s |
| 是否支持XIP | 否 | 是 | 是 |
| 引脚占用 | 4根 | 6根(含SCLK、nCS) | 6根 |
看到没?同样是100MHz时钟,在四线模式下,每个时钟能传4位数据,理论速率就是SPI的4倍。如果再上DDR(Double Data Rate),每个时钟上升沿和下降沿都采样,速率还能再翻一倍。
比如STM32H7配合W25Q128JV,在优化条件下实测持续读取可达70~80MB/s,足够流畅播放RGB565图片流或解码MP3音频。
STM32H7上的QSPI实战:一步步教你配置
我们以STM32H743为例,演示如何初始化QSPI并实现高速读取。
硬件连接很简单
只需要6根线:
MCU ↔ QSPI Flash ------------------------------------- QSPI_BK1_IO0 ↔ IO0 QSPI_BK1_IO1 ↔ IO1 QSPI_BK1_IO2 ↔ IO2 QSPI_BK1_IO3 ↔ IO3 QSPI_CLK ↔ CLK QSPI_NCS ↔ /CS建议在每条信号线上串联一个10~33Ω电阻,靠近MCU端放置,抑制高频反射。
第一步:初始化QSPI控制器
QSPI_HandleTypeDef hqspi; void MX_QUADSPI_Init(void) { hqspi.Instance = QUADSPI; hqspi.Init.ClockPrescaler = 1; // APB时钟200MHz → SCLK = 100MHz hqspi.Init.FifoThreshold = 4; hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE; // 半周期偏移采样 hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE; hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; // CPOL=0, CPHA=0 hqspi.Init.FlashSize = POSITION_VAL(0x1000000) - 1; // 16MB Flash (24-bit) hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE; if (HAL_QSPI_Init(&hqspi) != HAL_OK) { Error_Handler(); } }这里最关键的是ClockPrescaler:分频系数越小,SCLK越高。但要注意Flash的最高支持频率,别超了。
第二步:复位Flash,确保状态正常
很多初学者忽略这一步,结果通信失败还以为是时序问题。
void QSPI_ResetMemory(void) { QSPI_CommandTypeDef cmd = {0}; // Step 1: 发送 Reset Enable (0x66) cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; cmd.Instruction = 0x66; cmd.AddressMode = QSPI_ADDRESS_NONE; cmd.DataMode = QSPI_DATA_NONE; HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT_DEFAULT); // Step 2: 发送 Reset Memory (0x99) cmd.Instruction = 0x99; HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT_DEFAULT); }这两个命令组合起来相当于给Flash“重启”,清除可能存在的异常状态。
第三步:四线快速读取数据(0xEB指令)
这是最常用的读取方式,用于高效获取大量数据。
uint8_t* QSPI_ReadData(uint32_t address, uint32_t size) { static uint8_t rx_buffer[256]; QSPI_CommandTypeDef cmd = {0}; cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE; cmd.Instruction = 0xEB; // Fast Read Quad Output cmd.AddressMode = QSPI_ADDRESS_4_LINES; // 四线发送地址 cmd.AddressSize = QSPI_ADDRESS_24_BITS; cmd.Address = address; cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; cmd.DataMode = QSPI_DATA_4_LINES; // 四线接收数据 cmd.DummyCycles = 6; // 至少6个空周期(W25Q要求) cmd.NbData = size; cmd.DdrMode = QSPI_DDR_MODE_DISABLE; cmd.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD; HAL_QSPI_Command(&hqspi, &cmd, HAL_TIMEOUT_DEFAULT); HAL_QSPI_Receive(&hqspi, rx_buffer, HAL_TIMEOUT_DEFAULT); return rx_buffer; }重点说明:
-DummyCycles = 6:这是Flash厂商规定的“等待时间”,必须满足;
-AddressMode = QSPI_ADDRESS_4_LINES:地址也用四线发,提速;
- 如果Flash支持DDR模式,可以把DdrMode设为ENABLE,速率再翻倍。
如何开启XIP?一行代码搞定
// 启动内存映射模式 HAL_QSPI_MemoryMapped(&hqspi, &cmd); // 此时即可通过指针访问Flash内容 const uint8_t *logo = (const uint8_t*)0x90000000; draw_bmp(logo, 320, 240); // 直接绘制存储在Flash中的BMP图像从此以后,你再也不需要把整个图片解压到RAM了,想读哪就读哪,真正实现“按需加载”。
工程实践中常见的“坑”与应对策略
❌ 问题1:高频下通信不稳定
现象:低速(<50MHz)能通,高速就出错。
原因:PCB走线不匹配、阻抗失控、电源噪声大。
解决方案:
- 控制差分阻抗50Ω,长度尽量一致;
- 使用LDO单独供电给Flash;
- 加0.1μF + 10μF去耦电容紧贴VCC引脚;
- 在时钟和数据线上加10~22Ω串联电阻。
❌ 问题2:XIP运行崩溃
现象:代码能在RAM跑,但从Flash跳转就死机。
原因:
- 中断向量表仍在内部Flash;
- 函数调用了RAM中变量但未重定向;
- Flash尚未进入QPI模式(仍处于SPI模式);
正确做法:
1. 先用间接模式将Flash切换到四线模式(发送0x35指令);
2. 配置QSPI进入内存映射模式;
3. 修改链接脚本,将代码段(.text)和中断向量表定位到0x90000000;
4. 使用分散加载文件(scatter file)确保全局变量仍在RAM中。
✅ 最佳实践清单
| 项目 | 推荐做法 |
|---|---|
| Flash选型 | 选用支持QPI模式的芯片,如 W25Q128JV、MX25L12833F |
| PCB设计 | 走线等长、避免跨层、加入串阻 |
| 电源设计 | LDO独立供电 + 多级滤波电容 |
| 时序验证 | 高温低压下测试最大频率稳定性 |
| 软件健壮性 | 添加CRC校验、实现坏块管理、支持OTA双区备份 |
QSPI在系统架构中的角色
在一个典型的工业HMI或IoT网关中,QSPI往往是“承上启下”的关键组件:
+------------------------+ | Application Code | | (Running from SRAM) | +------------+-----------+ ^ | +------------------+------------------+ | MCU with QSPI Controller | | (e.g., STM32H7, GD32H7xx) | +------------------+------------------+ | QSPI Bus (CLK, CS, IO0~IO3) | +------------------+------------------+ | External NOR Flash (16MB~128MB) | | - Bootloader | | - App Image | | - GUI Resources (images, fonts) | | - Audio Clips | | - OTA Backup Partition | +---------------------------------------+它的存在使得:
- 启动更快(XIP免搬移)
- 功能更强(容纳复杂UI和多媒体)
- 升级更安全(双分区备份)
- 成本更低(减少对大SRAM的依赖)
写在最后:QSPI只是起点
别以为QSPI已经到头了。现在已有Octal-SPI(八线)、HyperBus、Xccela Bus等新一代接口出现,速率轻松突破200MB/s。
但你要知道,所有这些高级接口的设计思想,都源于QSPI的基本模型:
多线并行 + 命令协议分离 + 硬件状态机 + 内存映射支持。
所以,真正掌握QSPI,不只是学会一个外设驱动,而是理解了一种高速串行存储接口的通用范式。
当你有一天面对陌生的HyperFlash也能迅速上手时,你会感谢今天认真搞懂QSPI的那个自己。
如果你正在做图形界面、OTA升级、音频播放或者任何需要“大容量+高速读取”的项目,不妨试试QSPI。
也许,它就是你产品性能跃升的关键一步。
💬 你在项目中用过QSPI吗?遇到了哪些挑战?欢迎在评论区分享你的经验!