# 一、问题背景与现象 在 STM32F4 系列(如 STM32F407/429)中,当启用 D-Cache 并连接外部 SDRAM(如 W9825G6KH)时,常出现以下现象: - 数据写入 SDRAM 后,立即读取得到旧值; - DMA 从 SDRAM 搬运数据到外设时,数据错乱; - 多任务环境下,共享数据区出现随机性覆盖。 这些现象的本质是 **D-Cache 与 SDRAM 之间的数据一致性(Cache Coherency)未得到保证**。 # 二、原理剖析:为什么会产生不一致? ## 1. D-Cache 的工作机制 STM32F4 的 Cortex-M4 内核带有可配置的 D-Cache(通常 4KB 或 8KB),采用写回(Write-back)策略。当 CPU 写数据时,数据先写入 Cache,标记为脏(Dirty),只有在 Cache 行被替换或显式 Clean 操作时,才写回 SDRAM。 ## 2. SDRAM 的特性 SDRAM 是外部存储器,访问速度远低于内部 SRAM,且不支持字节级写操作(通常以 16 位或 32 位为单位)。当 CPU 通过总线访问 SDRAM 时,若未经过 Cache,则每次读写都直接访问物理存储。 ## 3. 不一致的根源 - **写操作**:CPU 写 SDRAM 地址,数据暂存于 Cache,但 SDRAM 中仍是旧值。若此时 DMA 或其他外设读取 SDRAM,则读到旧数据。 - **读操作**:CPU 先读 SDRAM 某地址,Cache 缓存该行。若 SDRAM 内容被 DMA 更新,CPU 再次读取时命中 Cache,得到旧值。 - **DMA 访问**:DMA 不经过 Cache,直接读写 SDRAM,与 Cache 内容脱节。 # 三、排查步骤:从理论到实测 ## 1. 确认 Cache 是否启用 检查启动代码或 HAL 库配置,确认是否调用了 `SCB_EnableDCache()`。若未启用,则问题与 Cache 无关,需排查其他因素。 ```c // 启动代码中启用 D-Cache SCB_EnableDCache(); ``` ## 2. 检查 MPU 配置 Cortex-M4 支持 MPU(内存保护单元),可设置内存区域的 Cache 策略。若 SDRAM 区域被配置为 Write-back,则会产生一致性问题。推荐将 SDRAM 区域配置为 **Write-through** 或 **Non-cacheable**。 ```c // 配置 MPU,将 SDRAM 区域设为 Write-through MPU_Region_InitTypeDef MPU_InitStruct; MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; // SDRAM 基地址 MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_REGION_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_REGION_CACHEABLE; // 允许 Cache MPU_InitStruct.IsShareable = MPU_REGION_NOT_SHAREABLE; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL_0; MPU_InitStruct.SubRegionDisable = 0; MPU_InitStruct.DisableExec = MPU_REGION_DISABLE_EXEC; HAL_MPU_ConfigRegion(&MPU_InitStruct); ``` 注意:`TypeExtField` 和 `IsCacheable` 组合决定策略。若想禁用 Cache,可设置 `IsCacheable = MPU_REGION_NOT_CACHEABLE`。 ## 3. 使用内存屏障指令 在关键操作前后插入 `__DSB()` 和 `__ISB()`,确保 Cache 操作完成。 ```c // 写 SDRAM 后,确保数据写回 SCB_CleanDCache(); __DSB(); ``` ## 4. 手动维护 Cache 一致性 对于 DMA 操作,需在启动 DMA 前 Clean Cache,在 DMA 完成后 Invalidate Cache。 ```c // 发送前:将数据从 Cache 写回 SDRAM SCB_CleanDCache_by_Addr((uint32_t*)buffer, size); // 接收后:使 Cache 行失效,重新从 SDRAM 读取 SCB_InvalidateDCache_by_Addr((uint32_t*)buffer, size); ``` 注意:地址需按 32 字节对齐(Cache 行大小)。 ## 5. 实测验证方法 - **测试 1:读写一致性**:向 SDRAM 写入递增数据,立即读回比对,循环多次。若失败,则存在一致性问题。 - **测试 2:DMA 交互**:CPU 写数据,DMA 搬运到串口,观察输出是否完整。 - **测试 3:多任务共享**:两个任务交替读写同一区域,加入随机延时,观察数据是否错乱。 ```c // 测试代码示例:读写一致性测试 #define BUFFER_SIZE 1024 uint32_t test_buf[BUFFER_SIZE] __attribute__((section(".sdram"))); void Cache_Test(void) { uint32_t i; for (i = 0; i < BUFFER_SIZE; i++) { test_buf[i] = i; } SCB_CleanDCache(); // 写回 for (i = 0; i < BUFFER_SIZE; i++) { if (test_buf[i] != i) { printf("Error at %d\n", i); break; } } printf("Test done\n"); } ``` # 四、完整代码示例:结合 MPU 和 Cache 维护 以下代码演示了如何正确配置 SDRAM 区域并处理 DMA 传输。 ```c #include "stm32f4xx_hal.h" // SDRAM 基地址(假设为 0xC0000000) #define SDRAM_BASE 0xC0000000 #define BUFFER_SIZE 256 uint32_t tx_buffer[BUFFER_SIZE] __attribute__((section(".sdram"))); void MPU_Config(void) { // 配置 SDRAM 为 Write-through,避免一致性问题 MPU_Region_InitTypeDef MPU_InitStruct = {0}; HAL_MPU_Disable(); MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = SDRAM_BASE; MPU_InitStruct.Size = MPU_REGION_SIZE_1MB; // 根据实际大小调整 MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_REGION_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_REGION_CACHEABLE; MPU_InitStruct.IsShareable = MPU_REGION_NOT_SHAREABLE; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL_1; // 配合 Write-through MPU_InitStruct.SubRegionDisable = 0; MPU_InitStruct.DisableExec = MPU_REGION_DISABLE_EXEC; HAL_MPU_ConfigRegion(&MPU_InitStruct); HAL_MPU_Enable(MPU_CONTROL_HRNDM_ENABLE); } void DMA_Transmit(void) { // 填充数据 for (int i = 0; i < BUFFER_SIZE; i++) { tx_buffer[i] = i * 2; } // Clean Cache,确保数据写回 SDRAM SCB_CleanDCache_by_Addr((uint32_t*)tx_buffer, BUFFER_SIZE * 4); __DSB(); // 启动 DMA(假设已配置) HAL_UART_Transmit_DMA(&huart1, (uint8_t*)tx_buffer, BUFFER_SIZE * 4); } void DMA_Receive(void) { // 接收前 Invalidate Cache,避免读到旧数据 SCB_InvalidateDCache_by_Addr((uint32_t*)rx_buffer, BUFFER_SIZE * 4); __DSB(); HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buffer, BUFFER_SIZE * 4); } ``` # 五、注意事项与最佳实践 - **地址对齐**:Cache 维护函数要求地址按 32 字节对齐,否则可能无效。建议将缓冲区定义在 `.sdram` 段并手动对齐。 - **性能权衡**:Write-through 策略会降低写性能,但保证一致性。若对性能要求高,可保留 Write-back,但需严格管理 Clean/Invalidate。 - **DMA 描述符**:若 DMA 描述符也位于 SDRAM,需同样处理 Cache 一致性。 - **多核/中断**:在中断中访问 SDRAM 时,注意关中断或使用临界区,防止 Cache 操作被打断。 - **调试技巧**:使用硬件调试器观察 SDRAM 内存值,对比 Cache 内容,可快速定位不一致点。 # 六、总结 D-Cache 与 SDRAM 的一致性问题在 STM32F4 项目中十分常见,但通过合理的 MPU 配置、显式的 Cache 维护操作以及系统的测试方法,完全可以避免。本文提供的实测排查方法已在实际项目中验证,能有效减少数据错误,提升系统稳定性。建议开发者在设计初期就考虑 Cache 策略,避免后期返工。