STM32F4 使用 D-Cache 与 SDRAM 帧缓冲:Cache 一致性维护的几种高效策略
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列驱动 LCD 或图形库(如 LTDC、DMA2D)时,SDRAM 常作为帧缓冲,但 D-Cache 的介入会引发数据不一致问题,导致画面撕裂或数据错乱。本文深入剖析 Cache 一致性问题的根源,并给出三种高效维护策略:全缓存关闭、Clean+Invalidate 手动维护、以及基于 MPU 的 Non-cacheable 区域配置。每种策略均附原理讲解、配置步骤和完整代码示例,帮助开发者根据性能与复杂度权衡选择。
# 引言
在 STM32F4 系列(如 STM32F429/469)驱动 TFT-LCD 或图形加速器(LTDC、DMA2D)时,SDRAM 常被用作帧缓冲(Framebuffer)。然而,当 CPU 通过 D-Cache 写入帧缓冲,而 DMA2D 或 LTDC 直接读取 SDRAM 时,D-Cache 中的脏数据尚未回写,外设读到的却是旧数据,这就是经典的 Cache 一致性问题。若不处理,画面会出现撕裂、花屏或数据错乱。
本文面向有一定嵌入式基础的开发者,介绍三种高效的维护策略,并给出可落地的代码示例。
# 问题根源:D-Cache 与 SDRAM 的“双轨”
STM32F4 的 D-Cache(Cortex-M4 内核)是写回(Write-back)模式,CPU 写数据时先写入 Cache,标记为脏(Dirty),只有在缓存行被替换或显式 Clean 时才回写 SDRAM。而 DMA2D、LTDC 等外设直接访问 SDRAM,不经过 Cache。因此,CPU 写入帧缓冲后,外设可能读到旧数据;反之,外设写入(如摄像头采集)后,CPU 可能读到 Cache 中的旧副本。
# 策略一:直接关闭 D-Cache(最简单,但性能损失大)
## 原理
关闭 D-Cache 后,CPU 每次读写都直接访问 SDRAM,彻底避免一致性问题。但代价是 CPU 访问 SDRAM 的延迟显著增加(SDRAM 通常 10-20ns,而 Cache 命中仅 1-2ns),图形渲染性能可能下降 30%-50%。
## 配置步骤
1. 在系统初始化时调用 `SCB_DisableDCache()`。
2. 确保所有外设(LTDC、DMA2D)正常工作。
## 代码示例
```c
#include "stm32f4xx.h"
void SystemInit_CacheDisabled(void) {
SCB_DisableDCache(); // 关闭 D-Cache
// 后续所有 SDRAM 访问均直接走 AHB 总线
}
// 使用示例:初始化 LTDC 和 SDRAM 后,直接操作帧缓冲
void DrawPixel(uint32_t x, uint32_t y, uint16_t color) {
uint16_t *fb = (uint16_t*)SDRAM_FB_ADDR;
fb[y * LCD_WIDTH + x] = color; // 无 Cache,直接写 SDRAM
}
```
## 注意事项
- 适合对性能要求不高、或帧缓冲访问频率低的场景(如静态图片显示)。
- 若同时使用 DMA2D 做图形加速,DMA2D 性能不受影响,但 CPU 操作会变慢。
# 策略二:手动 Clean + Invalidate(灵活,但需精细控制)
## 原理
保持 D-Cache 开启,在关键操作前后手动维护一致性。具体来说:
- 当 CPU 写入帧缓冲后,调用 `SCB_CleanDCache_by_Addr()` 将脏数据回写 SDRAM,确保外设可读。
- 当外设写入内存(如摄像头 DMA 采集)后,调用 `SCB_InvalidateDCache_by_Addr()` 使 Cache 行失效,强制 CPU 下次从 SDRAM 重新读取。
## 配置步骤
1. 开启 D-Cache(默认开启,也可显式调用 `SCB_EnableDCache()`)。
2. 在每次 CPU 写帧缓冲后,调用 Clean 函数。
3. 在每次外设写内存后,调用 Invalidate 函数。
## 代码示例
```c
#include "stm32f4xx.h"
#define FB_ADDR 0xC0000000 // SDRAM 帧缓冲地址
#define FB_SIZE (800*480*2) // 假设 800x480 RGB565
// CPU 绘制完成后,回写脏数据
void Framebuffer_Flush(void) {
SCB_CleanDCache_by_Addr((uint32_t*)FB_ADDR, FB_SIZE);
}
// 外设(如摄像头 DMA)写入后,使 Cache 失效
void Framebuffer_Invalidate(void) {
SCB_InvalidateDCache_by_Addr((uint32_t*)FB_ADDR, FB_SIZE);
}
// 使用示例:绘制一帧后刷新
void DrawFrame(void) {
// ... 绘制操作,直接写 FB_ADDR ...
Framebuffer_Flush(); // 确保 LTDC 读到最新数据
}
```
## 注意事项
- 地址必须 32 字节对齐(Cache line 大小),大小需为 32 的倍数,否则可能遗漏部分缓存行。
- 频繁 Clean 会带来性能开销,建议在帧绘制完成后一次性 Clean,而不是每像素操作。
- 若使用 DMA2D 进行块拷贝,需在启动 DMA2D 前 Clean 源地址,在完成后 Invalidate 目标地址。
# 策略三:MPU 配置 Non-cacheable 区域(推荐,性能与一致性兼得)
## 原理
通过 MPU(Memory Protection Unit)将 SDRAM 帧缓冲区域设置为 Non-cacheable(或 Write-through),这样 CPU 访问该区域时绕过 D-Cache,直接读写 SDRAM,而其他内存区域(如 SRAM)仍保持 Cache 加速。这是最优雅的方案,无需手动维护,且性能损失极小(仅帧缓冲区域无缓存)。
## 配置步骤
1. 初始化 MPU,配置一个 Region 覆盖 SDRAM 地址范围。
2. 设置 Region 属性为 Non-cacheable(TEX=0, C=0, B=1 或 TEX=1, C=0, B=0,具体见参考手册)。
3. 使能 MPU 和 D-Cache。
## 代码示例
```c
#include "stm32f4xx.h"
void MPU_Config_NonCacheable(void) {
// 禁用 MPU 进行配置
MPU->CTRL = 0;
// 配置 Region 0:SDRAM 区域
MPU->RNR = 0; // Region 0
MPU->RBAR = 0xC0000000; // 基地址
// 大小:256MB(0x10000000),使能,子区域使能
MPU->RASR = (0x1E << 1) | // 大小 256MB (2^28)
(0x0 << 0) | // 使能
(0x0 << 16) | // TEX=0
(0x0 << 18) | // C=0
(0x1 << 19) | // B=1 (Non-cacheable)
(0x0 << 24); // 访问权限:全权限
// 使能 MPU,使用默认内存映射作为后备
MPU->CTRL = (0x1 << 0) | (0x1 << 2); // ENABLE=1, PRIVDEFENA=1
// 使能 D-Cache(若未开启)
SCB_EnableDCache();
}
// 使用示例:初始化时调用一次
int main(void) {
// ... 系统时钟、SDRAM 初始化 ...
MPU_Config_NonCacheable();
// 之后直接操作帧缓冲,无需任何手动维护
uint16_t *fb = (uint16_t*)0xC0000000;
fb[0] = 0xFFFF; // 直接写,LTDC 立即可见
while(1);
}
```
## 注意事项
- 确保 SDRAM 地址和大小与 MPU Region 匹配,且 Region 大小必须是 2 的幂次。
- 若 SDRAM 同时用于其他数据(如堆),需谨慎划分区域,避免影响性能。
- 此策略下,CPU 访问帧缓冲无 Cache 加速,但相比关闭整个 D-Cache,其他代码和数据仍受益于 Cache,整体性能最优。
# 总结与选择建议
| 策略 | 性能 | 复杂度 | 适用场景 |
|------|------|--------|----------|
| 关闭 D-Cache | 低 | 极低 | 简单显示,性能不敏感 |
| 手动 Clean/Invalidate | 中 | 中 | 需要精细控制,帧率要求高 |
| MPU Non-cacheable | 高 | 中高 | 图形应用,推荐生产使用 |
对于大多数 STM32F4 图形项目,推荐使用策略三(MPU 配置 Non-cacheable),它平衡了性能与开发效率。若项目对帧缓冲访问频率极高且需要 CPU 加速,可考虑策略二,但务必注意缓存行对齐和操作时机。
希望本文能帮助你彻底解决 D-Cache 与 SDRAM 的一致性问题,让图形显示更稳定流畅。欢迎在评论区交流你的实践经验!