STM32F4 D-Cache 与 SDRAM 数据一致性实战:从踩坑到 flush/invalidate 策略
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列中,当使用 D-Cache 和外部 SDRAM 时,数据一致性问题常导致难以排查的故障。本文从实际踩坑出发,深入解析 D-Cache 的工作原理,对比 Cache 写回与写通策略,并给出基于 SCB_DisableDCache/EnableDCache 和 Clean/Invalidate 操作的完整解决方案。通过具体代码示例,演示如何正确配置 MPU 区域、在 DMA 传输前后执行 flush 与 invalidate,确保数据一致性,避免随机崩溃和显示错乱。
# 引言
在 STM32F4 系列(如 STM32F429/439)中,D-Cache 的引入显著提升了 CPU 访问内部 SRAM 的速度,但当外部 SDRAM 介入时,数据一致性(Cache Coherency)问题便成为开发者的噩梦。本文以一个真实项目为例,讲述因 D-Cache 未正确处理导致的显示花屏、数据错乱问题,并给出系统性的 flush/invalidate 策略。
# D-Cache 与 SDRAM 的冲突根源
## 1. Cache 的工作原理
D-Cache 是 CPU 与主存(如 SDRAM)之间的高速缓存,以 32 字节(或 64 字节)为一行(Line)存储数据。当 CPU 读取数据时,若命中 Cache,则直接返回;否则从主存加载整个 Cache Line 到 Cache。写入时,根据策略不同,可能只更新 Cache(写回 Write-back)或同时更新主存(写通 Write-through)。
## 2. 问题场景
- **CPU 写数据到 SDRAM,但 DMA 外设读取时拿到旧数据**:因为 CPU 写入的数据可能还停留在 Cache 中,尚未写回 SDRAM。
- **DMA 写入 SDRAM,但 CPU 读取时拿到旧数据**:因为 Cache 中保留了旧数据,CPU 读取时命中 Cache,未感知 DMA 的更新。
## 3. 为什么 STM32F4 默认关闭 D-Cache?
STM32F4 的 D-Cache 默认是关闭的,因为其设计初衷是用于内部 SRAM 加速,而外部存储器(如 SDRAM)的时序和共享访问(如 DMA)容易引发一致性问题。开启后,必须由软件管理一致性。
# 实战踩坑记录
## 项目背景
使用 STM32F429 驱动 4.3 寸 RGB 屏,显存位于外部 SDRAM(地址 0xC0000000),同时使用 DMA2D 进行图形加速,LTDC 控制器直接读取 SDRAM 刷新屏幕。
## 现象
- 屏幕显示花屏,部分区域出现残影。
- 通过 CPU 写入的图形数据,DMA2D 读取时偶尔错误。
- 调试时发现,关闭 D-Cache 后一切正常,但性能下降明显。
## 根因分析
1. CPU 写入显存数据到 SDRAM,但数据留在 D-Cache 中,LTDC 读取 SDRAM 时未看到最新数据。
2. DMA2D 从 SDRAM 读取源数据时,CPU 之前写入的数据可能未写回,导致 DMA 拿到旧数据。
3. 反之,DMA2D 写入结果到 SDRAM,但 CPU 读取时命中 Cache 中的旧数据。
# 解决方案:flush 与 invalidate 策略
## 1. 配置 MPU 区域
首先,将 SDRAM 区域配置为 Write-back 模式,并允许 Cache 使用。使用 `MPU_Config()` 函数设置区域属性。
```c
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
__HAL_RCC_MPU_CLK_ENABLE();
HAL_MPU_Disable();
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_4MB; // 根据 SDRAM 大小调整
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // Write-back, no write allocate
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
```
## 2. 开启 D-Cache
在系统初始化时,调用 `SCB_EnableDCache()` 开启 D-Cache。注意,开启后所有内存访问都经过 Cache,包括 SDRAM。
```c
SCB_EnableDCache();
```
## 3. 数据一致性操作
### 3.1 CPU 写数据到 SDRAM,供外设(如 DMA2D、LTDC)读取
在 CPU 写入完成后,必须调用 `SCB_CleanDCache()` 或 `SCB_CleanDCache_by_Addr()` 将 Cache 中的数据写回 SDRAM。
```c
// 假设写入 100 字节到 0xC0000000
uint32_t addr = 0xC0000000;
for (int i = 0; i < 100; i++) {
*(uint8_t *)(addr + i) = i;
}
// 将 Cache 中该区域的数据写回 SDRAM
SCB_CleanDCache_by_Addr((uint32_t *)addr, 100);
```
### 3.2 外设(如 DMA)写入数据到 SDRAM,供 CPU 读取
在 DMA 传输完成后,必须调用 `SCB_InvalidateDCache()` 或 `SCB_InvalidateDCache_by_Addr()` 使 Cache 中的旧数据失效,强制 CPU 从 SDRAM 重新加载。
```c
// DMA 传输完成中断中
uint32_t addr = 0xC0000000;
SCB_InvalidateDCache_by_Addr((uint32_t *)addr, 100);
// 现在 CPU 读取 addr 时,会从 SDRAM 获取最新数据
```
### 3.3 全量操作
如果数据量较大或区域不连续,可以直接使用全量操作(注意性能开销):
```c
SCB_CleanDCache(); // 写回所有脏数据
SCB_InvalidateDCache(); // 使所有 Cache 行无效
```
## 4. 完整示例:DMA 传输与 Cache 维护
以下是一个典型场景:CPU 准备数据,通过 DMA 发送到 SDRAM,然后 DMA 从 SDRAM 读取结果。
```c
// 缓冲区地址(SDRAM)
#define BUF_ADDR 0xC0000000
#define BUF_SIZE 1024
void DMA_Transfer_Example(void)
{
// 1. CPU 写入数据到 SDRAM
for (int i = 0; i < BUF_SIZE; i++) {
*((uint8_t *)BUF_ADDR + i) = i;
}
// 2. 写回 Cache,确保数据在 SDRAM 中
SCB_CleanDCache_by_Addr((uint32_t *)BUF_ADDR, BUF_SIZE);
// 3. 启动 DMA 读取 SDRAM 数据(例如 DMA2D 或普通 DMA)
// ... 配置 DMA 并启动
// 4. 等待 DMA 完成(中断或轮询)
// ...
// 5. 使 Cache 失效,以便 CPU 读取 DMA 写入的新数据
SCB_InvalidateDCache_by_Addr((uint32_t *)BUF_ADDR, BUF_SIZE);
// 6. 现在可以安全读取 BUF_ADDR 的数据
}
```
# 注意事项
- **地址对齐**:`SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr` 的地址必须 32 字节对齐,长度也建议对齐到 32 的倍数,否则可能操作不完整。
- **性能权衡**:频繁的 flush/invalidate 会降低性能,建议在批量操作后执行,而非每次小写入。
- **DMA 描述符**:如果使用 DMA 描述符,描述符本身也可能被 Cache 缓存,需要同样处理。
- **中断安全**:在中断中执行 Cache 操作时,注意优先级和嵌套,避免死锁。
- **调试技巧**:若怀疑一致性问题,可临时关闭 D-Cache 验证,但最终必须正确实现维护。
# 总结
STM32F4 的 D-Cache 是一把双刃剑,正确使用能大幅提升性能,但忽视一致性则会导致随机故障。通过配置 MPU 区域为 Write-back,并在关键操作前后调用 `SCB_CleanDCache` 和 `SCB_InvalidateDCache`,可以彻底解决 SDRAM 与 Cache 的数据冲突。记住:**CPU 写后要 Clean,外设写后要 Invalidate**。希望本文能帮助你少走弯路,写出稳定高效的嵌入式代码。