# 引言 在 STM32F4 系列(如 STM32F429/469)中,当使用外部 SDRAM 作为帧缓冲或数据缓冲区时,D-Cache 的引入虽然提升了 CPU 访问速度,但也带来了数据一致性问题。CPU 写入的数据可能暂存在 Cache 中,而 DMA 或 LTDC 外设直接访问 SDRAM,导致读取到旧数据;反之,外设写入的数据可能被 Cache 中的旧数据覆盖。本文针对这一痛点,提供三种经过验证的解决方案。 # 问题根源 STM32F4 的 D-Cache 是 4 路组关联、总大小 4KB(部分型号 8KB),以 32 字节为一行。当 CPU 写内存时,数据先写入 Cache(写回策略),仅当 Cache 行被替换或显式清理时才写回 SDRAM。DMA 控制器不经过 Cache,直接访问 SDRAM,因此: - **CPU 写 → DMA 读**:DMA 可能读到 SDRAM 中的旧数据,因为 CPU 的新数据还在 Cache 中。 - **DMA 写 → CPU 读**:CPU 可能读到 Cache 中的旧数据,因为 DMA 已更新 SDRAM,但 Cache 未失效。 # 解决方案一:Cache 清理与失效(软件控制) ## 原理 通过调用 CMSIS 提供的函数,在关键操作前后手动维护 Cache 一致性。 - `SCB_CleanDCache()`:将 Cache 中脏数据写回 SDRAM。 - `SCB_InvalidateDCache()`:使 Cache 行失效,强制下次读取从 SDRAM 加载。 - `SCB_CleanInvalidateDCache()`:两者结合。 ## 配置步骤 1. 启用 D-Cache(默认在启动文件中已启用,若未启用则需在 main 中调用 `SCB_EnableDCache()`)。 2. 在 DMA 传输前,清理包含源数据的 Cache 区域。 3. 在 DMA 传输完成后,失效包含目标数据的 Cache 区域。 ## 代码示例 ```c // 假设使用 DMA 从 SDRAM 缓冲区 buf_src 传输数据到外设 #define BUF_SIZE 1024 uint32_t buf_src[BUF_SIZE] __attribute__((section(".sdram"))); uint32_t buf_dst[BUF_SIZE] __attribute__((section(".sdram"))); void dma_transfer_with_cache_maintenance(void) { // 清理源缓冲区,确保 DMA 能看到 CPU 写入的最新数据 SCB_CleanDCache_by_Addr((uint32_t*)buf_src, BUF_SIZE * sizeof(uint32_t)); // 启动 DMA 传输(此处省略 DMA 配置) DMA_Start_Transfer(buf_src, buf_dst, BUF_SIZE); // 等待 DMA 完成 while(DMA_Is_Busy()); // 失效目标缓冲区,使 CPU 读取时从 SDRAM 重新加载 SCB_InvalidateDCache_by_Addr((uint32_t*)buf_dst, BUF_SIZE * sizeof(uint32_t)); } ``` ## 注意事项 - 地址必须 32 字节对齐,长度建议为 32 的倍数,否则需手动处理边界。 - 频繁调用会影响性能,适合低频操作。 # 解决方案二:MPU 配置非缓存区域(硬件隔离) ## 原理 利用 MPU(内存保护单元)将 SDRAM 区域配置为“非缓存”属性(如 `NORMAL, NON-CACHEABLE`),使 CPU 访问该区域时直接读写 SDRAM,绕过 D-Cache。这样 DMA 和 CPU 看到的数据始终一致,无需软件干预。 ## 配置步骤 1. 定义 MPU 区域,覆盖 SDRAM 地址范围(例如 0xC0000000,大小 8MB)。 2. 设置属性:`MPU_REGION_NORMAL_NON_CACHEABLE`(或 `MPU_ACCESS_BUFFERABLE`)。 3. 使能 MPU 和区域。 ## 代码示例 ```c void MPU_Config_SDRAM_NonCacheable(void) { MPU_Region_InitTypeDef MPU_InitStruct; // 禁用 MPU 进行配置 HAL_MPU_Disable(); // 配置 SDRAM 区域为 0xC0000000,大小 8MB MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_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); } ``` ## 注意事项 - 非缓存区域会降低 CPU 访问速度,但保证一致性,适合帧缓冲等高频访问场景,可接受性能损失。 - 需确保 MPU 区域与 SDRAM 物理地址完全匹配,避免覆盖其他外设。 # 解决方案三:DMA 双缓冲策略(架构优化) ## 原理 为 DMA 和 CPU 分配独立的缓冲区,通过交替使用避免同时访问同一内存区域。例如,CPU 写入缓冲区 A,DMA 从缓冲区 B 读取;当 DMA 完成,交换角色。这样 CPU 和 DMA 永远不会同时操作同一块内存,从根源上消除一致性问题。 ## 配置步骤 1. 在 SDRAM 中分配两个缓冲区(A 和 B)。 2. 使用 DMA 双缓冲模式(如 STM32 的 DMA2D 或通用 DMA 的 double-buffer 功能)。 3. 在 DMA 传输完成中断中切换缓冲区指针。 ## 代码示例 ```c #define BUF_SIZE 1024 uint32_t buf_A[BUF_SIZE] __attribute__((section(".sdram"))); uint32_t buf_B[BUF_SIZE] __attribute__((section(".sdram"))); volatile uint32_t *active_buf = buf_A; volatile uint32_t *dma_buf = buf_B; void DMA_Transfer_Complete_Callback(void) { // 交换缓冲区 uint32_t *tmp = active_buf; active_buf = dma_buf; dma_buf = tmp; // 启动下一次 DMA 传输,使用新的 dma_buf DMA_Start_Transfer(dma_buf, BUF_SIZE); } void main_loop(void) { // CPU 写入 active_buf,无需 Cache 维护 process_data(active_buf); } ``` ## 注意事项 - 需要额外的内存空间,但避免了 Cache 操作,性能最优。 - 适用于数据流场景(如音频、图像采集),不适合随机访问。 # 总结 | 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | Cache 清理/失效 | 无需额外内存,实现简单 | 软件开销大,需注意对齐 | 低频小数据量传输 | | MPU 非缓存 | 硬件保证一致性,无需软件干预 | 降低 CPU 访问速度 | 高频访问的帧缓冲 | | DMA 双缓冲 | 性能最优,无 Cache 操作 | 内存占用翻倍,逻辑复杂 | 连续数据流处理 | 开发者应根据实际需求选择:若追求简单,用方案一;若性能敏感且可接受非缓存,用方案二;若数据流大且内存充足,用方案三。理解 D-Cache 与 SDRAM 的交互机制,是解决嵌入式系统数据一致性问题的关键。