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 搬运帧缓冲,并在传输完成后交换缓冲。

// 定义帧缓冲结构
#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 的流畅度。