# STM32F4 使用 D-Cache 时 DMA 描述符缓存一致性维护的三种实用策略 ## 问题背景:D-Cache 与 DMA 的冲突 STM32F4 系列(Cortex-M4F)内置了 D-Cache(数据缓存),用于加速 CPU 对内存的访问。然而,DMA 控制器直接访问物理内存,不经过 Cache。当 CPU 修改 DMA 描述符(如描述符中的缓冲区地址、长度、状态标志)后,这些数据可能仍停留在 Cache 中,尚未写回物理内存;反之,DMA 更新描述符后,CPU 读取时可能命中过期的 Cache 行。这种不一致性会导致 DMA 传输错误、数据丢失甚至系统崩溃。 典型场景:使用 STM32F4 的以太网 MAC(DMA 描述符)或 SDIO(SD 卡)时,若启用了 D-Cache,必须处理缓存一致性。 ## 策略一:直接禁用 D-Cache(简单粗暴) ### 原理 禁用 D-Cache 后,CPU 所有访问都直接操作物理内存,DMA 与 CPU 天然一致。代价是 CPU 访问内存速度下降(尤其对频繁访问的数据),但系统功能正确。适合对性能要求不高或调试阶段。 ### 配置步骤 1. 在系统初始化时,不使能 D-Cache(默认关闭)。 2. 若已使能,则调用 `SCB_DisableDCache()` 关闭。 ### 代码示例 ```c #include "stm32f4xx.h" void SystemInit_CacheConfig(void) { // 禁用 D-Cache(默认关闭,此处显式调用) SCB_DisableDCache(); // 注意:禁用后无需清理,因为未使用 } // 以太网 DMA 描述符定义(无 Cache 时直接使用) ETH_DMADescTypeDef DMATxDescTab[ETH_TXBUFNB] __attribute__((aligned(4))); int main(void) { SystemInit_CacheConfig(); // 初始化以太网... HAL_ETH_Init(&heth); // 正常操作,无需额外维护 } ``` ### 注意事项 - 禁用 D-Cache 会降低整体性能,尤其在大量内存拷贝或计算场景。 - 若系统其他部分依赖 D-Cache(如图形加速),需谨慎评估。 ## 策略二:使用 Cache 清理/无效化操作(精准控制) ### 原理 在 CPU 修改描述符后,调用 `SCB_CleanDCache()` 将 Cache 内容写回内存;在 DMA 更新描述符后,调用 `SCB_InvalidateDCache()` 使 Cache 行失效,强制从内存重新读取。此策略保持 D-Cache 开启,性能较好,但需在关键点手动操作。 ### 配置步骤 1. 使能 D-Cache:`SCB_EnableDCache()`。 2. 在每次 CPU 写描述符后,清理相关地址的 Cache。 3. 在每次 DMA 完成中断后,无效化相关地址的 Cache。 ### 代码示例 ```c #include "stm32f4xx.h" // 描述符缓冲区(需对齐到 Cache 行大小,通常 32 字节) #define CACHE_LINE_SIZE 32 ETH_DMADescTypeDef DMATxDescTab[ETH_TXBUFNB] __attribute__((aligned(CACHE_LINE_SIZE))); // CPU 准备描述符(例如设置缓冲区地址和长度) void PrepareTxDescriptor(ETH_DMADescTypeDef *desc, uint8_t *buf, uint16_t len) { desc->Buffer1Addr = (uint32_t)buf; desc->ControlBufferSize = len; desc->Status = ETH_DMATXDESC_VALID; // 清理该描述符对应的 Cache 行,确保写回内存 SCB_CleanDCache_by_Addr((uint32_t*)desc, sizeof(*desc)); } // DMA 发送完成中断回调 void HAL_ETH_TxCpltCallback(ETH_HandleTypeDef *heth) { // 无效化描述符,读取 DMA 更新的状态 SCB_InvalidateDCache_by_Addr((uint32_t*)DMATxDescTab, sizeof(DMATxDescTab)); // 检查状态... } int main(void) { SCB_EnableDCache(); // 初始化以太网... HAL_ETH_Init(&heth); // 发送数据 PrepareTxDescriptor(&DMATxDescTab[0], tx_buffer, 100); HAL_ETH_Transmit_IT(&heth, &DMATxDescTab[0]); while(1) { /* 主循环 */ } } ``` ### 注意事项 - 必须确保描述符地址按 Cache 行对齐(STM32F4 的 D-Cache 行大小为 32 字节),否则清理/无效化可能影响相邻数据。 - 清理和无效化操作有开销,频繁调用会降低性能,建议批量处理(如整个描述符表)。 - 在 DMA 传输过程中,CPU 不应访问描述符,否则需额外同步。 ## 策略三:使用 MPU 配置非缓存区域(硬件隔离) ### 原理 通过 MPU(内存保护单元)将 DMA 描述符所在的内存区域配置为“非缓存”属性(即 `Device` 或 `Strongly-ordered`),这样 CPU 访问该区域时直接绕过 D-Cache,与 DMA 保持一致。其他内存区域仍可启用 Cache,兼顾性能与正确性。 ### 配置步骤 1. 使能 MPU 并配置区域:设置基地址、大小、属性(`MPU_REGION_NO_CACHE`)。 2. 将描述符数组放置在该区域(通过链接脚本或属性指定)。 3. 初始化 MPU 后,正常使用描述符,无需手动清理。 ### 代码示例 ```c #include "stm32f4xx.h" // 描述符区域基地址(例如 SRAM3 的某段) #define DESC_REGION_BASE 0x20004000 // 示例地址,需根据实际内存布局 #define DESC_REGION_SIZE (4 * 1024) // 4KB // 将描述符数组放置到该区域(使用 __attribute__) ETH_DMADescTypeDef DMATxDescTab[ETH_TXBUFNB] __attribute__((section(".dma_desc"))); void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct; // 禁用 MPU 进行配置 HAL_MPU_Disable(); // 配置区域 0:描述符区域,非缓存 MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = DESC_REGION_BASE; MPU_InitStruct.Size = MPU_REGION_SIZE_4KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); // 使能 MPU HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } // 链接脚本中需将 .dma_desc 段放置到 DESC_REGION_BASE 地址 int main(void) { HAL_Init(); MPU_Config(); SCB_EnableDCache(); // 其他区域仍可使用 Cache // 初始化以太网... // 直接操作描述符,无需清理/无效化 } ``` ### 注意事项 - MPU 配置需在系统早期完成,且注意区域大小和地址对齐(通常 32 字节倍数)。 - 非缓存区域访问速度较慢,但仅影响描述符,对整体性能影响小。 - 需修改链接脚本(如 `.ld` 文件)将描述符段定位到指定地址,确保与 MPU 区域匹配。 ## 总结与选型建议 | 策略 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | 禁用 D-Cache | 简单,无额外代码 | 性能下降明显 | 调试阶段或性能要求低 | | 手动清理/无效化 | 性能较好,灵活 | 需精确控制,易出错 | 描述符数量少,实时性要求高 | | MPU 非缓存区域 | 硬件保证,性能影响小 | 配置复杂,需链接脚本配合 | 生产环境,描述符频繁使用 | 对于大多数 STM32F4 项目,推荐使用策略三(MPU 配置),它平衡了性能与正确性。若项目简单或处于开发初期,策略一可快速验证功能;策略二适合对 Cache 操作熟悉且描述符访问点明确的场景。无论选择哪种,务必在初始化阶段确保一致性,并在文档中记录维护点,以便后续维护。