# 引言 在 STM32F4 系列(如 STM32F407/429)中,D-Cache 和 SDRAM 的组合常被用于高性能图形显示。然而,D-Cache 的缓存机制会导致 CPU 与 DMA 或 LCD 控制器访问同一帧缓冲时出现数据不一致,表现为画面闪烁、残影或数据错误。本文针对这一痛点,提供三种经过实战验证的维护策略,助你彻底解决 Cache 一致性问题。 ## 问题根源:D-Cache 与 SDRAM 的“双轨制” STM32F4 的 D-Cache 是 4 路组相联、总容量 64KB(部分型号)。当 CPU 写入帧缓冲时,数据可能只停留在 Cache 中,尚未写回 SDRAM。而 LCD 控制器(如 LTDC)或 DMA 直接读取 SDRAM 时,看到的仍是旧数据,导致显示异常。反之,若 DMA 更新了 SDRAM 数据,CPU 读取时可能命中过期的 Cache 行。 ## 策略一:全量失效与清理(简单粗暴) ### 原理 在每次 CPU 写帧缓冲后、启动 LCD 刷新前,强制将整个 D-Cache 写回(Clean)并失效(Invalidate)。此方法简单可靠,但开销较大,适合帧率要求不高的场景。 ### 配置步骤 1. 使能 D-Cache:`SCB_EnableDCache()`。 2. 在关键操作点调用清理和失效函数。 ### 代码示例 ```c #include "stm32f4xx_hal.h" void LCD_FlushFrameBuffer(uint32_t* fb, uint32_t size) { // 确保 CPU 写入全部写回 SDRAM SCB_CleanDCache(); // 启动 LTDC 刷新(或 DMA2D 传输) LTDC_Reload(); } void DMA_UpdateFrameBuffer(uint32_t* fb, uint32_t size) { // DMA 写入 SDRAM 后,使 D-Cache 失效,避免 CPU 读到旧数据 SCB_InvalidateDCache(); // 此时 CPU 访问 fb 将重新从 SDRAM 读取 } ``` ### 注意事项 - 全量清理/失效会暂停 CPU 访问,影响实时性。 - 适用于帧缓冲较小或刷新率低于 30fps 的场景。 ## 策略二:按区域维护(精准高效) ### 原理 只对修改过的帧缓冲区域执行 Clean 或 Invalidate,利用 `SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr` 函数,按地址和大小操作。这减少了开销,适合频繁局部更新的 UI。 ### 配置步骤 1. 确保帧缓冲地址按 32 字节对齐(Cache line 大小)。 2. 在每次局部绘制后,仅清理对应区域。 3. 在 DMA 接收数据后,仅失效对应区域。 ### 代码示例 ```c #define CACHE_LINE_SIZE 32 void LCD_DrawRect(uint32_t* fb, int x, int y, int w, int h, uint32_t color) { // 绘制逻辑(略) // 计算区域地址和大小,注意对齐 uint32_t addr = (uint32_t)(fb + y * 800 + x); // 假设 800 宽 uint32_t size = w * h * 4; // 32-bit 像素 // 对齐到 cache line uint32_t aligned_addr = addr & ~(CACHE_LINE_SIZE - 1); uint32_t aligned_size = (size + (addr - aligned_addr) + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1); SCB_CleanDCache_by_Addr((uint32_t*)aligned_addr, aligned_size); // 触发 LTDC 刷新 } void DMA_ReceiveData(uint32_t* buf, uint32_t size) { // DMA 传输完成后 uint32_t addr = (uint32_t)buf; uint32_t aligned_addr = addr & ~(CACHE_LINE_SIZE - 1); uint32_t aligned_size = (size + (addr - aligned_addr) + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1); SCB_InvalidateDCache_by_Addr((uint32_t*)aligned_addr, aligned_size); } ``` ### 注意事项 - 地址和大小必须对齐到 32 字节,否则会破坏相邻数据。 - 适合局部刷新频繁的 GUI 应用,性能提升明显。 ## 策略三:MPU 配置免 Cache(彻底隔离) ### 原理 通过 MPU 将 SDRAM 区域设置为“非缓存”(Normal memory, Non-cacheable),这样 CPU 访问 SDRAM 时直接读写,不经过 D-Cache。彻底避免一致性问题,但牺牲了缓存加速效果。 ### 配置步骤 1. 初始化 MPU,配置 SDRAM 区域属性。 2. 使能 MPU。 3. 注意:此区域内的所有访问都无缓存,适合帧缓冲和 DMA 共享区域。 ### 代码示例 ```c void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct; 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_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); HAL_MPU_Enable(MPU_CONTROL_HRD_MODE); } int main(void) { HAL_Init(); MPU_Config(); // 后续初始化 SDRAM 和 LCD } ``` ### 注意事项 - 禁用缓存后,CPU 访问 SDRAM 速度下降,但通常 LCD 刷新瓶颈不在 CPU。 - 适用于帧缓冲较大且频繁 DMA 更新的场景,如视频播放。 ## 总结与选型建议 | 策略 | 开销 | 适用场景 | |------|------|----------| | 全量清理/失效 | 高 | 低帧率、简单界面 | | 按区域维护 | 中 | 局部刷新、GUI 应用 | | MPU 免缓存 | 低(但无缓存加速) | 高带宽 DMA、视频 | 实际项目中,可结合策略二和策略三:将帧缓冲设为非缓存,但将 CPU 频繁读写的辅助缓冲区设为可缓存。务必在开发初期就规划好内存布局,避免后期修改带来的风险。 ## 结语 Cache 一致性是嵌入式图形开发中的经典难题,掌握这三种策略,你就能根据项目需求灵活应对。记住,没有银弹,只有最适合场景的方案。希望本文能助你在 STM32F4 上打造流畅稳定的显示系统。