# 一、问题根源: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 时序)。 ## 配置步骤 1. 在系统初始化时(如 main 函数开头)调用 `SCB_DisableDCache()`。 2. 确保所有 DMA 和 CPU 访问均在禁用后执行。 ## 代码示例 ```c #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 的倍数。 ## 配置步骤 1. 保持 D-Cache 启用(默认开启)。 2. 在 CPU 写入 SDRAM 缓冲区后,调用 Clean 函数确保数据写回。 3. 在 DMA 写入 SDRAM 缓冲区后,调用 Invalidate 函数使 Cache 失效。 ## 代码示例 ```c #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 提供区域划分,可精确控制哪些地址段缓存。 ## 配置步骤 1. 使能 MPU(`MPU_Control` 寄存器)。 2. 配置一个区域,基地址为 SDRAM 起始地址,大小为 SDRAM 容量,属性设为 Normal, Non-cacheable。 3. 使能该区域,并设置优先级。 ## 代码示例 ```c #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 交互的边界情况。