# STM32H7 显存搬运的 Cache 一致性难题:MDMA 与 DMA2D 协同的三种实战策略 ## 一、问题根源:Cortex-M7 的 Cache 与 DMA 的“盲区” STM32H7 搭载 Cortex-M7 内核,主频高达 480MHz,内置 L1-Cache(I-Cache 和 D-Cache)。D-Cache 通过写回(Write-back)策略缓存内存数据,CPU 写入显存(通常位于 AXI SRAM 或外部 SDRAM)时,数据可能只停留在 Cache 中,尚未同步到物理内存。 而 MDMA(Master DMA)和 DMA2D(2D 图形加速器)作为总线主设备,直接访问物理内存,**它们看不到 CPU 的 Cache**。当 CPU 通过 Cache 写入数据后,立即启动 MDMA 搬运,MDMA 读到的可能是旧数据;反之,DMA2D 写入显存后,CPU 读取时可能命中过期的 Cache 行,导致花屏或数据错乱。 ## 二、三种实战策略详解 ### 策略一:全量失效(Invalidate)与 Clean——简单粗暴,适合小显存 **原理**:在 DMA 操作前,将 D-Cache 中对应区域的数据写回内存(Clean);在 DMA 写完后,使 Cache 行失效(Invalidate),强制 CPU 从内存重新读取。 **适用场景**:显存较小(<16KB),或对性能要求不苛刻的场合。 **配置步骤**: 1. 开启 D-Cache(默认开启)。 2. 在 MDMA 搬运前调用 `SCB_CleanDCache()` 或 `SCB_CleanDCache_by_Addr()`。 3. 在 DMA2D 写入完成后调用 `SCB_InvalidateDCache_by_Addr()`。 **代码示例**: ```c // 假设显存区域:uint32_t fb[320*240] __attribute__((section(".ARM.__at_0x24000000"))); #define FB_ADDR 0x24000000 #define FB_SIZE (320*240*4) // 320x240 ARGB8888 // MDMA 搬运前:确保 CPU 写入的数据落内存 SCB_CleanDCache_by_Addr((uint32_t*)FB_ADDR, FB_SIZE); // 启动 MDMA 搬运(从显存到另一缓冲区) MDMA_Start(&mdma_handle, FB_ADDR, dst_addr, FB_SIZE/4); // DMA2D 写入显存后:使 Cache 失效,让 CPU 重新读取 SCB_InvalidateDCache_by_Addr((uint32_t*)FB_ADDR, FB_SIZE); ``` **注意事项**: - 地址需 32 字节对齐(Cache line 大小)。 - 全量操作开销随区域大小线性增长,大显存时性能下降明显。 ### 策略二:按区域 Clean+Invalidate——精准打击,平衡性能 **原理**:只对 DMA 涉及的具体内存区域执行 Clean 或 Invalidate,避免全量操作。 **适用场景**:显存较大(如 800x480 的 RGB565 帧缓冲),且频繁更新局部区域。 **配置步骤**: 1. 定义显存区域和子区域。 2. 在 MDMA 读取前,对源区域执行 Clean;在 DMA2D 写入后,对目标区域执行 Invalidate。 3. 使用 `SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr`,注意地址对齐。 **代码示例**: ```c // 定义显存区域(假设 800x480 RGB565,每像素 2 字节) #define LCD_WIDTH 800 #define LCD_HEIGHT 480 #define LCD_PIXEL 2 #define LCD_FB_SIZE (LCD_WIDTH*LCD_HEIGHT*LCD_PIXEL) #define LCD_FB_ADDR 0xD0000000 // 外部 SDRAM // 更新一个矩形区域 (x, y, w, h) void update_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { uint32_t offset = (y*LCD_WIDTH + x) * LCD_PIXEL; uint32_t size = w * h * LCD_PIXEL; uint32_t addr = LCD_FB_ADDR + offset; // 1. CPU 绘制到显存(写入 Cache) draw_rect(x, y, w, h); // 2. 启动 DMA2D 搬运前,Clean 该区域 SCB_CleanDCache_by_Addr((uint32_t*)addr, size); // 3. 启动 DMA2D 搬运(例如从另一缓冲到显存) DMA2D_Start(&dma2d_handle, src_addr, addr, w, h); // 4. 搬运完成后,Invalidate 该区域,供 CPU 后续读取 SCB_InvalidateDCache_by_Addr((uint32_t*)addr, size); } ``` **注意事项**: - 确保区域大小向上取整到 32 字节倍数,否则可能遗漏边界。 - 多次小区域操作时,可合并相邻区域以减少开销。 ### 策略三:MPU 配置非 Cacheable 区域——釜底抽薪,零维护 **原理**:利用 Cortex-M7 的 MPU(Memory Protection Unit),将显存所在的地址区域配置为 **非 Cacheable(或 Write-through)**。这样 CPU 访问该区域时直接读写物理内存,DMA 与 CPU 看到的数据始终一致,无需任何软件维护。 **适用场景**:显存区域固定,且对性能要求极高(如持续视频流)。 **配置步骤**: 1. 使能 MPU,配置一个区域覆盖显存地址。 2. 设置 TEX=1, C=0, B=0(非 Cacheable)或 TEX=0, C=1, B=1(Write-through,但仍有缓存)。推荐非 Cacheable。 3. 在启动代码或系统初始化中配置。 **代码示例**(使用 STM32H7 HAL 库): ```c void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct = {0}; // 禁用 MPU HAL_MPU_Disable(); // 配置显存区域(例如 SDRAM 0xD0000000,大小 8MB) MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0xD0000000; MPU_InitStruct.Size = MPU_REGION_SIZE_8MB; MPU_InitStruct.SubRegionDisable = 0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; // TEX=1 MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 非 Cacheable MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); // 使能 MPU HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } // 在 main 初始化中调用 MPU_Config(); ``` **注意事项**: - 非 Cacheable 区域 CPU 访问速度较慢,但 DMA 效率不受影响。 - 若同时需要 CPU 频繁读写该区域,可考虑 Write-through(TEX=0, C=1, B=1),但仍有缓存一致性问题,需在 DMA 前 Clean。 - 确保 MPU 区域与显存实际大小匹配,避免覆盖其他关键内存。 ## 三、策略对比与选型建议 | 策略 | 性能开销 | 实现复杂度 | 适用场景 | |------|----------|------------|----------| | 全量失效 | 高(线性增长) | 低 | 小显存、低频操作 | | 按区域 Clean+Invalidate | 中(按需) | 中 | 大显存、局部更新 | | MPU 非 Cacheable | 低(无软件开销) | 中 | 固定显存、高频 DMA | **实战建议**: - 如果显存小于 16KB,直接使用策略一,代码简单。 - 如果显存较大且更新频繁,优先策略二,配合 DMA2D 的矩形搬运。 - 如果项目对实时性要求极高,且显存区域固定,果断采用策略三,彻底消除一致性隐患。 ## 四、总结 Cache 一致性是 STM32H7 高性能开发的必经之路。本文的三种策略从“软件维护”到“硬件隔离”层层递进,开发者可根据实际需求灵活选择。记住:**没有银弹,只有最合适的方案**。建议在项目初期就规划好显存布局和 Cache 策略,避免后期调试的深坑。 希望这篇文章能帮助你彻底驾驭 MDMA 与 DMA2D 的协同工作,让图形性能真正飞起来!