STM32F4 使用 SDRAM 作为显存时的 Cache 一致性维护策略
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列中,当使用 SDRAM 作为 LCD 显存时,CPU 与 DMA 对同一内存区域的访问会引发 Cache 一致性问题,导致图像撕裂或数据错乱。本文深入剖析 STM32F4 的 Cache 架构,结合 SDRAM 显存的实际场景,给出三种维护策略:关闭 Cache、Clean+Invalidate 操作、以及双缓冲切换。通过原理讲解、配置步骤和完整代码示例,帮助开发者彻底解决显存一致性问题,提升系统稳定性。
# STM32F4 使用 SDRAM 作为显存时的 Cache 一致性维护策略
## 1. 问题背景
在嵌入式图形系统中,SDRAM 常被用作 LCD 的显存(FrameBuffer),以支持高分辨率显示。STM32F4 系列(如 STM32F429/439)内置了 D-Cache(数据缓存)和 I-Cache(指令缓存),当 CPU 写入显存时,数据可能只停留在 Cache 中,而 DMA(如 LTDC 控制器)读取 SDRAM 时却直接访问物理内存,导致两者数据不一致,表现为屏幕显示异常(如花屏、撕裂)。
## 2. 原理剖析
### 2.1 Cache 的工作机制
STM32F4 的 D-Cache 采用写回(Write-back)策略:CPU 写数据时,先写入 Cache,并标记为脏(Dirty),只有在缓存行被替换或显式 Clean 操作时,才将数据写回 SDRAM。而 LTDC(LCD 控制器)通过 DMA 直接读取 SDRAM,不经过 Cache。因此,若 CPU 更新显存后未及时写回,LTDC 读取的仍是旧数据。
### 2.2 一致性问题的触发场景
- CPU 绘制图形到显存 → LTDC 读取显存刷新屏幕
- DMA 从外部存储器拷贝图像到显存 → CPU 读取显存进行图像处理
这两种场景都会因 Cache 与内存数据不同步而产生错误。
## 3. 维护策略
### 策略一:关闭 D-Cache(简单粗暴)
直接禁用 D-Cache,所有内存访问都直接走 SDRAM,彻底避免一致性问题。但会显著降低 CPU 访问内存的性能,尤其是图形绘制时。
**配置方法:**
```c
SCB_DisableDCache();
```
**适用场景:** 对性能要求不高,或调试阶段快速验证。
### 策略二:手动 Clean + Invalidate(推荐)
在关键操作前后,手动维护 Cache。
- **Clean**:将脏缓存行写回 SDRAM,确保 DMA 读取到最新数据。
- **Invalidate**:使缓存行失效,下次 CPU 读取时从 SDRAM 重新加载,避免读到旧数据。
**配置步骤:**
1. 开启 D-Cache:`SCB_EnableDCache()`
2. 在 CPU 写完显存后,调用 `SCB_CleanDCache_by_Addr()` 写回数据。
3. 在 CPU 读取 DMA 写入的数据前,调用 `SCB_InvalidateDCache_by_Addr()` 使缓存失效。
**代码示例:**
```c
// 显存地址和大小(需按 32 字节对齐)
#define FRAME_BUFFER_ADDR 0xC0000000
#define FRAME_BUFFER_SIZE (800*480*2) // RGB565 格式
// CPU 绘制完成后,写回 Cache
void FrameBuffer_Flush(void) {
SCB_CleanDCache_by_Addr((uint32_t*)FRAME_BUFFER_ADDR, FRAME_BUFFER_SIZE);
}
// 在 CPU 读取 DMA 写入的数据前,使缓存失效
void FrameBuffer_Invalidate(void) {
SCB_InvalidateDCache_by_Addr((uint32_t*)FRAME_BUFFER_ADDR, FRAME_BUFFER_SIZE);
}
```
**注意:** 地址和大小必须 32 字节对齐,否则操作无效。
### 策略三:双缓冲 + 切换(高级)
使用两个显存区域,一个用于 CPU 绘制,另一个用于 LTDC 显示。当 CPU 完成绘制后,切换 LTDC 的显存地址,并 Clean 新显存区域。这样避免了在绘制过程中 LTDC 读取到不完整数据,同时减少了 Cache 操作频率。
**配置步骤:**
1. 定义两个缓冲区:`frame_buf[2]`,地址对齐到 32 字节。
2. LTDC 当前显示 `frame_buf[active]`。
3. CPU 绘制到 `frame_buf[1-active]`,绘制完成后 Clean 该区域。
4. 切换 LTDC 显存地址到新缓冲区,并设置 active 翻转。
**代码示例:**
```c
static uint8_t frame_buf[2][FRAME_BUFFER_SIZE] __attribute__((aligned(32)));
static volatile uint8_t active_buf = 0;
void LTDC_SwitchBuffer(uint32_t addr) {
LTDC->Layer1->WHPCR &= ~(LTDC_LxWHPCR_WHSTPOS | LTDC_LxWHPCR_WHSPPOS);
LTDC->Layer1->WHPCR |= ((addr >> 16) & 0xFFF) | ((addr & 0xFFFF) << 16);
// 实际需根据 LTDC 寄存器配置,此处简化
}
void Draw_Scene(void) {
uint8_t *draw_buf = frame_buf[1-active_buf];
// 绘制内容到 draw_buf
SCB_CleanDCache_by_Addr((uint32_t*)draw_buf, FRAME_BUFFER_SIZE);
LTDC_SwitchBuffer((uint32_t)draw_buf);
active_buf ^= 1;
}
```
## 4. 完整示例:结合 LTDC 和 SDRAM
以下代码演示了在 STM32F429 上初始化 SDRAM、开启 Cache、配置 LTDC,并实现显存刷新的完整流程。
```c
#include "stm32f4xx.h"
#define SDRAM_BASE 0xC0000000
#define LCD_WIDTH 800
#define LCD_HEIGHT 480
#define PIXEL_SIZE 2 // RGB565
#define FB_SIZE (LCD_WIDTH * LCD_HEIGHT * PIXEL_SIZE)
// SDRAM 初始化(略,需根据具体硬件配置)
void SDRAM_Init(void) {
// 配置 FMC 控制器,使能 SDRAM 银行
}
// LTDC 初始化(略)
void LTDC_Init(void) {
// 配置时序、层参数,设置显存地址为 SDRAM_BASE
}
int main(void) {
// 系统时钟、GPIO、FMC、LTDC 初始化
SDRAM_Init();
LTDC_Init();
// 开启 D-Cache(注意:必须在 SDRAM 初始化之后)
SCB_EnableDCache();
uint16_t *fb = (uint16_t*)SDRAM_BASE;
while (1) {
// CPU 绘制:填充红色
for (int i = 0; i < FB_SIZE/2; i++) {
fb[i] = 0xF800;
}
// 写回 Cache,确保 LTDC 看到新数据
SCB_CleanDCache_by_Addr((uint32_t*)fb, FB_SIZE);
HAL_Delay(100);
// 填充蓝色
for (int i = 0; i < FB_SIZE/2; i++) {
fb[i] = 0x001F;
}
SCB_CleanDCache_by_Addr((uint32_t*)fb, FB_SIZE);
HAL_Delay(100);
}
}
```
## 5. 注意事项
- **对齐要求**:`SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr` 的地址和长度必须 32 字节对齐,否则操作无效。建议显存缓冲区使用 `__attribute__((aligned(32)))`。
- **性能权衡**:频繁 Clean 整个显存会消耗 CPU 时间。如果只修改部分区域,可只 Clean 修改的行,但需注意行大小(通常 32 字节)。
- **DMA 方向**:如果 DMA 从外设写入显存,CPU 读取前必须 Invalidate,否则可能读到 Cache 中的旧数据。
- **中断安全**:在中断中执行 Cache 操作时,注意优先级和原子性,避免与主循环冲突。
- **调试技巧**:出现花屏时,可先暂时关闭 D-Cache 验证是否一致性导致,再优化策略。
## 6. 总结
STM32F4 的 Cache 一致性是使用 SDRAM 显存时必须面对的问题。通过关闭 Cache、手动 Clean/Invalidate 或双缓冲切换,可以有效解决。推荐使用策略二(手动维护)作为默认方案,兼顾性能与可靠性。对于高性能图形应用,结合双缓冲和局部 Clean 能获得最佳效果。理解 Cache 的工作原理,并根据实际场景选择合适策略,是嵌入式开发者必备的技能。