STM32H7 帧缓冲搬运的时序对齐:MDMA 与 DMA2D 协同优化实战

为什么需要 MDMA 与 DMA2D 协同?

在 STM32H7 系列中,DMA2D 是专用的 2D 图形加速器,可执行像素格式转换、混合、填充等操作,但它的源地址和目的地址通常位于 AHB 总线域。而 MDMA(Master DMA)则能访问所有存储器映射区域,包括 AXI SRAM、SDRAM 等,且支持链表模式,可自动执行复杂的数据搬运。

当我们需要将摄像头采集的 RGB565 帧数据搬运到 SDRAM 中的帧缓冲,再通过 DMA2D 进行格式转换或混合后显示到 LCD 时,若简单串联使用,会因总线仲裁和 DMA 优先级差异导致时序错位,出现画面撕裂。

核心目标:让 MDMA 完成大块数据搬运,DMA2D 在数据就绪后立即处理,两者通过中断和标志位精确同步,避免数据竞争。

硬件特性与时序对齐原理

MDMA 的关键特性

  • 支持 8/16/32/64 位数据宽度,突发传输可达 128 字节。
  • 拥有 4 个 FIFO,可缓存数据,降低总线占用。
  • 支持链接列表(Linked List),可连续执行多个传输块,无需 CPU 干预。

DMA2D 的关键特性

  • 支持前景/背景层,可执行颜色填充、混合、格式转换。
  • 操作基于像素,需确保源数据完整。
  • 完成中断可触发 MDMA 启动下一轮搬运。

时序对齐策略

  1. 双缓冲机制:使用两个帧缓冲(Buffer A 和 Buffer B),MDMA 将新数据写入 Buffer B,同时 DMA2D 从 Buffer A 读取并显示。当 MDMA 完成写入后,交换角色。
  2. 中断同步:MDMA 传输完成中断(TCIF)触发 DMA2D 启动;DMA2D 完成中断(TCIF)触发 MDMA 启动下一轮。
  3. 优先级设置:MDMA 优先级设为高,DMA2D 设为中,确保搬运不被显示操作阻塞。

配置步骤

1. 初始化 MDMA

  • 使能 MDMA 时钟(RCC->AHB3ENR |= RCC_AHB3ENR_MDMAEN)。
  • 配置 MDMA 通道,设置源地址、目的地址、数据块大小(以 32 位字为单位)。
  • 启用传输完成中断,并设置优先级。

2. 初始化 DMA2D

  • 使能 DMA2D 时钟(RCC->AHB1ENR |= RCC_AHB1ENR_DMA2DEN)。
  • 配置 DMA2D 为内存到内存模式,设置源地址(帧缓冲)、目的地址(LCD 显存)。
  • 设置像素格式(如 ARGB8888)、行偏移等。
  • 启用完成中断。

3. 中断服务函数

  • 在 MDMA 中断中,启动 DMA2D 传输。
  • 在 DMA2D 中断中,切换缓冲角色,并启动 MDMA 搬运下一帧。

完整代码示例

以下代码基于 STM32H743,使用 HAL 库,假设帧缓冲位于 AXI SRAM,LCD 显存位于 SDRAM。

// 定义缓冲结构
#define FRAME_WIDTH  480
#define FRAME_HEIGHT 272
#define PIXEL_SIZE   2  // RGB565
#define FRAME_SIZE   (FRAME_WIDTH * FRAME_HEIGHT * PIXEL_SIZE)

uint8_t buffer_A[FRAME_SIZE] __attribute__((section(".RAM_D1")));
uint8_t buffer_B[FRAME_SIZE] __attribute__((section(".RAM_D1")));
uint8_t *active_buffer = buffer_A;
uint8_t *display_buffer = buffer_B;

// MDMA 句柄
MDMA_HandleTypeDef hmdma;
// DMA2D 句柄
DMA2D_HandleTypeDef hdma2d;

// MDMA 初始化
void MDMA_Init(void) {
    __HAL_RCC_MDMA_CLK_ENABLE();
    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_PRESERVE;
    hmdma.Init.SourceInc = MDMA_SRC_INC_WORD;
    hmdma.Init.DestinationInc = 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 = FRAME_SIZE / 4; // 32位字数量
    HAL_MDMA_Init(&hmdma);
    HAL_NVIC_SetPriority(MDMA_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(MDMA_IRQn);
}

// DMA2D 初始化
void DMA2D_Init(void) {
    __HAL_RCC_DMA2D_CLK_ENABLE();
    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);
    HAL_NVIC_SetPriority(DMA2D_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(DMA2D_IRQn);
}

// 启动 MDMA 搬运(从源到目标)
void MDMA_StartTransfer(uint8_t *src, uint8_t *dst) {
    HAL_MDMA_Start_IT(&hmdma, (uint32_t)src, (uint32_t)dst, FRAME_SIZE / 4, 0);
}

// 启动 DMA2D 转换(从帧缓冲到显存)
void DMA2D_StartTransfer(uint8_t *src) {
    HAL_DMA2D_Start_IT(&hdma2d, (uint32_t)src, (uint32_t)LCD_FB_ADDR, FRAME_WIDTH, FRAME_HEIGHT);
}

// MDMA 中断回调
void HAL_MDMA_TC_Callback(MDMA_HandleTypeDef *hmdma) {
    // 数据搬运完成,启动 DMA2D 显示
    DMA2D_StartTransfer(active_buffer);
}

// DMA2D 中断回调
void HAL_DMA2D_TC_Callback(DMA2D_HandleTypeDef *hdma2d) {
    // 显示完成,交换缓冲并启动下一轮搬运
    uint8_t *tmp = active_buffer;
    active_buffer = display_buffer;
    display_buffer = tmp;
    // 假设新数据已由外部填充到 active_buffer
    MDMA_StartTransfer(source_data, active_buffer);
}

// 主程序启动
int main(void) {
    HAL_Init();
    SystemClock_Config();
    MDMA_Init();
    DMA2D_Init();
    // 首次启动
    MDMA_StartTransfer(source_data, active_buffer);
    while(1) {
        // 主循环可处理其他任务
    }
}

注意事项

  • 内存对齐:帧缓冲地址必须按 32 字节对齐,否则 MDMA 可能产生总线错误。
  • 缓存一致性:如果使用 AXI SRAM,需注意 CPU 缓存与 DMA 的一致性,必要时使用 SCB_CleanDCache()SCB_InvalidateDCache()
  • 中断优先级:MDMA 中断优先级应高于 DMA2D,避免 DMA2D 完成中断被 MDMA 中断抢占导致时序错乱。
  • 传输长度单位:MDMA 的 BufferTransferLength 以 32 位字为单位,计算时务必除以 4。
  • 调试建议:使用逻辑分析仪或示波器观察帧同步信号,验证时序是否对齐。

总结

通过合理配置 MDMA 与 DMA2D 的中断同步和双缓冲机制,可以高效实现帧缓冲搬运与显示的流水线操作,大幅降低 CPU 负载并避免画面撕裂。本文提供的代码框架可直接应用于实际项目,但需根据具体硬件调整地址和参数。掌握这一技巧,将让你的 STM32H7 图形应用性能更上一层楼。