STM32F4 系列 D-Cache 与 SDRAM 帧缓冲:Cache 一致性维护的三种实战策略
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列驱动 LCD 或图形库时,将帧缓冲放在外部 SDRAM 是常见做法,但 D-Cache 的介入会引发数据不一致问题,导致画面撕裂或数据错乱。本文深入剖析 Cache 一致性问题的根源,并给出三种实战策略:全量失效/清理、按区域维护、以及免 Cache 的 MPU 配置。每种策略均附原理讲解、配置步骤和完整代码示例,帮助嵌入式开发者根据场景选择最优方案,确保显示稳定可靠。
# 引言
在 STM32F4 系列(如 STM32F407/429)中,D-Cache 和 SDRAM 的组合常被用于高性能图形显示。然而,D-Cache 的缓存机制会导致 CPU 与 DMA 或 LCD 控制器访问同一帧缓冲时出现数据不一致,表现为画面闪烁、残影或数据错误。本文针对这一痛点,提供三种经过实战验证的维护策略,助你彻底解决 Cache 一致性问题。
## 问题根源:D-Cache 与 SDRAM 的“双轨制”
STM32F4 的 D-Cache 是 4 路组相联、总容量 64KB(部分型号)。当 CPU 写入帧缓冲时,数据可能只停留在 Cache 中,尚未写回 SDRAM。而 LCD 控制器(如 LTDC)或 DMA 直接读取 SDRAM 时,看到的仍是旧数据,导致显示异常。反之,若 DMA 更新了 SDRAM 数据,CPU 读取时可能命中过期的 Cache 行。
## 策略一:全量失效与清理(简单粗暴)
### 原理
在每次 CPU 写帧缓冲后、启动 LCD 刷新前,强制将整个 D-Cache 写回(Clean)并失效(Invalidate)。此方法简单可靠,但开销较大,适合帧率要求不高的场景。
### 配置步骤
1. 使能 D-Cache:`SCB_EnableDCache()`。
2. 在关键操作点调用清理和失效函数。
### 代码示例
```c
#include "stm32f4xx_hal.h"
void LCD_FlushFrameBuffer(uint32_t* fb, uint32_t size) {
// 确保 CPU 写入全部写回 SDRAM
SCB_CleanDCache();
// 启动 LTDC 刷新(或 DMA2D 传输)
LTDC_Reload();
}
void DMA_UpdateFrameBuffer(uint32_t* fb, uint32_t size) {
// DMA 写入 SDRAM 后,使 D-Cache 失效,避免 CPU 读到旧数据
SCB_InvalidateDCache();
// 此时 CPU 访问 fb 将重新从 SDRAM 读取
}
```
### 注意事项
- 全量清理/失效会暂停 CPU 访问,影响实时性。
- 适用于帧缓冲较小或刷新率低于 30fps 的场景。
## 策略二:按区域维护(精准高效)
### 原理
只对修改过的帧缓冲区域执行 Clean 或 Invalidate,利用 `SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr` 函数,按地址和大小操作。这减少了开销,适合频繁局部更新的 UI。
### 配置步骤
1. 确保帧缓冲地址按 32 字节对齐(Cache line 大小)。
2. 在每次局部绘制后,仅清理对应区域。
3. 在 DMA 接收数据后,仅失效对应区域。
### 代码示例
```c
#define CACHE_LINE_SIZE 32
void LCD_DrawRect(uint32_t* fb, int x, int y, int w, int h, uint32_t color) {
// 绘制逻辑(略)
// 计算区域地址和大小,注意对齐
uint32_t addr = (uint32_t)(fb + y * 800 + x); // 假设 800 宽
uint32_t size = w * h * 4; // 32-bit 像素
// 对齐到 cache line
uint32_t aligned_addr = addr & ~(CACHE_LINE_SIZE - 1);
uint32_t aligned_size = (size + (addr - aligned_addr) + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
SCB_CleanDCache_by_Addr((uint32_t*)aligned_addr, aligned_size);
// 触发 LTDC 刷新
}
void DMA_ReceiveData(uint32_t* buf, uint32_t size) {
// DMA 传输完成后
uint32_t addr = (uint32_t)buf;
uint32_t aligned_addr = addr & ~(CACHE_LINE_SIZE - 1);
uint32_t aligned_size = (size + (addr - aligned_addr) + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
SCB_InvalidateDCache_by_Addr((uint32_t*)aligned_addr, aligned_size);
}
```
### 注意事项
- 地址和大小必须对齐到 32 字节,否则会破坏相邻数据。
- 适合局部刷新频繁的 GUI 应用,性能提升明显。
## 策略三:MPU 配置免 Cache(彻底隔离)
### 原理
通过 MPU 将 SDRAM 区域设置为“非缓存”(Normal memory, Non-cacheable),这样 CPU 访问 SDRAM 时直接读写,不经过 D-Cache。彻底避免一致性问题,但牺牲了缓存加速效果。
### 配置步骤
1. 初始化 MPU,配置 SDRAM 区域属性。
2. 使能 MPU。
3. 注意:此区域内的所有访问都无缓存,适合帧缓冲和 DMA 共享区域。
### 代码示例
```c
void MPU_Config(void) {
MPU_Region_InitTypeDef MPU_InitStruct;
HAL_MPU_Disable();
// 配置 SDRAM 区域(假设基址 0xC0000000,大小 8MB)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_8MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 关键:禁用缓存
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
HAL_MPU_Enable(MPU_CONTROL_HRD_MODE);
}
int main(void) {
HAL_Init();
MPU_Config();
// 后续初始化 SDRAM 和 LCD
}
```
### 注意事项
- 禁用缓存后,CPU 访问 SDRAM 速度下降,但通常 LCD 刷新瓶颈不在 CPU。
- 适用于帧缓冲较大且频繁 DMA 更新的场景,如视频播放。
## 总结与选型建议
| 策略 | 开销 | 适用场景 |
|------|------|----------|
| 全量清理/失效 | 高 | 低帧率、简单界面 |
| 按区域维护 | 中 | 局部刷新、GUI 应用 |
| MPU 免缓存 | 低(但无缓存加速) | 高带宽 DMA、视频 |
实际项目中,可结合策略二和策略三:将帧缓冲设为非缓存,但将 CPU 频繁读写的辅助缓冲区设为可缓存。务必在开发初期就规划好内存布局,避免后期修改带来的风险。
## 结语
Cache 一致性是嵌入式图形开发中的经典难题,掌握这三种策略,你就能根据项目需求灵活应对。记住,没有银弹,只有最适合场景的方案。希望本文能助你在 STM32F4 上打造流畅稳定的显示系统。