引言

在 STM32F4 系列(如 STM32F429/439)中,Cortex-M4 内核集成了可选的 D-Cache(数据缓存),用于加速对内部 SRAM 和外部存储器的访问。然而,当外部 SDRAM 被用作帧缓冲或数据交换区,且 DMA 控制器直接读写 SDRAM 时,D-Cache 与 SDRAM 之间的数据一致性成为棘手问题。CPU 写入的数据可能暂存于 Cache,而 DMA 读取的是 SDRAM 中的旧数据,反之亦然。本文基于 STM32F429 开发板,实测三种强制刷新策略,并给出工程建议。

D-Cache 与 SDRAM 的冲突根源

Cortex-M4 的 D-Cache 采用写回(write-back)策略:CPU 写操作只更新 Cache 行(通常 32 字节),并不会立即写回 SDRAM。当 DMA 外设(如 LTDC、SDIO)直接访问 SDRAM 时,它看不到 Cache 中的脏数据,导致数据不一致。同理,DMA 写入 SDRAM 后,Cache 中可能残留旧数据,CPU 读取时命中 Cache 得到过期值。

解决思路有两种:

  • 保证 CPU 与 DMA 访问同一内存区域时,Cache 数据与 SDRAM 同步。
  • 或者干脆绕过 Cache,直接操作 SDRAM。

三种强制刷新策略实测

策略一:全无效(Clean & Invalidate Entire D-Cache)

原理:在每次 DMA 传输前后,调用 SCB_CleanDCache()SCB_CleanInvalidateDCache() 将整个 D-Cache 写回并失效。这是最简单粗暴的方法,但代价高,因为会清空所有缓存,导致后续 CPU 访问性能下降。

代码示例

// 在 DMA 写 SDRAM 前,确保 CPU 数据已写回
SCB_CleanDCache();
// 启动 DMA 传输
DMA_Start();
// 等待 DMA 完成
while(DMA_IsBusy());
// 使 Cache 失效,避免读到旧数据
SCB_InvalidateDCache();

实测结果

  • 功能正确,无数据错误。
  • 性能损耗:每次操作约 10-20 微秒(取决于 Cache 大小和命中率),在高速刷新场景(如 LCD 显示)中不可接受。

策略二:按地址无效(Clean/Invalidate by Address)

原理:使用 SCB_CleanDCache_by_Addr()SCB_InvalidateDCache_by_Addr() 仅对目标内存区域(通常对齐到 32 字节)进行操作。这需要确保地址对齐,否则会触发断言或无效操作。

代码示例

#define BUFFER_SIZE 1024
uint32_t buffer[BUFFER_SIZE] __attribute__((aligned(32)));

// CPU 写数据到 buffer
fill_buffer(buffer);
// 写回该区域
SCB_CleanDCache_by_Addr((uint32_t*)buffer, BUFFER_SIZE * sizeof(uint32_t));
// DMA 从 SDRAM 读取
DMA_Read_SDRAM(buffer);
// 使该区域失效,以便 CPU 读取最新 DMA 数据
SCB_InvalidateDCache_by_Addr((uint32_t*)buffer, BUFFER_SIZE * sizeof(uint32_t));

实测结果

  • 正确性良好,但需注意地址对齐和长度对齐(32 字节倍数)。
  • 性能:开销与操作区域大小成正比,但远小于全无效。实测 1KB 区域操作约 2-3 微秒,适合中等频率的数据交换。

策略三:关闭 D-Cache(保守方案)

原理:在系统初始化时直接禁用 D-Cache(SCB_DisableDCache()),所有对 SDRAM 的访问都直接走总线,无缓存一致性烦恼。但代价是 CPU 访问 SDRAM 性能下降(因为 SDRAM 本身较慢)。

代码示例

void SystemInit(void) {
    // ... 其他初始化
    SCB_DisableDCache();  // 关闭 D-Cache
}

实测结果

  • 绝对安全,无一致性问题。
  • 性能:CPU 访问 SDRAM 速度降低约 30-50%(取决于访问模式),但若 SDRAM 主要用于 DMA 传输(如显示缓冲),CPU 访问频率不高,此方案可行。

性能对比与适用场景

| 策略 | 正确性 | 性能开销 | 适用场景 | |------|--------|----------|----------| | 全无效 | 高 | 高(10-20us/次) | 低频大块数据传输,如固件升级 | | 按地址无效 | 高 | 中(2-3us/KB) | 高频中小块数据,如音频流、网络包 | | 关闭 D-Cache | 高 | 低(但CPU访问慢) | 显示缓冲、DMA为主,CPU访问少 |

注意事项

  • 对齐要求SCB_CleanDCache_by_Addr 的地址和长度必须是 32 字节对齐,否则结果不可预测。建议使用 __attribute__((aligned(32))) 定义缓冲区。
  • DMA 配置:确保 DMA 使用内存地址与 Cache 操作区域一致,避免遗漏。
  • 中断上下文:在中断中执行 Cache 操作需谨慎,可能阻塞系统,建议在任务级处理。
  • 编译器优化:使用 volatile 或内存屏障(__DSB())确保操作顺序。
  • 多核/多主控:若使用 LTDC 等外设,需考虑其是否支持 Cache 一致性(通常不支持),必须手动维护。

总结

三种强制刷新策略各有优劣。全无效简单但昂贵,按地址无效平衡了性能与复杂度,关闭 D-Cache 则适合特定场景。实际项目中,建议优先采用按地址无效,并配合良好的内存布局(如将 DMA 缓冲区独立分区)来最大化性能。嵌入式开发中,缓存一致性是隐蔽的“杀手”,理解原理并实测验证是保证系统可靠性的关键。