# STM32H7 双缓冲图形加速:MDMA 与 DMA2D 协同配置深度解析 ## 一、为什么需要 MDMA + DMA2D 协同? 在 STM32H7 系列(如 H743/H750)中,CPU 主频高达 480MHz,但图形渲染(如 LVGL、TouchGFX)若直接由 CPU 操作显存,会占用大量内核周期。DMA2D 专为 2D 图形加速设计,可执行颜色填充、位块传输(BLT)、像素格式转换等操作,但 DMA2D 本身无法直接访问外部 SDRAM(除非通过总线矩阵),且其传输完成中断会打断 CPU。 MDMA(Master DMA)是 STM32H7 新增的高性能 DMA 控制器,支持内存到内存、外设到内存的突发传输,可链接列表(Linked List)方式描述复杂传输任务。通过 MDMA 将数据从后台缓冲(如 SDRAM)搬运到 DMA2D 的输入 FIFO,或直接搬运到 LTDC(LCD 控制器)的帧缓冲,可彻底解放 CPU。 **协同优势**: - 双缓冲:后台缓冲(渲染)与前台缓冲(显示)切换,避免撕裂。 - 零拷贝:MDMA 直接搬运像素数据,无需 CPU 介入。 - 流水线:DMA2D 处理像素格式转换(如 RGB565→ARGB8888),MDMA 负责大块数据搬运,两者并行。 ## 二、硬件架构与内存映射 STM32H7 的总线矩阵支持多个主设备(CPU、MDMA、DMA2D、LTDC)并发访问内存。关键点: - MDMA 可访问所有内存空间(包括 SDRAM、内部 SRAM、Flash)。 - DMA2D 只能通过 AHB 主接口访问,但可配置为从 MDMA 的 FIFO 读取数据(需通过 DMA2D 的外设接口)。 - 双缓冲通常使用两个帧缓冲:Buffer0 和 Buffer1,位于 SDRAM 中(如 0xC0000000)。 ## 三、关键配置步骤 ### 1. 时钟与 GPIO 初始化 确保 MDMA、DMA2D、LTDC 时钟使能,并配置 SDRAM 控制器(FMC)。 ```c __HAL_RCC_MDMA_CLK_ENABLE(); __HAL_RCC_DMA2D_CLK_ENABLE(); __HAL_RCC_LTDC_CLK_ENABLE(); // FMC SDRAM 初始化略,需确保时序正确 ``` ### 2. DMA2D 配置(以像素格式转换为例) 配置 DMA2D 为内存到内存模式,输入 RGB565,输出 ARGB8888。 ```c DMA2D_HandleTypeDef hdma2d; hdma2d.Instance = DMA2D; hdma2d.Init.Mode = DMA2D_M2M; // 内存到内存 hdma2d.Init.ColorMode = DMA2D_ARGB8888; // 输出格式 hdma2d.Init.OutputOffset = 0; // 行偏移 hdma2d.Init.AlphaInverted = DISABLE; hdma2d.Init.RedBlueSwap = DISABLE; HAL_DMA2D_Init(&hdma2d); // 配置输入层(前景层) DMA2D_FG_InitTypeDef fg; fg.ColorMode = DMA2D_RGB565; fg.AlphaMode = DMA2D_NO_MODIF_ALPHA; fg.InputOffset = 0; fg.RedBlueSwap = DISABLE; fg.Alpha = 0xFF; // 不透明 HAL_DMA2D_ConfigLayer(&hdma2d, 0, &fg); ``` ### 3. MDMA 配置(链接列表模式) MDMA 支持链接列表,可描述多个传输块。这里配置一个简单的内存到内存传输,将后台缓冲数据搬运到 DMA2D 的输入地址(即 DMA2D 的 FG 基地址)。 ```c MDMA_HandleTypeDef hmdma; MDMA_LinkNodeTypeDef node; hmdma.Instance = MDMA_Channel0; hmdma.Init.Request = MDMA_REQUEST_MEM2MEM; hmdma.Init.TransferTriggerMode = MDMA_BUFFER_TRANSFER; hmdma.Init.Priority = MDMA_PRIORITY_HIGH; hmdma.Init.Endianness = MDMA_LITTLE_ENDIAN; hmdma.Init.SourceInc = MDMA_SRC_INC_WORD; // 源地址递增 hmdma.Init.DestInc = MDMA_DEST_INC_WORD; // 目的地址递增 hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD; hmdma.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD; hmdma.Init.DataAlignment = MDMA_DATAALIGN_PACK; hmdma.Init.BufferTransferLength = 1024*600; // 总字节数(需为块大小倍数) HAL_MDMA_Init(&hmdma); // 配置链接节点 node.SourceAddress = (uint32_t)background_buffer; // 源地址 node.DestAddress = (uint32_t)&DMA2D->FGCOLR; // 注意:这里应指向 DMA2D 的输入地址(如 FG 的基地址寄存器,但实际需通过外设接口) // 实际中,DMA2D 的输入地址由 DMA2D 的 FG 基地址寄存器(FG0AR)决定,MDMA 应写入该寄存器?不,MDMA 直接搬运数据到 DMA2D 的 FIFO,需配置 DMA2D 为外设模式。 // 简化:MDMA 将数据从 SDRAM 搬运到内部 SRAM 的中间缓冲,DMA2D 再从该缓冲读取。 // 此处为示例,实际需根据具体应用调整。 node.BlockSize = 1024*600; // 块大小 node.SourceInc = MDMA_SRC_INC_WORD; node.DestInc = MDMA_DEST_INC_WORD; node.SourceDataSize = MDMA_SRC_DATASIZE_WORD; node.DestDataSize = MDMA_DEST_DATASIZE_WORD; node.NextNodeAddress = 0; // 单节点 HAL_MDMA_ConfigLinkNode(&node); HAL_MDMA_Start(&hmdma, (uint32_t)background_buffer, (uint32_t)intermediate_buffer, 1024*600); ``` 注意:MDMA 与 DMA2D 协同的典型模式是:MDMA 将数据从 SDRAM 搬运到内部 SRAM(DMA2D 可访问),然后 DMA2D 处理格式转换并输出到 LTDC 帧缓冲。或者,若 DMA2D 支持外设模式,可配置 MDMA 直接写入 DMA2D 的 FIFO,但需仔细查阅参考手册。 ### 4. 双缓冲切换与同步 使用 LTDC 的影式寄存器(Shadow Registers)实现缓冲切换,配合 MDMA 和 DMA2D 完成中断。 ```c // 在 DMA2D 传输完成中断中,切换 LTDC 帧缓冲 void DMA2D_IRQHandler(void) { if (__HAL_DMA2D_GET_FLAG(&hdma2d, DMA2D_FLAG_TC)) { __HAL_DMA2D_CLEAR_FLAG(&hdma2d, DMA2D_FLAG_TC); // 切换前台缓冲 LTDC->Layer1->WHPCR = (uint32_t)&front_buffer; // 等待垂直消隐期(可选) while ((LTDC->CDSR & LTDC_CDSR_VSYNCS) == 0); // 交换指针 uint32_t *tmp = front_buffer; front_buffer = back_buffer; back_buffer = tmp; } } ``` ### 5. 缓存一致性处理 STM32H7 的 D-Cache 可能导致 DMA 访问的数据不一致。必须对 MDMA 和 DMA2D 涉及的缓冲区执行 Cache 维护操作。 ```c // 在 MDMA 启动前,将源缓冲区(后台缓冲)的 Cache 行回写 SCB_CleanDCache_by_Addr((uint32_t*)background_buffer, buffer_size); // 在 MDMA 完成后,使目的缓冲区(中间缓冲)的 Cache 失效 SCB_InvalidateDCache_by_Addr((uint32_t*)intermediate_buffer, buffer_size); // 在 DMA2D 启动前,同样处理输入缓冲区 SCB_CleanDCache_by_Addr((uint32_t*)intermediate_buffer, buffer_size); // 在 DMA2D 完成后,使输出缓冲区(帧缓冲)失效 SCB_InvalidateDCache_by_Addr((uint32_t*)front_buffer, buffer_size); ``` ## 四、完整代码示例(简化) 以下是一个初始化函数,整合了 MDMA 和 DMA2D 的配置,并启动一次传输。 ```c void Graphics_Accelerated_Transfer(uint32_t *src, uint32_t *dst, uint32_t size) { // 1. 清理源缓存 SCB_CleanDCache_by_Addr((uint32_t*)src, size); // 2. 配置 DMA2D 为 M2M 模式,输入 RGB565,输出 ARGB8888(假设) // 这里省略具体配置,使用之前初始化的 hdma2d // 3. 配置 MDMA 将 src 搬运到中间缓冲(内部 SRAM) HAL_MDMA_Start(&hmdma, (uint32_t)src, (uint32_t)intermediate, size); // 等待 MDMA 完成(可中断) HAL_MDMA_PollForTransfer(&hmdma, HAL_MAX_DELAY); // 4. 使中间缓冲缓存失效,供 DMA2D 读取 SCB_InvalidateDCache_by_Addr((uint32_t*)intermediate, size); // 5. 启动 DMA2D 传输,从中间缓冲到帧缓冲 HAL_DMA2D_Start(&hdma2d, (uint32_t)intermediate, (uint32_t)dst, width, height); // 等待完成(或中断) HAL_DMA2D_PollForTransfer(&hdma2d, HAL_MAX_DELAY); // 6. 使帧缓冲缓存失效,供 LTDC 读取 SCB_InvalidateDCache_by_Addr((uint32_t*)dst, size); } ``` ## 五、注意事项与常见陷阱 - **内存对齐**:MDMA 和 DMA2D 要求源/目的地址按 4 字节对齐,否则可能触发总线错误。建议使用 `__attribute__((aligned(4)))`。 - **传输长度**:MDMA 的 `BufferTransferLength` 必须是块大小的整数倍,且块大小需匹配 DMA2D 的行宽。 - **中断优先级**:MDMA 和 DMA2D 中断应设置为高于 LTDC 垂直消隐中断,避免帧撕裂。 - **链接列表**:复杂场景下,使用 MDMA 的链接列表可避免 CPU 多次启动传输,但需注意节点内存的 Cache 一致性。 - **DMA2D 外设模式**:若使用 DMA2D 的外设模式(如从 MDMA 读取),需配置 `DMA2D->CR` 的 `MODE` 位,并确保 MDMA 的目的地址指向 DMA2D 的 FIFO 地址(如 `DMA2D_FG0AR` 寄存器映射的地址)。 - **功耗与带宽**:MDMA 和 DMA2D 同时工作会增加总线负载,需合理配置 AHB 总线矩阵的 QoS 优先级。 ## 六、总结 MDMA 与 DMA2D 的协同,将图形数据搬运与像素处理分离,充分利用 STM32H7 的总线架构,显著降低 CPU 占用。通过双缓冲机制,配合缓存一致性维护,可实现高帧率、无撕裂的 GUI 显示。开发者需深入理解 MDMA 的链接列表和 DMA2D 的层配置,并根据实际内存布局调整传输策略。