3. RT1052 FlexSPI 外设详解:时钟、AHB 与模式对比
FlexSPI 这个外设,说白了就是 RT1052 跟外部 Flash 之间的「翻译官」。你想想看,LVGL 要加载一张 800x480 的图片,原始数据少说几百 KB,如果走 SPI 一个字节一个字节地搬,那画面得卡成什么样?FlexSPI 就是为了解决这个痛点而生的。
我个人习惯把 FlexSPI 理解成一个「智能 DMA + 协议转换器」。它既能当普通 SPI 用,也能跑 QuadSPI、OctalSPI,甚至支持双通道并行。这一章,咱们就把它的三个核心模块拆开揉碎了讲。
3.1 FlexSPI 时钟配置:别让频率成为瓶颈
时钟是 FlexSPI 的心脏。配置不对,要么 Flash 不认,要么跑不到标称速度。我在项目里就吃过这个亏——明明 Flash 支持 133MHz,结果读图片时总出 CRC 错误,最后发现是时钟相位没调好。
RT1052 的 FlexSPI 时钟源来自 PLL3(USB1 PLL)的 PFD0 分频,典型配置流程如下:
- 选择时钟源:通常用 PLL3 PFD0,频率 720MHz
- 设置分频系数:通过 SCKCR 寄存器的 FLEXSPIx_SEL 位域
- 配置串行时钟:在 FlexSPI 的 MCR0 寄存器中设置 RXCLKSRC 和 DLLCR
- 校准 DLL:动态延迟锁相环,补偿 PCB 走线延迟
关键寄存器速查表
| 寄存器 | 作用 | 典型值 |
|---|---|---|
| MCR0[RXCLKSRC] | 选择读数据采样时钟源 | 0b01(Dummy 循环移位) |
| MCR0[DOZEN] | 休眠模式使能 | 0(关闭) |
| DLLCR[DLLEN] | DLL 使能 | 1(使能) |
| DLLCR[OVERRIDEEN] | DLL 覆盖使能 | 0(自动校准) |
嗯,这里要注意:DLL 校准不是一次性的。温度变化、电压波动都会导致延迟漂移。我建议在 LVGL 的 lv_timer 里每隔 30 秒触发一次 FLEXSPI_DRV_Calibrate(),保证长时间运行稳定。
我的经验:如果 Flash 工作在 104MHz 以上,务必开启 DLL 并设置 RXCLKSRC = 0b10(内部循环移位)。否则采样窗口会偏移,导致偶发性读错误。
3.2 AHB 总线访问:让 CPU 像读内存一样读 Flash
FlexSPI 最爽的一点,就是支持 AHB 总线映射。什么意思呢?你把外部 Flash 映射到 0x60000000~0x7FFFFFFF 的地址空间,然后 CPU 直接 *(uint32_t *)0x60001000 就能读到数据。不需要手动发命令、等状态、收数据——硬件全帮你干了。
AHB 访问的流程大致是这样:
- CPU 发起 AHB 读请求,地址落在 FlexSPI 映射区
- FlexSPI 自动将 AHB 地址转换为 Flash 命令序列
- 数据通过 IP 总线或 AHB 直接返回
- 如果开启了 prefetch(预取),连续读命中时零等待
我曾经在 LVGL 的 lv_draw_img 回调里直接做 AHB 读取,配合 16 字节对齐的 buffer,性能比用 FLEXSPI_TransferBlocking() 快了将近 3 倍。为什么?因为 AHB 走的是 64 位或 128 位总线,而 IP 命令接口一次只能传 32 位。
避坑指南:AHB 映射虽然快,但有个坑——它不支持写操作。你要擦除或编程 Flash,还是得走 IP 命令接口。我曾经在项目里直接往 0x60000000 写数据,结果写了个寂寞,Flash 内容纹丝不动。
配置 AHB 映射需要设置 AHBCR 寄存器和 AHBRXBUFxCR0 系列寄存器。我一般这样配:
// 使能 AHB 访问,设置预取深度
FLEXSPI->AHBCR = FLEXSPI_AHBCR_APAREN(1) | // 并行访问使能
FLEXSPI_AHBCR_CACHEN(1) | // 缓存使能
FLEXSPI_AHBCR_PREFETCHEN(1); // 预取使能
// 配置 AHB RX Buffer 0:4KB,优先级最高
FLEXSPI->AHBRXBUF0CR0 = FLEXSPI_AHBRXBUF0CR0_BUFSZ(0x0F) | // 4KB
FLEXSPI_AHBRXBUF0CR0_MSTRID(0) |
FLEXSPI_AHBRXBUF0CR0_PRIORITY(3);
3.3 QuadSPI vs OctalSPI 模式对比
很多初学者搞不清 QuadSPI 和 OctalSPI 到底差在哪。我直接说结论:OctalSPI 不是 QuadSPI 的两倍速度,它俩的差异比你想的复杂。
先看一张对比表:
| 对比项 | QuadSPI | OctalSPI |
|---|---|---|
| 数据线数量 | 4 根(IO0~IO3) | 8 根(IO0~IO7) |
| 单次传输位宽 | 4 bit | 8 bit |
| 典型频率 | 80~104 MHz | 104~166 MHz |
| 理论峰值带宽 | ~52 MB/s | ~166 MB/s |
| 引脚数量 | 6(CS+CLK+4IO) | 10(CS+CLK+8IO) |
| DQS 信号 | 无 | 可选(高速模式必需) |
| Flash 成本 | 低 | 高(约 1.5~2 倍) |
看到没?OctalSPI 的带宽优势主要来自两个维度:更宽的数据总线和更高的时钟频率。但代价也很明显——多出 4 根 IO 线,PCB 布局更麻烦,而且需要 Flash 芯片支持 DQS 信号才能跑高频。
我在一个实际项目里做过对比测试:同样一张 800x480 的 16 位色 BMP 图片(约 750KB),用 QuadSPI 加载需要 18ms,用 OctalSPI 只需要 6ms。对于 LVGL 的帧率来说,这 12ms 的差距可能就是 30fps 和 60fps 的区别。
选型建议:
- 如果 LVGL 只显示静态界面,偶尔切换页面 → QuadSPI 完全够用
- 如果要做动画、视频播放、快速滑动 → 上 OctalSPI,别犹豫
- 如果 PCB 空间紧张、层数少 → QuadSPI 布线更友好
下面这张图展示了两种模式在 FlexSPI 内部的信号路径差异:
最后说一个很多人忽略的点:OctalSPI 的 DQS 信号。在高速模式下(>104MHz),Flash 会输出 DQS 作为数据选通,FlexSPI 用这个信号来采样读数据。如果不用 DQS,FlexSPI 只能靠内部 DLL 猜测采样点,稳定性大打折扣。我建议只要 Flash 支持,就开启 DQS,哪怕多占一个引脚也值。
我的配置习惯:在 LUT(查找表) 中,QuadSPI 用 CMD_SDR + 4PAD,OctalSPI 用 CMD_DDR + 8PAD。DDR 模式能在同样时钟频率下翻倍带宽,但时序约束更严格。