# 引言 在 STM32F4 系列(如 STM32F429/469)驱动 TFT-LCD 或图形加速器(LTDC、DMA2D)时,SDRAM 常被用作帧缓冲(Framebuffer)。然而,当 CPU 通过 D-Cache 写入帧缓冲,而 DMA2D 或 LTDC 直接读取 SDRAM 时,D-Cache 中的脏数据尚未回写,外设读到的却是旧数据,这就是经典的 Cache 一致性问题。若不处理,画面会出现撕裂、花屏或数据错乱。 本文面向有一定嵌入式基础的开发者,介绍三种高效的维护策略,并给出可落地的代码示例。 # 问题根源:D-Cache 与 SDRAM 的“双轨” STM32F4 的 D-Cache(Cortex-M4 内核)是写回(Write-back)模式,CPU 写数据时先写入 Cache,标记为脏(Dirty),只有在缓存行被替换或显式 Clean 时才回写 SDRAM。而 DMA2D、LTDC 等外设直接访问 SDRAM,不经过 Cache。因此,CPU 写入帧缓冲后,外设可能读到旧数据;反之,外设写入(如摄像头采集)后,CPU 可能读到 Cache 中的旧副本。 # 策略一:直接关闭 D-Cache(最简单,但性能损失大) ## 原理 关闭 D-Cache 后,CPU 每次读写都直接访问 SDRAM,彻底避免一致性问题。但代价是 CPU 访问 SDRAM 的延迟显著增加(SDRAM 通常 10-20ns,而 Cache 命中仅 1-2ns),图形渲染性能可能下降 30%-50%。 ## 配置步骤 1. 在系统初始化时调用 `SCB_DisableDCache()`。 2. 确保所有外设(LTDC、DMA2D)正常工作。 ## 代码示例 ```c #include "stm32f4xx.h" void SystemInit_CacheDisabled(void) { SCB_DisableDCache(); // 关闭 D-Cache // 后续所有 SDRAM 访问均直接走 AHB 总线 } // 使用示例:初始化 LTDC 和 SDRAM 后,直接操作帧缓冲 void DrawPixel(uint32_t x, uint32_t y, uint16_t color) { uint16_t *fb = (uint16_t*)SDRAM_FB_ADDR; fb[y * LCD_WIDTH + x] = color; // 无 Cache,直接写 SDRAM } ``` ## 注意事项 - 适合对性能要求不高、或帧缓冲访问频率低的场景(如静态图片显示)。 - 若同时使用 DMA2D 做图形加速,DMA2D 性能不受影响,但 CPU 操作会变慢。 # 策略二:手动 Clean + Invalidate(灵活,但需精细控制) ## 原理 保持 D-Cache 开启,在关键操作前后手动维护一致性。具体来说: - 当 CPU 写入帧缓冲后,调用 `SCB_CleanDCache_by_Addr()` 将脏数据回写 SDRAM,确保外设可读。 - 当外设写入内存(如摄像头 DMA 采集)后,调用 `SCB_InvalidateDCache_by_Addr()` 使 Cache 行失效,强制 CPU 下次从 SDRAM 重新读取。 ## 配置步骤 1. 开启 D-Cache(默认开启,也可显式调用 `SCB_EnableDCache()`)。 2. 在每次 CPU 写帧缓冲后,调用 Clean 函数。 3. 在每次外设写内存后,调用 Invalidate 函数。 ## 代码示例 ```c #include "stm32f4xx.h" #define FB_ADDR 0xC0000000 // SDRAM 帧缓冲地址 #define FB_SIZE (800*480*2) // 假设 800x480 RGB565 // CPU 绘制完成后,回写脏数据 void Framebuffer_Flush(void) { SCB_CleanDCache_by_Addr((uint32_t*)FB_ADDR, FB_SIZE); } // 外设(如摄像头 DMA)写入后,使 Cache 失效 void Framebuffer_Invalidate(void) { SCB_InvalidateDCache_by_Addr((uint32_t*)FB_ADDR, FB_SIZE); } // 使用示例:绘制一帧后刷新 void DrawFrame(void) { // ... 绘制操作,直接写 FB_ADDR ... Framebuffer_Flush(); // 确保 LTDC 读到最新数据 } ``` ## 注意事项 - 地址必须 32 字节对齐(Cache line 大小),大小需为 32 的倍数,否则可能遗漏部分缓存行。 - 频繁 Clean 会带来性能开销,建议在帧绘制完成后一次性 Clean,而不是每像素操作。 - 若使用 DMA2D 进行块拷贝,需在启动 DMA2D 前 Clean 源地址,在完成后 Invalidate 目标地址。 # 策略三:MPU 配置 Non-cacheable 区域(推荐,性能与一致性兼得) ## 原理 通过 MPU(Memory Protection Unit)将 SDRAM 帧缓冲区域设置为 Non-cacheable(或 Write-through),这样 CPU 访问该区域时绕过 D-Cache,直接读写 SDRAM,而其他内存区域(如 SRAM)仍保持 Cache 加速。这是最优雅的方案,无需手动维护,且性能损失极小(仅帧缓冲区域无缓存)。 ## 配置步骤 1. 初始化 MPU,配置一个 Region 覆盖 SDRAM 地址范围。 2. 设置 Region 属性为 Non-cacheable(TEX=0, C=0, B=1 或 TEX=1, C=0, B=0,具体见参考手册)。 3. 使能 MPU 和 D-Cache。 ## 代码示例 ```c #include "stm32f4xx.h" void MPU_Config_NonCacheable(void) { // 禁用 MPU 进行配置 MPU->CTRL = 0; // 配置 Region 0:SDRAM 区域 MPU->RNR = 0; // Region 0 MPU->RBAR = 0xC0000000; // 基地址 // 大小:256MB(0x10000000),使能,子区域使能 MPU->RASR = (0x1E << 1) | // 大小 256MB (2^28) (0x0 << 0) | // 使能 (0x0 << 16) | // TEX=0 (0x0 << 18) | // C=0 (0x1 << 19) | // B=1 (Non-cacheable) (0x0 << 24); // 访问权限:全权限 // 使能 MPU,使用默认内存映射作为后备 MPU->CTRL = (0x1 << 0) | (0x1 << 2); // ENABLE=1, PRIVDEFENA=1 // 使能 D-Cache(若未开启) SCB_EnableDCache(); } // 使用示例:初始化时调用一次 int main(void) { // ... 系统时钟、SDRAM 初始化 ... MPU_Config_NonCacheable(); // 之后直接操作帧缓冲,无需任何手动维护 uint16_t *fb = (uint16_t*)0xC0000000; fb[0] = 0xFFFF; // 直接写,LTDC 立即可见 while(1); } ``` ## 注意事项 - 确保 SDRAM 地址和大小与 MPU Region 匹配,且 Region 大小必须是 2 的幂次。 - 若 SDRAM 同时用于其他数据(如堆),需谨慎划分区域,避免影响性能。 - 此策略下,CPU 访问帧缓冲无 Cache 加速,但相比关闭整个 D-Cache,其他代码和数据仍受益于 Cache,整体性能最优。 # 总结与选择建议 | 策略 | 性能 | 复杂度 | 适用场景 | |------|------|--------|----------| | 关闭 D-Cache | 低 | 极低 | 简单显示,性能不敏感 | | 手动 Clean/Invalidate | 中 | 中 | 需要精细控制,帧率要求高 | | MPU Non-cacheable | 高 | 中高 | 图形应用,推荐生产使用 | 对于大多数 STM32F4 图形项目,推荐使用策略三(MPU 配置 Non-cacheable),它平衡了性能与开发效率。若项目对帧缓冲访问频率极高且需要 CPU 加速,可考虑策略二,但务必注意缓存行对齐和操作时机。 希望本文能帮助你彻底解决 D-Cache 与 SDRAM 的一致性问题,让图形显示更稳定流畅。欢迎在评论区交流你的实践经验!