# 引言 在 STM32F4 系列(如 STM32F407/429)上,当使用 SDRAM 作为帧缓冲并启用 D-Cache(数据缓存)时,CPU 与 DMA 或 LCD 控制器对同一内存区域的访问会因缓存未同步而产生数据不一致,导致画面撕裂、颜色错误甚至系统崩溃。本文面向有一定嵌入式基础的开发者,深入剖析问题根源,并提供三种实用规避方案。 ## 问题根源:D-Cache 与 SDRAM 的“信息孤岛” D-Cache 是 CPU 与主存(SDRAM)之间的高速缓存,用于加速数据访问。当 CPU 写入帧缓冲时,数据可能暂存在 Cache 中,并未立即写回 SDRAM。而 LCD 控制器(如 LTDC)或 DMA 直接读取 SDRAM 时,读到的可能是旧数据,造成不一致。反之,当外设写入 SDRAM(如摄像头采集),CPU 读取时可能命中过期的 Cache 行,读到错误数据。 STM32F4 的 D-Cache 是写回(write-back)策略,且不自动维护一致性,因此需要软件干预。 # 方案一:MPU 配置——将 SDRAM 区域设为非 Cacheable 最简单粗暴的方式是,通过 MPU(内存保护单元)将 SDRAM 区域配置为“非缓存”(Strongly-ordered 或 Device 类型),这样 CPU 每次访问都直接读写 SDRAM,彻底避免一致性问题。 **适用场景**:对性能要求不高,或帧缓冲访问频率较低(如静态图像显示)。 ## 配置步骤 1. 使能 MPU 并配置区域属性。 2. 将 SDRAM 基地址(如 0xC0000000)设为 Region,大小匹配 SDRAM 容量。 3. 设置 TEX=0, C=0, B=0(非缓存),或使用 Device 类型。 ## 代码示例(基于 STM32Cube HAL) ```c #include "stm32f4xx_hal.h" void MPU_Config_SDRAM_NonCacheable(void) { MPU_Region_InitTypeDef MPU_InitStruct; __HAL_RCC_MPU_CLK_ENABLE(); HAL_MPU_Disable(); MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xC0000000; // SDRAM 基地址 MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; // 根据实际 SDRAM 大小调整 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.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } ``` **注意事项**: - 非缓存区域访问速度较慢,可能影响 CPU 性能,但帧缓冲写入频率低时影响可忽略。 - 确保 MPU 配置在 SDRAM 初始化之后执行。 # 方案二:软件维护——手动 Clean 和 Invalidate 保留 D-Cache 以提升性能,但在关键操作前后手动执行 Cache 维护指令。 **适用场景**:帧缓冲频繁更新,且对性能有要求,但能接受一定的软件开销。 ## 原理 - **Clean**:将 Cache 中脏数据写回 SDRAM。 - **Invalidate**:将 Cache 行标记为无效,下次访问时从 SDRAM 重新加载。 对于帧缓冲,典型操作序列: - CPU 写入帧缓冲后,在启动 DMA 或 LCD 传输前,执行 Clean。 - 外设写入后,CPU 读取前,执行 Invalidate。 ## 代码示例 ```c #include "stm32f4xx_hal.h" void Clean_DCache_By_Addr(uint32_t addr, uint32_t size) { SCB_CleanDCache_by_Addr((uint32_t*)addr, (int32_t)size); } void Invalidate_DCache_By_Addr(uint32_t addr, uint32_t size) { SCB_InvalidateDCache_by_Addr((uint32_t*)addr, (int32_t)size); } // 示例:CPU 绘制完成后,刷新到 SDRAM void Draw_Frame(void) { // 绘制操作... Clean_DCache_By_Addr(0xC0000000, 800*480*2); // 假设 RGB565 // 启动 LTDC 或 DMA 传输 } // 示例:DMA 从摄像头采集到 SDRAM 后,CPU 读取前 void Process_Camera_Data(void) { Invalidate_DCache_By_Addr(0xC0000000, 800*480*2); // 处理数据... } ``` **注意事项**: - 地址必须 32 字节对齐(Cache line 大小),否则行为未定义。 - Clean 和 Invalidate 操作有开销,频繁调用可能影响性能,建议在批量操作后执行。 - 若使用 DMA,需确保 DMA 传输完成后再 Invalidate,避免竞争。 # 方案三:双缓冲 + 缓存区隔离 使用两个帧缓冲,一个用于 CPU 绘制,一个用于 LCD 显示,通过切换缓冲避免同时访问同一区域。 **适用场景**:需要高性能动画或视频播放,且内存充足。 ## 原理 - 分配两个 SDRAM 区域:Buffer A 和 Buffer B。 - CPU 绘制到 Buffer A,同时 LCD 显示 Buffer B。 - 绘制完成后,Clean Buffer A,然后切换 LTDC 的帧缓冲地址到 A,同时将 B 设为可绘制。 - 由于 CPU 和 LCD 不会同时访问同一缓冲,一致性风险大幅降低。 ## 代码示例 ```c #define BUFFER_SIZE (800*480*2) #define SDRAM_BASE 0xC0000000 #define BUFFER_A (SDRAM_BASE) #define BUFFER_B (SDRAM_BASE + BUFFER_SIZE) uint32_t current_draw_buffer = BUFFER_A; uint32_t current_display_buffer = BUFFER_B; void Swap_Buffers(void) { // 确保当前绘制缓冲的数据已写回 SCB_CleanDCache_by_Addr((uint32_t*)current_draw_buffer, BUFFER_SIZE); // 切换 LTDC 帧缓冲地址(以 LTDC 为例) LTDC_Layer1->CFBAR = current_draw_buffer; LTDC_Layer1->CFBLR = (800*2) | ((800*2+3) << 16); // 配置行长度 LTDC_Layer1->CFBLNR = 480; // 重新加载影子寄存器 LTDC->SRCR = LTDC_SRCR_IMR; // 交换指针 uint32_t temp = current_draw_buffer; current_draw_buffer = current_display_buffer; current_display_buffer = temp; } // 主循环中: // 绘制到 current_draw_buffer // 绘制完成后调用 Swap_Buffers() ``` **注意事项**: - 需要确保 LTDC 支持帧缓冲地址动态切换(F4 系列支持)。 - 双缓冲增加内存占用,需评估 SDRAM 容量。 - 切换时机需与垂直同步(VSYNC)对齐,避免撕裂,可使用 LTDC 中断。 # 总结与选型建议 | 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | MPU 非缓存 | 实现简单,无软件开销 | 性能下降 | 静态显示、低帧率 | | 软件维护 | 性能较好,灵活性高 | 需手动操作,有开销 | 中等性能需求 | | 双缓冲 | 性能最佳,无一致性风险 | 内存占用大,实现复杂 | 高性能动画、视频 | 实际项目中,可组合使用:例如,用 MPU 将 SDRAM 设为写回缓存,配合软件 Clean/Invalidate,同时采用双缓冲减少切换频率。关键是根据帧率、内存和 CPU 负载权衡。 希望本文能帮助你解决 STM32F4 上的 Cache 一致性问题。如有疑问,欢迎在评论区交流!