STM32H7 无 CPU 干预的图层混合:MDMA 与 LTDC 深度联动实战
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 高性能 MCU 上,LTDC 控制器可驱动 RGB 屏幕,但多图层混合通常需要 CPU 参与像素运算,消耗大量主频。本文深入解析如何利用 MDMA(Master DMA)与 LTDC 的联动机制,通过硬件自动搬运与像素格式转换,实现双图层 Alpha 混合,全程无需 CPU 干预。文章涵盖原理分析、CubeMX 配置步骤、完整代码示例及关键注意事项,帮助开发者释放 CPU 性能,打造流畅的 GUI 体验。
# STM32H7 无 CPU 干预的图层混合:MDMA 与 LTDC 深度联动实战
## 1. 为什么需要 MDMA 与 LTDC 联动?
在嵌入式 GUI 开发中,图层混合(如半透明菜单、弹窗阴影)是常见需求。传统做法是 CPU 读取两个图层像素,进行 Alpha 混合计算,再写回显存。对于 800x480 的 RGB565 屏幕,每帧混合运算量高达 76.8 万次,CPU 占用率极高。
STM32H7 的 LTDC 控制器支持最多 8 个图层,每个图层可独立配置像素格式和 Alpha 值,硬件自动完成混合。但问题在于:LTDC 的 Alpha 混合是**固定透明度**的,无法实现逐像素渐变效果。若需动态混合,仍需 CPU 介入。
MDMA(Master DMA)的引入改变了这一局面。MDMA 支持**内存到内存**的搬运,且具备 2D 寻址能力,可配合 LTDC 的帧同步信号(Line Interrupt)触发,实现图层数据的硬件级预处理。通过 MDMA 将背景图层数据搬运到前景图层缓冲区,并利用 LTDC 的 Alpha 混合功能,即可实现无 CPU 干预的叠加效果。
## 2. 硬件原理:MDMA 如何与 LTDC 协同
### 2.1 LTDC 图层混合机制
LTDC 每个图层都有独立的 PFCR(像素格式配置寄存器)和 CACR(恒定 Alpha 配置寄存器)。当两个图层重叠时,LTDC 根据前景图层的 Alpha 值(恒定值或像素值)与背景图层进行混合,公式为:
`输出像素 = (前景像素 × Alpha + 背景像素 × (255 - Alpha)) / 255`
### 2.2 MDMA 的 2D 搬运能力
MDMA 支持块传输(Block Transfer),可配置源/目的地址偏移、数据块大小和传输次数。利用此特性,MDMA 可以**按行搬运**图层数据,并在搬运过程中进行数据转换(如 RGB888 转 RGB565),甚至可对数据进行移位或掩码操作。
### 2.3 联动触发方式
LTDC 在每行扫描结束时会触发 Line Interrupt,该中断可配置为触发 MDMA 请求。MDMA 收到请求后,搬运下一行数据到目标图层缓冲区。这样,LTDC 扫描到某行时,MDMA 已提前准备好该行数据,形成流水线作业。
## 3. 实现方案设计
假设场景:背景图层(Layer0)为静态图片,前景图层(Layer1)为半透明菜单,菜单区域需要动态更新。我们使用 MDMA 将菜单数据从源缓冲区搬运到 LTDC 的 Layer1 帧缓冲区,并设置 Layer1 的恒定 Alpha 为 128(50% 透明度)。
### 3.1 硬件资源
- STM32H743 开发板(或类似 H7 系列)
- RGB 屏幕(800x480,RGB565)
- 外部 SDRAM(用于帧缓冲区)
### 3.2 软件架构
- 使用 STM32CubeMX 初始化 LTDC、MDMA 和 DMA2D(可选)
- 配置 LTDC 双图层,Layer0 为背景,Layer1 为前景
- 配置 MDMA 为内存到内存模式,触发源为 LTDC Line Interrupt
- 在中断服务函数中启动 MDMA 传输(或使用硬件自动触发)
## 4. 配置步骤(基于 STM32CubeMX)
### 4.1 LTDC 配置
1. 开启 LTDC,设置时序参数(Hsync、Vsync、HBP、VBP 等)
2. 使能 Layer0:像素格式 RGB565,帧缓冲区地址指向背景图像
3. 使能 Layer1:像素格式 RGB565,帧缓冲区地址指向前景缓冲区,设置恒定 Alpha 为 128
4. 在 Layer1 的配置中,启用 **Dithering** 和 **Alpha 混合** 选项
### 4.2 MDMA 配置
1. 在 CubeMX 中启用 MDMA,选择 MDMA_Channel0
2. 请求源选择 `LTDC_LINE`(需在 NVIC 中使能 LTDC 全局中断)
3. 传输模式:内存到内存,数据宽度 32 位(RGB565 可打包为 32 位)
4. 设置块大小(Block Size)为一行像素数(如 800),传输次数为 1(每次中断传输一行)
5. 源地址和目的地址分别指向源缓冲区和 Layer1 帧缓冲区,地址偏移按行自动增加
### 4.3 中断配置
- 使能 LTDC 全局中断,在中断回调中启动 MDMA 传输(或使用硬件请求自动触发,但需注意时序)
- 推荐使用硬件自动触发:LTDC 的 Line Interrupt 直接连接 MDMA 请求,无需 CPU 介入
## 5. 完整代码示例
以下代码基于 HAL 库,展示了核心初始化及中断处理逻辑。
```c
/* 全局变量 */
#define LCD_WIDTH 800
#define LCD_HEIGHT 480
#define LINE_BYTES (LCD_WIDTH * 2) // RGB565 每像素 2 字节
uint16_t background_buffer[LCD_HEIGHT][LCD_WIDTH]; // 背景图像数据
uint16_t foreground_buffer[LCD_HEIGHT][LCD_WIDTH]; // 前景图像数据(菜单)
uint16_t source_buffer[LCD_HEIGHT][LCD_WIDTH]; // 源数据(需搬运到前景)
/* LTDC 句柄 */
LTDC_HandleTypeDef hltdc;
/* MDMA 句柄 */
MDMA_HandleTypeDef hmdma_mdma_channel0;
/* 初始化 LTDC 和 MDMA */
void MX_LTDC_Init(void)
{
// ... 省略时序配置,使用 CubeMX 生成代码
// 配置 Layer0(背景)
LTDC_LayerCfgTypeDef layer0_cfg = {0};
layer0_cfg.WindowX0 = 0;
layer0_cfg.WindowY0 = 0;
layer0_cfg.WindowX1 = LCD_WIDTH;
layer0_cfg.WindowY1 = LCD_HEIGHT;
layer0_cfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
layer0_cfg.FBStartAdress = (uint32_t)background_buffer;
layer0_cfg.Alpha = 255; // 不透明
HAL_LTDC_ConfigLayer(&hltdc, &layer0_cfg, 0);
// 配置 Layer1(前景)
LTDC_LayerCfgTypeDef layer1_cfg = {0};
layer1_cfg.WindowX0 = 100;
layer1_cfg.WindowY0 = 50;
layer1_cfg.WindowX1 = 500;
layer1_cfg.WindowY1 = 350;
layer1_cfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
layer1_cfg.FBStartAdress = (uint32_t)foreground_buffer;
layer1_cfg.Alpha = 128; // 50% 透明度
HAL_LTDC_ConfigLayer(&hltdc, &layer1_cfg, 1);
}
void MX_MDMA_Init(void)
{
hmdma_mdma_channel0.Instance = MDMA_Channel0;
hmdma_mdma_channel0.Init.Request = MDMA_REQUEST_LTDC_LINE;
hmdma_mdma_channel0.Init.TransferTriggerMode = MDMA_BLOCK_TRANSFER;
hmdma_mdma_channel0.Init.Priority = MDMA_PRIORITY_HIGH;
hmdma_mdma_channel0.Init.Endianness = MDMA_LITTLE_ENDIAN;
hmdma_mdma_channel0.Init.SourceInc = MDMA_SRC_INC_WORD;
hmdma_mdma_channel0.Init.DestinationInc = MDMA_DEST_INC_WORD;
hmdma_mdma_channel0.Init.SourceDataSize = MDMA_SRC_DATASIZE_WORD;
hmdma_mdma_channel0.Init.DestDataSize = MDMA_DEST_DATASIZE_WORD;
hmdma_mdma_channel0.Init.DataAlignment = MDMA_DATAALIGN_PACK;
hmdma_mdma_channel0.Init.BufferTransferLength = LCD_WIDTH / 2; // 每行 800 像素,32 位传输,即 400 次
hmdma_mdma_channel0.Init.SourceBurst = MDMA_SOURCE_BURST_4BEATS;
hmdma_mdma_channel0.Init.DestBurst = MDMA_DEST_BURST_4BEATS;
hmdma_mdma_channel0.Init.SourceBlockAddressOffset = 0;
hmdma_mdma_channel0.Init.DestBlockAddressOffset = 0;
hmdma_mdma_channel0.Init.SourceDataBlockCount = 1;
hmdma_mdma_channel0.Init.DestDataBlockCount = 1;
hmdma_mdma_channel0.Init.SourceDataBlockAddressOffset = 0;
hmdma_mdma_channel0.Init.DestDataBlockAddressOffset = 0;
HAL_MDMA_Init(&hmdma_mdma_channel0);
// 配置 MDMA 请求映射到 LTDC 行中断
HAL_MDMA_ConfigRequest(&hmdma_mdma_channel0, MDMA_REQUEST_LTDC_LINE, MDMA_REQ_MAPPING_0);
}
/* 启动 MDMA 传输(每次 LTDC 行中断时调用) */
void Start_MDMA_Transfer(uint32_t line_index)
{
// 源地址:源缓冲区第 line_index 行
uint32_t src_addr = (uint32_t)&source_buffer[line_index][0];
// 目的地址:前景缓冲区第 line_index 行(注意窗口偏移)
uint32_t dst_addr = (uint32_t)&foreground_buffer[line_index][0];
HAL_MDMA_Start_IT(&hmdma_mdma_channel0, src_addr, dst_addr, LCD_WIDTH/2, 1);
}
/* LTDC 中断回调 */
void HAL_LTDC_LineEven_EventCallback(LTDC_HandleTypeDef *hltdc)
{
static uint32_t line = 0;
if (line < LCD_HEIGHT) {
Start_MDMA_Transfer(line);
line++;
} else {
line = 0;
}
}
/* 主程序 */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_LTDC_Init();
MX_MDMA_Init();
// 初始化源缓冲区(例如生成渐变图案)
for (int y = 0; y < LCD_HEIGHT; y++) {
for (int x = 0; x < LCD_WIDTH; x++) {
source_buffer[y][x] = (x * 32) | ((y * 32) << 5); // 简单渐变
}
}
// 启动 LTDC
HAL_LTDC_Start(&hltdc);
while (1)
{
// 主循环无需处理图层混合,可执行其他任务
}
}
```
## 6. 注意事项与优化建议
### 6.1 时序同步
- MDMA 必须在 LTDC 扫描到该行之前完成搬运,否则会出现撕裂。建议将 LTDC 的 Line Interrupt 提前几行触发(通过配置 `LTDC_LIPCR` 寄存器),或使用双缓冲机制。
- 若使用硬件自动触发,需确保 MDMA 的传输时间小于一行扫描时间(例如 60Hz 刷新率下,一行约 20μs)。
### 6.2 内存带宽
- MDMA 和 LTDC 同时访问 SDRAM 时,可能产生总线冲突。建议将 MDMA 的优先级设为高,并启用 AXI 总线 QoS 设置。
- 使用 32 位数据宽度可提高带宽利用率,但需注意地址对齐。
### 6.3 像素格式
- 若使用 RGB888 格式,则每像素 3 字节,MDMA 搬运需调整为 24 位对齐,可考虑使用 DMA2D 进行格式转换。
- 本例使用 RGB565,每像素 2 字节,可打包为 32 位传输,效率更高。
### 6.4 中断开销
- 虽然 MDMA 传输无需 CPU 干预,但 LTDC 行中断仍会触发 CPU 中断。若每行都触发,CPU 负载仍较高。可改为每 N 行触发一次,或使用 MDMA 的链表模式(Linked List)实现多行连续传输。
### 6.5 调试建议
- 使用逻辑分析仪或示波器观察 LTDC 的 VSYNC 和 Line 信号,验证时序。
- 在 MDMA 传输完成中断中添加标志位,检查是否出现传输溢出。
## 7. 总结
通过 MDMA 与 LTDC 的联动,STM32H7 可以在无 CPU 干预的情况下实现图层混合叠加,大幅降低 CPU 负载。本文提供的方案适用于动态菜单、弹窗等场景,且易于扩展。开发者可根据实际需求调整 MDMA 传输策略,例如使用链表实现多行预取,或结合 DMA2D 进行像素格式转换,进一步优化性能。