# STM32H7 使用 MDMA 与 DMA2D 协同实现双缓冲图形加速的配置细节 ## 一、为什么需要 MDMA 与 DMA2D 协同? STM32H7 内置 Cortex-M7 内核,主频高达 480MHz,但 CPU 直接搬运大量像素数据会消耗大量时钟周期。DMA2D(Chrom-ART Accelerator)专用于 2D 图形操作,如颜色填充、像素拷贝、混合(Alpha Blending)和格式转换。而 MDMA(Master DMA)则是一种更通用的高速 DMA,支持内存到内存、外设到内存等复杂传输,且拥有独立的 FIFO 和突发传输能力。 双缓冲图形系统通常需要两个帧缓冲(Front Buffer 和 Back Buffer)。渲染时,CPU 或 DMA2D 写入 Back Buffer,完成后交换指针,显示控制器读取 Front Buffer。若由 CPU 负责交换和拷贝,会引入延迟。通过 MDMA 在后台将渲染完成的 Back Buffer 搬运到显示缓冲区,同时 DMA2D 准备下一帧,可实现渲染与传输的流水线并行,显著提升帧率。 ## 二、硬件架构与工作原理 - **DMA2D**:支持多种模式(寄存器到内存、内存到内存、内存到内存并 PFC、内存到内存并混合)。其内部包含像素格式转换器(PFC)和 Alpha 混合器,可自动完成 RGB565、ARGB8888 等格式转换。 - **MDMA**:拥有 16 个通道,每个通道支持 8/16/32 位数据宽度,可配置突发长度(1~128 字节),并支持链表模式(Linked List),可连续执行多个传输块。 - **协同流程**: 1. CPU 配置 DMA2D 将渲染数据(如精灵图)混合到 Back Buffer。 2. 渲染完成后,触发 MDMA 将 Back Buffer 搬运到显示控制器的帧缓冲地址。 3. MDMA 传输完成中断中,交换前后缓冲指针,并启动下一帧渲染。 ## 三、CubeMX 配置步骤 ### 1. 使能 DMA2D - 在 `Connectivity` 中找到 `DMA2D`,选择 `Memory-to-Memory with PFC` 模式。 - 设置输入/输出颜色模式(如输入 RGB565,输出 ARGB8888)。 - 配置传输方向、行偏移等参数(可在代码中动态修改)。 ### 2. 使能 MDMA - 在 `System Core` 中选择 `MDMA`,添加一个通道(如 MDMA_Channel0)。 - 设置请求为 `Memory To Memory`,数据宽度 32 位,突发长度 16 字节。 - 使能中断(传输完成和错误)。 ### 3. 配置 DMA 请求映射 - 在 `NVIC` 中使能 MDMA 中断优先级(建议高于显示刷新中断)。 - 确保 MDMA 的时钟源为 AXI 或 AHB,且优先级高于普通 DMA。 ## 四、关键代码实现 以下代码演示了使用 MDMA 搬运帧缓冲,并在传输完成后交换缓冲。 ```c // 定义帧缓冲结构 #define BUFFER_WIDTH 480 #define BUFFER_HEIGHT 272 #define PIXEL_SIZE 2 // RGB565 uint8_t back_buffer[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32))); uint8_t front_buffer[BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE] __attribute__((aligned(32))); // MDMA 传输完成回调 void MDMA_IRQHandler(void) { if (LL_MDMA_IsActiveFlag_TC0(MDMA)) { LL_MDMA_ClearFlag_TC0(MDMA); // 交换缓冲指针(实际中可更新显示控制器地址) uint8_t *tmp = front_buffer; front_buffer = back_buffer; back_buffer = tmp; // 触发下一帧渲染(例如通过信号量通知任务) } } // 启动 MDMA 搬运 void MDMA_StartTransfer(void) { LL_MDMA_ConfigAddresses(MDMA, MDMA_CHANNEL_0, (uint32_t)back_buffer, // 源地址 (uint32_t)front_buffer, // 目的地址 LL_MDMA_DATAWIDTH_32, // 数据宽度 LL_MDMA_DATAWIDTH_32); LL_MDMA_ConfigTransfer(MDMA, MDMA_CHANNEL_0, BUFFER_WIDTH * BUFFER_HEIGHT * PIXEL_SIZE / 4, // 传输次数(32位为单位) LL_MDMA_BURST_16, LL_MDMA_BURST_16); LL_MDMA_EnableChannel(MDMA, MDMA_CHANNEL_0); } // DMA2D 渲染一帧(示例:填充颜色) void DMA2D_FillBuffer(uint32_t color) { DMA2D->CR = DMA2D_CR_MODE_MEM2MEM; // 内存到内存模式 DMA2D->FGMAR = (uint32_t)back_buffer; DMA2D->FGOR = 0; DMA2D->BGMAR = (uint32_t)back_buffer; DMA2D->BGOR = 0; DMA2D->FGPFCCR = DMA2D_PFCCR_CM_RGB565; DMA2D->BGPFCCR = DMA2D_PFCCR_CM_RGB565; DMA2D->OPFCCR = DMA2D_PFCCR_CM_RGB565; DMA2D->NLR = (BUFFER_HEIGHT << 16) | BUFFER_WIDTH; DMA2D->OCOLR = color; DMA2D->CR |= DMA2D_CR_START; while (DMA2D->CR & DMA2D_CR_START); // 等待完成(实际可用中断) } // 主循环示例 int main(void) { HAL_Init(); SystemClock_Config(); MX_DMA2D_Init(); MX_MDMA_Init(); while (1) { DMA2D_FillBuffer(0xFFFF); // 填充白色 MDMA_StartTransfer(); // 启动搬运 // 此时 CPU 可执行其他任务,或准备下一帧渲染 // 等待 MDMA 完成中断(通过信号量阻塞) } } ``` ## 五、注意事项与常见陷阱 - **内存对齐**:MDMA 要求源和目的地址按数据宽度对齐(32 位时 4 字节对齐)。建议使用 `__attribute__((aligned(32)))` 或 `memalign` 分配缓冲。 - **缓存一致性**:STM32H7 的 D-Cache 可能导致 DMA 看到陈旧数据。需要在 DMA 传输前调用 `SCB_CleanDCache()`,传输后调用 `SCB_InvalidateDCache()`。 - **中断优先级**:MDMA 中断优先级应高于显示刷新中断(如 LTDC),否则可能撕裂画面。 - **DMA2D 与 MDMA 并发**:两者都访问 AXI 总线,可能产生仲裁延迟。建议将 DMA2D 优先级设为高,MDMA 设为中,并合理配置突发长度以减少总线占用。 - **传输完成标志**:MDMA 的传输完成标志需在中断中清除,否则会持续触发中断。 - **调试建议**:使用逻辑分析仪或示波器测量帧同步信号,验证双缓冲切换是否与垂直消隐同步。 ## 六、总结 通过 MDMA 与 DMA2D 的协同,STM32H7 可以实现高效的图形流水线:DMA2D 负责像素级操作,MDMA 负责大块数据搬运,CPU 仅需配置和响应中断。本文提供的配置步骤和代码可直接应用于实际项目,但需根据具体显示接口(如 LTDC、DSI)调整缓冲地址和时序。掌握这些细节,能显著提升嵌入式 GUI 的流畅度。