STM32H7 的 MDMA 与 DMA2D 协同实现双缓冲图形加速的配置要点

引言

在嵌入式图形系统中,CPU 直接搬运像素数据会严重占用处理周期。STM32H7 系列内置的 DMA2D(Chrom-ART Accelerator)和 MDMA(Master DMA)提供了硬件级的数据搬运能力。DMA2D 擅长像素格式转换、混合和填充,而 MDMA 则能高效地在内存区域间搬移大块数据。将两者结合,可以构建一条从帧缓冲区到显示控制器的自动流水线,实现双缓冲的无缝切换,大幅提升图形刷新率。本文面向有 STM32 开发经验的工程师,聚焦配置要点与代码实现。

原理:双缓冲与硬件加速

双缓冲的核心是:后台缓冲区用于绘制新帧,前台缓冲区用于显示。当后台绘制完成后,需要快速交换指针或复制数据。在 STM32H7 上,更高效的方式是使用 DMA2D 将后台缓冲区的像素数据搬运到 LTDC(LCD 控制器)的当前帧地址,或者直接切换 LTDC 的帧地址(但需注意时序)。而 MDMA 可以在此过程中扮演两个角色:

  • 预加载:将外部 SDRAM 中的图像数据块预先搬运到内部 SRAM(如 D1 域),供 DMA2D 快速处理。
  • 后处理:将 DMA2D 处理完的数据快速搬移到 LTDC 的帧缓冲区(可能位于 SDRAM)。

关键点在于,MDMA 和 DMA2D 都挂载在 AXI 总线上,它们可以并行工作,但需要避免访问冲突。通过合理配置 MDMA 的通道优先级和 DMA2D 的 FIFO 阈值,可以最大化吞吐量。

配置步骤

1. 时钟与总线使能

STM32H7 的 MDMA 和 DMA2D 均位于 AXI 总线域,需确保 RCC 中对应时钟已使能。

__HAL_RCC_MDMA_CLK_ENABLE();
__HAL_RCC_DMA2D_CLK_ENABLE();

注意:MDMA 的时钟源可选 AXI 或 AHB,通常使用 AXI(默认),频率可达 240MHz。

2. 内存区域规划

双缓冲需要至少两个缓冲区,建议分配在 D1 域(AXI SRAM)或外部 SDRAM,并确保 32 字节对齐(MDMA 要求)。

#define BUFFER_SIZE  (800*480*2)  // RGB565, 800x480
__attribute__((section(".ARM.__at_0x24000000"))) uint8_t buf_back[BUFFER_SIZE];
__attribute__((section(".ARM.__at_0x24000000+BUFFER_SIZE"))) uint8_t buf_front[BUFFER_SIZE];

3. MDMA 配置(用于搬运图像块到 SRAM)

MDMA 支持 Linked List 模式,可链式执行多个传输。以下配置一个通道,用于从 SDRAM 搬运 32KB 数据到内部 SRAM。

MDMA_HandleTypeDef hmdma;

void MDMA_Init(void) {
    hmdma.Instance = MDMA_Channel0;
    hmdma.Init.Request = MDMA_REQUEST_SW;  // 软件触发
    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.DestinationInc = MDMA_DST_INC_WORD;
    hmdma.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
    hmdma.Init.DestDataSize = MDMA_DST_DATASIZE_WORD;
    hmdma.Init.DataAlignment = MDMA_DATAALIGN_PACK;
    hmdma.Init.BufferTransferLength = 32*1024/4;  // 以 word 为单位
    hmdma.Init.SourceBurst = MDMA_SOURCE_BURST_32BEATS;
    hmdma.Init.DestBurst = MDMA_DEST_BURST_32BEATS;
    hmdma.Init.SourceAddress = (uint32_t)src_addr;
    hmdma.Init.DestAddress = (uint32_t)buf_back;
    HAL_MDMA_Init(&hmdma);
}

4. DMA2D 配置(用于像素格式转换与搬运)

DMA2D 可以执行内存到内存的搬运,并支持 RGB565 到 RGB888 等转换。以下配置将后台缓冲区内容复制到前台缓冲区(模拟双缓冲切换)。

DMA2D_HandleTypeDef hdma2d;

void DMA2D_Init(void) {
    hdma2d.Instance = DMA2D;
    hdma2d.Init.Mode = DMA2D_M2M;  // 内存到内存
    hdma2d.Init.ColorMode = DMA2D_RGB565;
    hdma2d.Init.OutputOffset = 0;
    hdma2d.LayerCfg[0].InputOffset = 0;
    hdma2d.LayerCfg[0].InputColorMode = DMA2D_RGB565;
    hdma2d.LayerCfg[0].AlphaMode = DMA2D_NO_MODIF_ALPHA;
    hdma2d.LayerCfg[0].InputAlpha = 0xFF;
    HAL_DMA2D_Init(&hdma2d);
}

void DMA2D_CopyBuffer(uint32_t src, uint32_t dst, uint16_t width, uint16_t height) {
    HAL_DMA2D_Start(&hdma2d, src, dst, width, height);
    HAL_DMA2D_PollForTransfer(&hdma2d, 100);
}

5. 协同工作流程

实际应用中,MDMA 和 DMA2D 可以流水线化:MDMA 从 SDRAM 预加载下一帧图像到 SRAM,同时 DMA2D 处理当前帧。以下是一个简单示例:

void Start_Frame_Transfer(void) {
    // 1. 启动 MDMA 搬运下一帧到后台缓冲区
    HAL_MDMA_Start_IT(&hmdma, (uint32_t)next_frame_sdram, (uint32_t)buf_back, 32*1024/4);
    // 2. 同时启动 DMA2D 将后台缓冲区内容复制到前台(或直接处理)
    DMA2D_CopyBuffer((uint32_t)buf_back, (uint32_t)buf_front, 800, 480);
    // 3. 等待 MDMA 完成,然后更新 LTDC 帧地址(可选)
}

注意:MDMA 和 DMA2D 的完成中断应分别处理,避免资源竞争。

完整代码示例(简化)

// 中断回调
void HAL_MDMA_XferCpltCallback(MDMA_HandleTypeDef *hmdma) {
    // 通知主循环可以安全使用 buf_back
}

void HAL_DMA2D_XferCpltCallback(DMA2D_HandleTypeDef *hdma2d) {
    // 切换 LTDC 帧地址到 buf_front
    LTDC->Layer1->CFBAR = (uint32_t)buf_front;
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    // 初始化 LTDC、SDRAM 等
    MDMA_Init();
    DMA2D_Init();
    // 注册回调
    HAL_MDMA_RegisterCallback(&hmdma, HAL_MDMA_XFER_CPLT_CB_ID, MDMA_XferCpltCallback);
    HAL_DMA2D_RegisterCallback(&hdma2d, HAL_DMA2D_XFER_CPLT_CB_ID, DMA2D_XferCpltCallback);
    // 启动第一次传输
    Start_Frame_Transfer();
    while(1) {
        // 主循环处理其他任务
    }
}

注意事项

  • 内存对齐:MDMA 要求源和目标地址按传输宽度对齐(如 32 位对齐),否则会触发错误。建议使用 32 字节对齐。
  • 缓存一致性:STM32H7 的 D-Cache 可能导致 DMA 看到的数据不一致。需在 DMA 操作前调用 SCB_CleanDCache() 或使用 MPU 配置为非缓存区域。
  • 中断优先级:MDMA 和 DMA2D 中断应设置为较高优先级,且避免在回调中执行耗时操作。
  • 总线仲裁:当 MDMA 和 DMA2D 同时访问 SDRAM 时,可能产生带宽竞争。可通过调整 MDMA 的 burst 大小和 DMA2D 的 FIFO 阈值来优化。
  • 描述符链:对于复杂传输,使用 MDMA 的 Linked List 模式可以避免 CPU 干预,但需注意描述符本身的内存放置(建议放在 D2 域 SRAM)。

结语

MDMA 与 DMA2D 的协同可以显著降低图形系统的 CPU 负载,实现高效的双缓冲刷新。关键在于合理规划内存、配置正确的触发模式,并处理好缓存一致性问题。本文提供的配置要点和代码框架可作为开发起点,实际项目中还需根据具体屏幕分辨率和刷新率调整参数。掌握这两者,你的 STM32H7 图形性能将得到质的飞跃。