# STM32F4 使用 SDRAM 作为显存时的 Cache 一致性维护策略 ## 1. 问题背景 在嵌入式图形系统中,SDRAM 常被用作 LCD 的显存(FrameBuffer),以支持高分辨率显示。STM32F4 系列(如 STM32F429/439)内置了 D-Cache(数据缓存)和 I-Cache(指令缓存),当 CPU 写入显存时,数据可能只停留在 Cache 中,而 DMA(如 LTDC 控制器)读取 SDRAM 时却直接访问物理内存,导致两者数据不一致,表现为屏幕显示异常(如花屏、撕裂)。 ## 2. 原理剖析 ### 2.1 Cache 的工作机制 STM32F4 的 D-Cache 采用写回(Write-back)策略:CPU 写数据时,先写入 Cache,并标记为脏(Dirty),只有在缓存行被替换或显式 Clean 操作时,才将数据写回 SDRAM。而 LTDC(LCD 控制器)通过 DMA 直接读取 SDRAM,不经过 Cache。因此,若 CPU 更新显存后未及时写回,LTDC 读取的仍是旧数据。 ### 2.2 一致性问题的触发场景 - CPU 绘制图形到显存 → LTDC 读取显存刷新屏幕 - DMA 从外部存储器拷贝图像到显存 → CPU 读取显存进行图像处理 这两种场景都会因 Cache 与内存数据不同步而产生错误。 ## 3. 维护策略 ### 策略一:关闭 D-Cache(简单粗暴) 直接禁用 D-Cache,所有内存访问都直接走 SDRAM,彻底避免一致性问题。但会显著降低 CPU 访问内存的性能,尤其是图形绘制时。 **配置方法:** ```c SCB_DisableDCache(); ``` **适用场景:** 对性能要求不高,或调试阶段快速验证。 ### 策略二:手动 Clean + Invalidate(推荐) 在关键操作前后,手动维护 Cache。 - **Clean**:将脏缓存行写回 SDRAM,确保 DMA 读取到最新数据。 - **Invalidate**:使缓存行失效,下次 CPU 读取时从 SDRAM 重新加载,避免读到旧数据。 **配置步骤:** 1. 开启 D-Cache:`SCB_EnableDCache()` 2. 在 CPU 写完显存后,调用 `SCB_CleanDCache_by_Addr()` 写回数据。 3. 在 CPU 读取 DMA 写入的数据前,调用 `SCB_InvalidateDCache_by_Addr()` 使缓存失效。 **代码示例:** ```c // 显存地址和大小(需按 32 字节对齐) #define FRAME_BUFFER_ADDR 0xC0000000 #define FRAME_BUFFER_SIZE (800*480*2) // RGB565 格式 // CPU 绘制完成后,写回 Cache void FrameBuffer_Flush(void) { SCB_CleanDCache_by_Addr((uint32_t*)FRAME_BUFFER_ADDR, FRAME_BUFFER_SIZE); } // 在 CPU 读取 DMA 写入的数据前,使缓存失效 void FrameBuffer_Invalidate(void) { SCB_InvalidateDCache_by_Addr((uint32_t*)FRAME_BUFFER_ADDR, FRAME_BUFFER_SIZE); } ``` **注意:** 地址和大小必须 32 字节对齐,否则操作无效。 ### 策略三:双缓冲 + 切换(高级) 使用两个显存区域,一个用于 CPU 绘制,另一个用于 LTDC 显示。当 CPU 完成绘制后,切换 LTDC 的显存地址,并 Clean 新显存区域。这样避免了在绘制过程中 LTDC 读取到不完整数据,同时减少了 Cache 操作频率。 **配置步骤:** 1. 定义两个缓冲区:`frame_buf[2]`,地址对齐到 32 字节。 2. LTDC 当前显示 `frame_buf[active]`。 3. CPU 绘制到 `frame_buf[1-active]`,绘制完成后 Clean 该区域。 4. 切换 LTDC 显存地址到新缓冲区,并设置 active 翻转。 **代码示例:** ```c static uint8_t frame_buf[2][FRAME_BUFFER_SIZE] __attribute__((aligned(32))); static volatile uint8_t active_buf = 0; void LTDC_SwitchBuffer(uint32_t addr) { LTDC->Layer1->WHPCR &= ~(LTDC_LxWHPCR_WHSTPOS | LTDC_LxWHPCR_WHSPPOS); LTDC->Layer1->WHPCR |= ((addr >> 16) & 0xFFF) | ((addr & 0xFFFF) << 16); // 实际需根据 LTDC 寄存器配置,此处简化 } void Draw_Scene(void) { uint8_t *draw_buf = frame_buf[1-active_buf]; // 绘制内容到 draw_buf SCB_CleanDCache_by_Addr((uint32_t*)draw_buf, FRAME_BUFFER_SIZE); LTDC_SwitchBuffer((uint32_t)draw_buf); active_buf ^= 1; } ``` ## 4. 完整示例:结合 LTDC 和 SDRAM 以下代码演示了在 STM32F429 上初始化 SDRAM、开启 Cache、配置 LTDC,并实现显存刷新的完整流程。 ```c #include "stm32f4xx.h" #define SDRAM_BASE 0xC0000000 #define LCD_WIDTH 800 #define LCD_HEIGHT 480 #define PIXEL_SIZE 2 // RGB565 #define FB_SIZE (LCD_WIDTH * LCD_HEIGHT * PIXEL_SIZE) // SDRAM 初始化(略,需根据具体硬件配置) void SDRAM_Init(void) { // 配置 FMC 控制器,使能 SDRAM 银行 } // LTDC 初始化(略) void LTDC_Init(void) { // 配置时序、层参数,设置显存地址为 SDRAM_BASE } int main(void) { // 系统时钟、GPIO、FMC、LTDC 初始化 SDRAM_Init(); LTDC_Init(); // 开启 D-Cache(注意:必须在 SDRAM 初始化之后) SCB_EnableDCache(); uint16_t *fb = (uint16_t*)SDRAM_BASE; while (1) { // CPU 绘制:填充红色 for (int i = 0; i < FB_SIZE/2; i++) { fb[i] = 0xF800; } // 写回 Cache,确保 LTDC 看到新数据 SCB_CleanDCache_by_Addr((uint32_t*)fb, FB_SIZE); HAL_Delay(100); // 填充蓝色 for (int i = 0; i < FB_SIZE/2; i++) { fb[i] = 0x001F; } SCB_CleanDCache_by_Addr((uint32_t*)fb, FB_SIZE); HAL_Delay(100); } } ``` ## 5. 注意事项 - **对齐要求**:`SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr` 的地址和长度必须 32 字节对齐,否则操作无效。建议显存缓冲区使用 `__attribute__((aligned(32)))`。 - **性能权衡**:频繁 Clean 整个显存会消耗 CPU 时间。如果只修改部分区域,可只 Clean 修改的行,但需注意行大小(通常 32 字节)。 - **DMA 方向**:如果 DMA 从外设写入显存,CPU 读取前必须 Invalidate,否则可能读到 Cache 中的旧数据。 - **中断安全**:在中断中执行 Cache 操作时,注意优先级和原子性,避免与主循环冲突。 - **调试技巧**:出现花屏时,可先暂时关闭 D-Cache 验证是否一致性导致,再优化策略。 ## 6. 总结 STM32F4 的 Cache 一致性是使用 SDRAM 显存时必须面对的问题。通过关闭 Cache、手动 Clean/Invalidate 或双缓冲切换,可以有效解决。推荐使用策略二(手动维护)作为默认方案,兼顾性能与可靠性。对于高性能图形应用,结合双缓冲和局部 Clean 能获得最佳效果。理解 Cache 的工作原理,并根据实际场景选择合适策略,是嵌入式开发者必备的技能。