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 分频,典型配置流程如下:

  1. 选择时钟源:通常用 PLL3 PFD0,频率 720MHz
  2. 设置分频系数:通过 SCKCR 寄存器的 FLEXSPIx_SEL 位域
  3. 配置串行时钟:在 FlexSPI 的 MCR0 寄存器中设置 RXCLKSRC 和 DLLCR
  4. 校准 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 的两倍速度,它俩的差异比你想的复杂。

先看一张对比表:

对比项QuadSPIOctalSPI
数据线数量4 根(IO0~IO3)8 根(IO0~IO7)
单次传输位宽4 bit8 bit
典型频率80~104 MHz104~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 内部的信号路径差异:

QuadSPI vs OctalSPI 信号路径对比 QuadSPI 模式 CS + CLK + IO[0:3] 数据线:4 根 无 DQS 信号 典型频率:≤104 MHz OctalSPI 模式 CS + CLK + IO[0:7] 数据线:8 根 DQS 信号(可选) 典型频率:≤166 MHz FlexSPI 核心控制器 AHB 总线映射 QuadSPI 用 4 根数据线 + 较低频率;OctalSPI 用 8 根数据线 + 更高频率 + 可选 DQS

最后说一个很多人忽略的点:OctalSPI 的 DQS 信号。在高速模式下(>104MHz),Flash 会输出 DQS 作为数据选通,FlexSPI 用这个信号来采样读数据。如果不用 DQS,FlexSPI 只能靠内部 DLL 猜测采样点,稳定性大打折扣。我建议只要 Flash 支持,就开启 DQS,哪怕多占一个引脚也值。

我的配置习惯:在 LUT(查找表) 中,QuadSPI 用 CMD_SDR + 4PAD,OctalSPI 用 CMD_DDR + 8PAD。DDR 模式能在同样时钟频率下翻倍带宽,但时序约束更严格。