# 引言 STM32F4 系列(如 STM32F429、STM32F407)凭借其强大的性能和丰富的外设,被广泛应用于嵌入式视觉、音频处理等场景。当程序规模增大,内部 SRAM 不够用时,外扩 SDRAM 成为常见选择。然而,很多开发者发现:**数据写入 SDRAM 后,读回却不对;或者 DMA 搬运的数据总是“差一拍”**。这些问题背后,往往隐藏着 Cache 一致性的陷阱。 # 1. 原理剖析:Cache 与 SDRAM 的“爱恨情仇” ## 1.1 Cortex-M4 的 Cache 架构 STM32F4 基于 Cortex-M4 内核,部分型号(如 STM32F429/439)带有 4KB I-Cache 和 4KB D-Cache。Cache 是 CPU 与主存(包括 SDRAM)之间的高速缓存,用于减少访问延迟。 - **D-Cache**:缓存数据读写,默认是写回(write-back)策略,即数据先写入 Cache,标记为脏(dirty),直到被替换或显式 clean 才写回主存。 - **I-Cache**:缓存指令,通常无需手动维护。 ## 1.2 问题根源 当 CPU 通过 AHB 总线访问 SDRAM 时,如果开启了 D-Cache,CPU 读写的数据会先经过 Cache。这导致两个典型问题: - **写不一致**:CPU 写入 SDRAM 的数据可能还滞留在 Cache 中,外部设备(如 DMA)直接访问 SDRAM 时读不到最新数据。 - **读不一致**:DMA 将新数据写入 SDRAM,但 CPU 读取时命中了 Cache 中的旧数据,导致数据陈旧。 ## 1.3 为什么 SDRAM 尤其容易触发? SDRAM 访问速度远慢于内部 SRAM,且通常位于外部存储器映射区(0xC0000000 附近)。当程序频繁读写 SDRAM 时,Cache 命中率低,替换频繁,脏数据写回时机不可控,问题更容易暴露。 # 2. 排查步骤:定位 Cache 一致性 Bug ## 2.1 典型现象 - 程序运行结果随机出错,但调试时(关闭优化)又正常。 - 使用 DMA 从 SDRAM 搬运数据到外设,数据错位或全零。 - 多任务环境下,一个任务写入的数据,另一个任务读不到。 ## 2.2 快速验证方法 1. **关闭 D-Cache**:在启动代码中注释掉 `SCB_EnableDCache()`,观察问题是否消失。若消失,则基本确定是 Cache 一致性问题。 2. **使用逻辑分析仪**:抓取 SDRAM 的读写时序,对比实际数据。 3. **打印调试**:在关键读写点前后,强制 Clean 或 Invalidate,看数据是否恢复。 ## 2.3 确认 Cache 配置 检查是否启用了 MPU 并正确配置了 SDRAM 区域的 Cache 策略。默认情况下,Cortex-M4 的 D-Cache 对所有可缓存区域采用 write-back 策略,但我们可以通过 MPU 将 SDRAM 区域设置为 write-through 或 non-cacheable。 # 3. 解决方案:三管齐下 ## 3.1 方案一:配置 MPU 设置 SDRAM 为 Write-Through Write-Through 策略下,CPU 写数据时会同时更新 Cache 和主存,保证外部设备能看到最新数据,但牺牲部分性能。 ```c // 配置 MPU 区域:SDRAM 基地址 0xC0000000,大小 8MB void MPU_Config_SDRAM_WriteThrough(void) { MPU_Region_InitTypeDef MPU_InitStruct; __HAL_RCC_MPU_CLK_ENABLE(); HAL_MPU_Disable(); MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; MPU_InitStruct.SubRegionDisable = 0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.DisableExec = MPU_REGION_ENABLE_EXEC; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; // 允许缓存 MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; // 写直通 HAL_MPU_ConfigRegion(&MPU_InitStruct); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } ``` **注意**:`IsBufferable` 设置为 `MPU_ACCESS_NOT_BUFFERABLE` 即 write-through。但需注意,Cortex-M4 的 MPU 对 write-through 的支持有限,实际效果可能仍为 write-back。因此,更可靠的是方案二。 ## 3.2 方案二:软件维护 Cache(推荐) 在关键操作前后,手动执行 Clean(写回)或 Invalidate(失效)操作。 ### 3.2.1 使用 CMSIS 函数 ```c #include "core_cm4.h" // 写数据到 SDRAM 后,确保数据写回 void SDRAM_Write_With_Clean(uint32_t *addr, uint32_t *data, uint32_t len) { // 写入数据(假设已通过普通赋值或 memcpy) memcpy(addr, data, len * 4); // Clean D-Cache:将指定地址范围的数据写回主存 SCB_CleanDCache_by_Addr((uint32_t *)addr, (int32_t)(len * 4)); } // 从 SDRAM 读取数据前,使 Cache 失效,强制从主存加载 void SDRAM_Read_With_Invalidate(uint32_t *addr, uint32_t *buf, uint32_t len) { // Invalidate D-Cache:丢弃 Cache 中的旧数据 SCB_InvalidateDCache_by_Addr((uint32_t *)addr, (int32_t)(len * 4)); // 读取数据 memcpy(buf, addr, len * 4); } ``` ### 3.2.2 注意事项 - **地址对齐**:CMSIS 函数要求地址和长度按 32 字节对齐(Cache line 大小)。若不对齐,需手动处理边界。 - **性能开销**:频繁 Clean/Invalidate 会降低效率,建议按块操作,而非逐字节。 - **DMA 场景**:在启动 DMA 传输前 Clean(若 DMA 从内存读),在 DMA 完成后 Invalidate(若 DMA 写入内存)。 ## 3.3 方案三:彻底关闭 D-Cache(不推荐) 直接禁用 D-Cache 可避免一致性问题,但会大幅降低 SDRAM 访问性能,仅用于调试或对性能无要求的场景。 ```c SCB_DisableDCache(); ``` # 4. 完整示例:SDRAM + DMA 传输的 Cache 维护 以下代码演示了从 SDRAM 缓冲区通过 DMA 发送数据到 UART,并正确处理 Cache。 ```c // 假设 SDRAM 缓冲区地址 0xC0001000,长度 256 字节 #define SDRAM_BUF_ADDR 0xC0001000 #define BUF_LEN 256 // 初始化 SDRAM 和 DMA 后,发送数据 void SDRAM_DMA_Send(void) { // 1. 确保 SDRAM 中的数据是最新的(如果之前 CPU 写过) SCB_CleanDCache_by_Addr((uint32_t *)SDRAM_BUF_ADDR, BUF_LEN); // 2. 配置 DMA 从 SDRAM 读取数据到 UART(此处省略具体配置) // DMA_Config(SDRAM_BUF_ADDR, UART_TX_ADDR, BUF_LEN); // 3. 启动 DMA // DMA_Start(); // 4. 等待 DMA 完成(中断或轮询) // while(DMA_Busy()); // 5. 如果 DMA 写入了 SDRAM(例如接收),需 Invalidate // SCB_InvalidateDCache_by_Addr((uint32_t *)SDRAM_BUF_ADDR, BUF_LEN); } ``` # 5. 常见误区与注意事项 - **误区一:只 Clean 不 Invalidate**:读操作前必须 Invalidate,否则可能读到旧数据。 - **误区二:忽略对齐**:CMSIS 函数要求 32 字节对齐,否则行为未定义。建议在定义 SDRAM 缓冲区时使用 `__attribute__((aligned(32)))`。 - **误区三:在中断中做 Cache 维护**:Clean/Invalidate 操作耗时较长,应避免在中断上下文执行,否则影响实时性。 - **误区四:MPU 配置后不验证**:不同型号的 MPU 行为可能略有差异,建议通过实际测试确认。 # 6. 总结 Cache 一致性是 STM32F4 使用 SDRAM 时不可回避的问题。通过理解 Cache 工作原理,合理配置 MPU,并在关键路径上执行软件维护,可以彻底解决数据错乱问题。推荐采用“MPU 设置非缓存 + 软件按需维护”的组合策略,兼顾性能与可靠性。希望本文能帮你少走弯路,让 SDRAM 真正成为你的得力助手。