一、问题根源:D-Cache 与 SDRAM 的“缓存陷阱”
STM32F4 系列(如 STM32F407、F429)内置 4KB 或 8KB 的 D-Cache,用于加速 CPU 对片内 SRAM 的访问。但当外部 SDRAM 被映射到内存空间(如 Bank1 的 0xC0000000 地址段)时,D-Cache 默认会缓存 SDRAM 数据。这引发两个典型问题:
- CPU 写入后,DMA 读取到旧数据:CPU 修改 SDRAM 中的缓冲区,但数据仍留在 Cache 中,尚未写回 SDRAM,DMA 外设直接访问 SDRAM 时读到的是旧值。
- DMA 写入后,CPU 读到脏数据:DMA 从外设接收数据写入 SDRAM,但 Cache 中仍保留着旧副本,CPU 读取时命中 Cache,得到过时数据。
根本原因:D-Cache 以 32 字节(Cache Line)为单位进行读写,而 SDRAM 的访问延迟远高于片内 SRAM,导致缓存策略与 DMA 的“非缓存”访问路径不一致。
二、解法一:直接禁用 D-Cache(简单粗暴)
原理
通过设置 SCB->SACR 寄存器中的 DCACHE 位,关闭整个 D-Cache。所有内存访问直接落到 SDRAM,彻底消除一致性问题,但代价是 CPU 访问 SDRAM 的性能下降(每次读写都需等待 SDRAM 时序)。
配置步骤
- 在系统初始化时(如 main 函数开头)调用
SCB_DisableDCache()。 - 确保所有 DMA 和 CPU 访问均在禁用后执行。
代码示例
#include "stm32f4xx.h"
void SystemInit_CacheDisable(void) {
// 关闭 D-Cache(同时关闭 I-Cache 可选)
SCB_DisableDCache();
// 可选:关闭 I-Cache 以简化调试
// SCB_DisableICache();
}
int main(void) {
SystemInit_CacheDisable();
// 初始化 SDRAM 和 DMA...
// 所有数据访问直接操作 SDRAM
while(1) {
// 业务逻辑
}
}
优缺点
- 优点:实现简单,无需考虑 Cache 维护,适合对性能要求不高的应用。
- 缺点:SDRAM 访问性能显著下降(可能降低 30%-50%),且无法利用 Cache 加速。
三、解法二:软件维护 Cache 行(精细控制)
原理
保持 D-Cache 启用,但在 DMA 传输前后,手动执行 Cache 清理(Clean)或失效(Invalidate)操作。使用 CMSIS 提供的函数:
-
SCB_CleanDCache_by_Addr(uint32_t *addr, int32_t dsize):将指定地址区域的 Cache 行写回 SDRAM。 -
SCB_InvalidateDCache_by_Addr(uint32_t *addr, int32_t dsize):使指定地址区域的 Cache 行失效,下次读取强制从 SDRAM 加载。
注意:地址必须按 32 字节对齐,大小需向上取整到 32 的倍数。
配置步骤
- 保持 D-Cache 启用(默认开启)。
- 在 CPU 写入 SDRAM 缓冲区后,调用 Clean 函数确保数据写回。
- 在 DMA 写入 SDRAM 缓冲区后,调用 Invalidate 函数使 Cache 失效。
代码示例
#include "stm32f4xx.h"
#define BUFFER_SIZE 1024 // 字节数
#define CACHE_LINE_SIZE 32
// 对齐到 32 字节的缓冲区(需在链接脚本中放置到 SDRAM 区域)
__attribute__((aligned(32))) uint8_t sdram_buffer[BUFFER_SIZE];
void DMA_Read_From_SDRAM(uint8_t *dest, uint32_t len) {
// 使 Cache 失效,确保读取最新数据(DMA 可能已写入)
SCB_InvalidateDCache_by_Addr((uint32_t *)sdram_buffer, len);
// 执行 DMA 读取(从 SDRAM 到外设)
// ... DMA 配置和启动 ...
}
void DMA_Write_To_SDRAM(uint8_t *src, uint32_t len) {
// 执行 DMA 写入(从外设到 SDRAM)
// ... DMA 配置和启动 ...
// 等待 DMA 完成
// 清理 Cache,将 CPU 可能缓存的旧数据写回(但此处 CPU 未写,可省略)
// 但为了安全,可清理整个区域
SCB_CleanDCache_by_Addr((uint32_t *)sdram_buffer, len);
}
int main(void) {
// 初始化 SDRAM 和 DMA...
// 注意:D-Cache 保持启用
while(1) {
// 使用缓冲区
}
}
注意事项
- 必须确保缓冲区地址和大小对齐到 32 字节,否则函数内部会断言或行为未定义。
- 频繁调用 Cache 维护函数会增加开销,但比禁用 Cache 性能好。
- 若多个缓冲区交叉,需分别维护。
四、解法三:使用 MPU 配置内存属性(硬件级隔离)
原理
利用 Cortex-M4 的 MPU(内存保护单元),将 SDRAM 区域配置为“非缓存”(Normal memory, Non-cacheable)属性。这样,CPU 访问 SDRAM 时绕过 D-Cache,而片内 SRAM 仍可享受缓存加速。MPU 提供区域划分,可精确控制哪些地址段缓存。
配置步骤
- 使能 MPU(
MPU_Control寄存器)。 - 配置一个区域,基地址为 SDRAM 起始地址,大小为 SDRAM 容量,属性设为 Normal, Non-cacheable。
- 使能该区域,并设置优先级。
代码示例
#include "stm32f4xx.h"
#define SDRAM_BASE_ADDR 0xC0000000
#define SDRAM_SIZE 0x2000000 // 32MB,需按实际调整
void MPU_Config_SDRAM_NonCacheable(void) {
// 1. 禁用 MPU
MPU->CTRL = 0;
// 2. 配置区域 0:SDRAM
MPU->RNR = 0; // 使用区域 0
MPU->RBAR = SDRAM_BASE_ADDR;
// 设置属性:Normal memory, Non-cacheable, 可读写,执行权限可设
MPU->RASR = (0x0 << 0) | // 禁用指令访问(可选)
(0x1 << 1) | // 允许读
(0x1 << 2) | // 允许写
(0x0 << 3) | // 非可执行(可选)
(0x1 << 4) | // 允许访问(Enable)
(0x0 << 5) | // 非缓存(TEX=0, C=0, B=0)
(0x0 << 8) | // 区域大小编码,需计算
(0x0 << 16) | // 子区域禁用
(0x0 << 24); // 优先级
// 计算区域大小编码:2^(SIZE+1) 字节,SIZE = log2(size)-1
// 例如 32MB => SIZE = 25-1 = 24,但需按 MPU 规则编码
// 简化:使用宏或手动计算
uint32_t size_code = 24; // 假设 32MB,实际需根据 SDRAM 大小调整
MPU->RASR |= (size_code << 1) | (1 << 0); // 设置 SIZE 和 ENABLE
// 3. 使能 MPU,使用默认内存映射作为背景
MPU->CTRL = (1 << 0) | (1 << 2); // ENABLE=1, PRIVDEFENA=1
// 4. 内存屏障,确保配置生效
__DSB();
__ISB();
}
int main(void) {
// 初始化 SDRAM 控制器...
MPU_Config_SDRAM_NonCacheable();
// 现在 SDRAM 区域访问不经过 D-Cache,DMA 和 CPU 直接操作 SDRAM
while(1) {
// 业务逻辑
}
}
注意事项
- 区域大小必须是 2 的幂,且基地址需对齐到区域大小。
- 若 SDRAM 容量不是 2 的幂,需拆分为多个区域。
- MPU 配置后,所有对该区域的访问均绕过 Cache,但片内 SRAM 仍可缓存,性能均衡。
五、三种方案对比与选型建议
| 方案 | 性能影响 | 实现复杂度 | 适用场景 | |------|----------|------------|----------| | 禁用 D-Cache | 高(SDRAM 访问慢) | 极低 | 原型验证、低速率应用 | | 软件维护 Cache | 中(维护开销) | 中 | 需要缓存加速,但 DMA 交互频繁且可控 | | MPU 配置非缓存 | 低(仅 SDRAM 无缓存) | 高 | 高性能应用,需同时使用 SRAM 缓存和 SDRAM 大容量存储 |
选型建议:
- 若项目对实时性要求不高,且 SDRAM 访问频率低,选方案一。
- 若 SDRAM 缓冲区固定且 DMA 操作规律,选方案二,可保留缓存加速。
- 若系统复杂,多个外设访问 SDRAM,且需保证一致性,选方案三,硬件隔离最可靠。
六、总结
D-Cache 与 SDRAM 的一致性是 STM32F4 开发中的经典问题。理解 Cache 行机制和 DMA 访问路径是解决关键。三种解法各有优劣,开发者应根据实际需求权衡。在实际工程中,推荐优先考虑 MPU 方案,它从硬件层面隔离了缓存,避免软件维护的遗漏风险,同时保留 SRAM 的缓存性能。无论选择哪种,务必在初始化阶段就明确配置,并测试 DMA 与 CPU 交互的边界情况。