STM32F4 D-Cache 与 SDRAM 数据一致性:一次 DMA 传输异常的实测排查与修复
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列高性能 MCU 上,当使用 D-Cache 与外部 SDRAM 进行数据交互时,极易遭遇数据一致性问题,导致 DMA 传输数据错乱或显示异常。本文基于实际项目,深入剖析 D-Cache 的工作原理、一致性问题产生的根源,并通过实测波形与代码示例,详细展示从现象到修复的完整排查过程。你将学到如何正确配置 MPU 区域、使用 Cache 清理/无效化操作,以及编写健壮的 DMA 传输代码,彻底规避此类隐患。
# 一、问题现象:SDRAM 中的图像数据莫名“花屏”
在某基于 STM32F429 的图形显示项目中,使用 LTDC 从外部 SDRAM 读取帧缓冲数据。系统运行一段时间后,屏幕出现随机条纹和错位像素,且仅在开启 D-Cache 时出现。关闭 D-Cache 后问题消失,但性能下降明显。这强烈暗示 D-Cache 与 SDRAM 之间存在数据一致性问题。
## 1.1 硬件环境
- MCU:STM32F429ZIT6(Cortex-M4,带 16KB D-Cache)
- 外部 SDRAM:W9825G6KH,32MB,16-bit 总线,挂载在 FMC 的 Bank1
- 显示接口:LTDC,RGB565,分辨率 800x480
- 数据传输:CPU 写入图像数据到 SDRAM,LTDC 通过 DMA 读取 SDRAM 显示
## 1.2 关键代码(问题版本)
```c
// 初始化 SDRAM 和 LTDC 后,开启 D-Cache
SCB_EnableDCache();
// 在 SDRAM 中写入图像数据(例如填充颜色)
void FillSDRAM(uint32_t addr, uint32_t size, uint16_t color) {
uint16_t *p = (uint16_t *)addr;
for (uint32_t i = 0; i < size; i++) {
p[i] = color;
}
}
// 主循环中调用
FillSDRAM(0xC0000000, 800*480, 0xFFFF); // 期望全白
```
现象:屏幕显示并非全白,而是出现大量随机条纹。
# 二、原理剖析:D-Cache 与 SDRAM 的“各自为政”
## 2.1 D-Cache 的工作机制
Cortex-M4 的 D-Cache 是 4 路组相联,缓存行大小为 32 字节。当 CPU 访问内存地址时,首先查找 Cache,命中则直接返回,未命中则从主存(SDRAM)加载整个缓存行到 Cache。
## 2.2 一致性问题的根源
- **写操作**:CPU 写数据时,若采用写回(Write-back)策略,数据先写入 Cache,标记为脏(Dirty),但不会立即更新 SDRAM。只有当缓存行被替换或显式清理时,才写回 SDRAM。
- **读操作**:CPU 读数据时,若 SDRAM 中的数据被 DMA 或其他外设修改,而 Cache 中仍有旧副本,则 CPU 会读到过时数据。
在本例中,CPU 通过 `FillSDRAM` 写入图像数据,数据可能残留在 Cache 中。LTDC 的 DMA 控制器直接读取 SDRAM 地址,但此时 SDRAM 中可能还是旧数据或未写回的部分数据,导致显示花屏。
## 2.3 MPU 的作用
默认情况下,STM32F4 的 D-Cache 对所有内存区域都是开启的,且使用写回策略。我们可以通过 MPU 将 SDRAM 区域配置为“写透”(Write-through)或“不可缓存”(Non-cacheable),以规避一致性问题,但这会牺牲性能。更精细的做法是保持缓存,但在关键操作前后手动维护一致性。
# 三、实测排查:定位问题根源
## 3.1 复现与观察
- 在 `FillSDRAM` 后,立即读取 SDRAM 地址并打印,发现数据正确(因为 CPU 读 Cache 命中)。
- 但通过调试器查看 SDRAM 内存窗口,发现大量地址数据未更新。
- 使用逻辑分析仪抓取 FMC 总线,发现写入 SDRAM 的数据不连续,且部分写入被延迟。
## 3.2 验证假设
编写测试代码:在 `FillSDRAM` 后,调用 `SCB_CleanDCache()` 强制写回,再观察显示。结果屏幕正常。这证实了问题正是 D-Cache 未写回所致。
# 四、解决方案:正确维护 D-Cache 一致性
## 4.1 方案一:配置 MPU 将 SDRAM 区域设为写透(推荐)
```c
void MPU_Config(void) {
MPU_Region_InitTypeDef MPU_InitStruct;
// 禁用 MPU
HAL_MPU_Disable();
// 配置 SDRAM 区域(地址 0xC0000000,大小 32MB)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xC0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; // 允许缓存
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // 写透,读分配
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
// 使能 MPU
HAL_MPU_Enable(MPU_CONTROL_PRIVILEGED_DEFAULT);
}
```
注意:设置 `TypeExtField = MPU_TEX_LEVEL1` 且 `IsCacheable = 1`,`IsBufferable = 0`,对应写透策略。这样 CPU 写操作会同时更新 Cache 和 SDRAM,但读操作仍可命中 Cache,性能损失较小。
## 4.2 方案二:手动维护 Cache(适用于 DMA 频繁场景)
在每次 CPU 写 SDRAM 后,调用 `SCB_CleanDCache()` 或按地址范围清理;在 DMA 写入 SDRAM 后,CPU 读取前,调用 `SCB_InvalidateDCache()`。
```c
// 示例:CPU 写数据到 SDRAM,然后通知 DMA 读取
void WriteData_ToSDRAM(uint32_t addr, uint32_t len) {
// 写入数据...
SCB_CleanDCache_by_Addr((uint32_t*)addr, len); // 写回 SDRAM
}
// 示例:DMA 从 SDRAM 读取数据后,CPU 读取前
void ReadData_FromSDRAM(uint32_t addr, uint32_t len) {
SCB_InvalidateDCache_by_Addr((uint32_t*)addr, len); // 使 Cache 失效
// 然后读取 addr 数据...
}
```
注意:`SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr` 的地址参数需要 32 字节对齐,长度也需是 32 的倍数,否则需手动调整。
# 五、完整修复代码示例(基于 HAL 库)
```c
// 初始化函数中调用 MPU_Config(),然后使能 D-Cache
void SystemClock_Config(void); // 略
int main(void) {
HAL_Init();
SystemClock_Config();
// 配置 MPU(在使能 Cache 之前)
MPU_Config();
// 使能 D-Cache(注意:MPU 配置必须在使能 Cache 之前完成)
SCB_EnableDCache();
// 初始化 SDRAM、LTDC 等...
// 填充图像数据
FillSDRAM(0xC0000000, 800*480, 0xFFFF);
// 由于 MPU 配置为写透,无需手动清理,但为保险可调用一次
SCB_CleanDCache();
while (1) {
// 主循环...
}
}
```
# 六、注意事项与经验总结
- **MPU 配置必须在使能 D-Cache 之前**,否则无效。
- 如果使用手动维护方式,务必注意地址对齐和长度对齐(32 字节)。
- 对于 DMA 双向传输(如 ADC 采集到 SDRAM),建议使用 MPU 配置为“不可缓存”或“写透”,避免频繁手动操作。
- 调试时,可通过查看 Cache 操作后的 SDRAM 内存窗口验证一致性。
- 在 FreeRTOS 等 RTOS 环境中,多任务访问共享 SDRAM 时,需确保任务间同步,防止 Cache 操作交错。
通过本次实测,我们深刻理解了 D-Cache 与 SDRAM 的一致性机制,并掌握了两种有效修复方法。推荐优先使用 MPU 配置写透,兼顾性能与正确性。希望本文能帮助你在 STM32F4 开发中避免同类“坑”。