QSPI Flash 概述
QSPI 协议简介
说白了,QSPI 就是 SPI 的升级版。标准 SPI 用 4 根线——SCK、CS、MOSI、MISO,一次只能传 1 位数据。QSPI 把 MOSI 和 MISO 扩展成了 4 根数据线(IO0~IO3),同样时钟下吞吐量翻 4 倍。我在一个高速数据采集项目里试过,用 80MHz 时钟跑 QSPI,读速度能到 40MB/s 左右,SPI 只能到 10MB/s。差距非常明显。
但要注意一个坑:QSPI 不是所有 Flash 都支持。芯片手册里会写“Quad SPI”或“QSPI”字样。我遇到过有人买了个普通 SPI Flash 强行配成 QSPI 模式,结果指令不识别,板子直接黑屏。
| 特性 | SPI | QSPI |
|---|---|---|
| 数据线数量 | 2 (MOSI, MISO) | 4 (IO0~IO3) |
| 单次传输位数 | 1 bit | 4 bits |
| 典型读速度 (80MHz) | ~10 MB/s | ~40 MB/s |
| 引脚数 | 4 (含片选) | 6~8 (含片选/时钟) |
| 兼容 SPI 模式 | – | 通常向下兼容 |
Flash 芯片工作原理
Flash 存储单元实际上就是浮栅晶体管。写数据靠“隧穿效应”把电荷注入浮栅,擦除靠高电压把电荷拉走。你想想看,每擦写一次,绝缘层就会磨损一点。QSPI Flash 的擦写寿命通常在 10 万次左右。我做 bootloader 时经常反复擦写同一片区域,后来发现 Flash 坏块越来越多,才改成随机地址写入。
指令集是统一的。常用的有:
- Read (0x03) – 普通读,速度慢但兼容性好
- Fast Read (0x0B) – 带 dummy cycle,跑更高时钟
- Quad Read (0x6B / 0xEB) – 四线模式读,启动初始化常用
- Write Enable (0x06) – 每次写操作前必须先发这个
- Page Program (0x02) – 写入数据到页缓存
- Sector Erase (0x20 / 0xD8) – 擦除 4KB 或 64KB 块
我个人习惯把初始化过程定义为:“读 ID → 配置模式 → 读数据”三步走。每次上电后先读 JEDEC ID(指令 0x9F),确认 Flash 型号、容量,然后根据芯片手册切换到 QSPI 模式。这一步做错,后面全白费。
// QSPI 读 ID 示例(伪代码)
void qspi_read_id(uint8_t *id_buf) {
qspi_cs_low();
qspi_send_byte(0x9F); // 发送读 ID 指令
for (int i = 0; i < 3; i++) {
id_buf[i] = qspi_read_byte();
}
qspi_cs_high();
}
启动流程概览
系统上电后,CPU 会从默认地址取第一条指令。如果这个地址映射到 QSPI Flash,那么 Flash 控制器必须在 CPU 取指完成前完成初始化。这就是所谓的“零等待启动”——CPU 还没醒来,Flash 已经准备好了。
典型的流程分为 4 个阶段:
- 硬件复位与时钟起振 – 电源稳定,PLL 开始输出 QSPI 时钟
- QSPI 控制器初始化 – 配置 IO 复用、时序参数、中断
- Flash 初始化 – 读 ID、切换 Quad 模式、设置读速度(dummy cycle 数)
- CPU 取指启动 – 从 Flash 首地址读出 bootloader 并执行
这里有个关键点:第二步和第三步谁先谁后?我记得第一次做 SoC 方案,手册说先配控制器再操作 Flash,结果死活读不到 ID。后来发现控制器复位后默认时钟频率太低,Flash 不响应。嗯,后来我在控制器初始化里先设一个慢时钟(比如 1MHz),等 Flash 识别完再升频。
看了这张图应该就能明白,整个流程是串行的。任何一个节点卡住,系统就起不来。我调试过一个项目,现象是偶尔能启动偶尔不能,查了几天发现是 Flash 初始化时的 dummy cycle 数配少了,高速读命令丢字节。后来我把 dummy cycle 从 4 改成 6,再也没出过问题。