STM32F4 系列 D-Cache 与 SDRAM 数据一致性问题的三种实战解决方案
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列高性能 MCU 中,D-Cache 能显著提升 CPU 访问 SDRAM 的速度,但同时也引入了数据一致性问题,导致外设(如 DMA、LTDC)与 CPU 看到的数据不一致。本文深入剖析问题根源,并给出三种实战解决方案:Cache 清理/失效、MPU 配置非缓存区域、以及 DMA 双缓冲策略。每种方案均附有原理讲解、配置步骤和完整代码示例,帮助开发者根据场景选择最优策略,确保系统稳定可靠。
# 引言
在 STM32F4 系列(如 STM32F429/469)中,当使用外部 SDRAM 作为帧缓冲或数据缓冲区时,D-Cache 的引入虽然提升了 CPU 访问速度,但也带来了数据一致性问题。CPU 写入的数据可能暂存在 Cache 中,而 DMA 或 LTDC 外设直接访问 SDRAM,导致读取到旧数据;反之,外设写入的数据可能被 Cache 中的旧数据覆盖。本文针对这一痛点,提供三种经过验证的解决方案。
# 问题根源
STM32F4 的 D-Cache 是 4 路组关联、总大小 4KB(部分型号 8KB),以 32 字节为一行。当 CPU 写内存时,数据先写入 Cache(写回策略),仅当 Cache 行被替换或显式清理时才写回 SDRAM。DMA 控制器不经过 Cache,直接访问 SDRAM,因此:
- **CPU 写 → DMA 读**:DMA 可能读到 SDRAM 中的旧数据,因为 CPU 的新数据还在 Cache 中。
- **DMA 写 → CPU 读**:CPU 可能读到 Cache 中的旧数据,因为 DMA 已更新 SDRAM,但 Cache 未失效。
# 解决方案一:Cache 清理与失效(软件控制)
## 原理
通过调用 CMSIS 提供的函数,在关键操作前后手动维护 Cache 一致性。
- `SCB_CleanDCache()`:将 Cache 中脏数据写回 SDRAM。
- `SCB_InvalidateDCache()`:使 Cache 行失效,强制下次读取从 SDRAM 加载。
- `SCB_CleanInvalidateDCache()`:两者结合。
## 配置步骤
1. 启用 D-Cache(默认在启动文件中已启用,若未启用则需在 main 中调用 `SCB_EnableDCache()`)。
2. 在 DMA 传输前,清理包含源数据的 Cache 区域。
3. 在 DMA 传输完成后,失效包含目标数据的 Cache 区域。
## 代码示例
```c
// 假设使用 DMA 从 SDRAM 缓冲区 buf_src 传输数据到外设
#define BUF_SIZE 1024
uint32_t buf_src[BUF_SIZE] __attribute__((section(".sdram")));
uint32_t buf_dst[BUF_SIZE] __attribute__((section(".sdram")));
void dma_transfer_with_cache_maintenance(void) {
// 清理源缓冲区,确保 DMA 能看到 CPU 写入的最新数据
SCB_CleanDCache_by_Addr((uint32_t*)buf_src, BUF_SIZE * sizeof(uint32_t));
// 启动 DMA 传输(此处省略 DMA 配置)
DMA_Start_Transfer(buf_src, buf_dst, BUF_SIZE);
// 等待 DMA 完成
while(DMA_Is_Busy());
// 失效目标缓冲区,使 CPU 读取时从 SDRAM 重新加载
SCB_InvalidateDCache_by_Addr((uint32_t*)buf_dst, BUF_SIZE * sizeof(uint32_t));
}
```
## 注意事项
- 地址必须 32 字节对齐,长度建议为 32 的倍数,否则需手动处理边界。
- 频繁调用会影响性能,适合低频操作。
# 解决方案二:MPU 配置非缓存区域(硬件隔离)
## 原理
利用 MPU(内存保护单元)将 SDRAM 区域配置为“非缓存”属性(如 `NORMAL, NON-CACHEABLE`),使 CPU 访问该区域时直接读写 SDRAM,绕过 D-Cache。这样 DMA 和 CPU 看到的数据始终一致,无需软件干预。
## 配置步骤
1. 定义 MPU 区域,覆盖 SDRAM 地址范围(例如 0xC0000000,大小 8MB)。
2. 设置属性:`MPU_REGION_NORMAL_NON_CACHEABLE`(或 `MPU_ACCESS_BUFFERABLE`)。
3. 使能 MPU 和区域。
## 代码示例
```c
void MPU_Config_SDRAM_NonCacheable(void) {
MPU_Region_InitTypeDef MPU_InitStruct;
// 禁用 MPU 进行配置
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_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);
// 使能 MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
```
## 注意事项
- 非缓存区域会降低 CPU 访问速度,但保证一致性,适合帧缓冲等高频访问场景,可接受性能损失。
- 需确保 MPU 区域与 SDRAM 物理地址完全匹配,避免覆盖其他外设。
# 解决方案三:DMA 双缓冲策略(架构优化)
## 原理
为 DMA 和 CPU 分配独立的缓冲区,通过交替使用避免同时访问同一内存区域。例如,CPU 写入缓冲区 A,DMA 从缓冲区 B 读取;当 DMA 完成,交换角色。这样 CPU 和 DMA 永远不会同时操作同一块内存,从根源上消除一致性问题。
## 配置步骤
1. 在 SDRAM 中分配两个缓冲区(A 和 B)。
2. 使用 DMA 双缓冲模式(如 STM32 的 DMA2D 或通用 DMA 的 double-buffer 功能)。
3. 在 DMA 传输完成中断中切换缓冲区指针。
## 代码示例
```c
#define BUF_SIZE 1024
uint32_t buf_A[BUF_SIZE] __attribute__((section(".sdram")));
uint32_t buf_B[BUF_SIZE] __attribute__((section(".sdram")));
volatile uint32_t *active_buf = buf_A;
volatile uint32_t *dma_buf = buf_B;
void DMA_Transfer_Complete_Callback(void) {
// 交换缓冲区
uint32_t *tmp = active_buf;
active_buf = dma_buf;
dma_buf = tmp;
// 启动下一次 DMA 传输,使用新的 dma_buf
DMA_Start_Transfer(dma_buf, BUF_SIZE);
}
void main_loop(void) {
// CPU 写入 active_buf,无需 Cache 维护
process_data(active_buf);
}
```
## 注意事项
- 需要额外的内存空间,但避免了 Cache 操作,性能最优。
- 适用于数据流场景(如音频、图像采集),不适合随机访问。
# 总结
| 方案 | 优点 | 缺点 | 适用场景 |
|------|------|------|----------|
| Cache 清理/失效 | 无需额外内存,实现简单 | 软件开销大,需注意对齐 | 低频小数据量传输 |
| MPU 非缓存 | 硬件保证一致性,无需软件干预 | 降低 CPU 访问速度 | 高频访问的帧缓冲 |
| DMA 双缓冲 | 性能最优,无 Cache 操作 | 内存占用翻倍,逻辑复杂 | 连续数据流处理 |
开发者应根据实际需求选择:若追求简单,用方案一;若性能敏感且可接受非缓存,用方案二;若数据流大且内存充足,用方案三。理解 D-Cache 与 SDRAM 的交互机制,是解决嵌入式系统数据一致性问题的关键。