DMA2D 传输模式:四种核心玩法

DMA2D 这玩意儿,说白了就是个专门干图像搬运和处理的硬件加速器。我刚开始接触的时候,总觉得它跟普通 DMA 差不多,后来才发现完全不是一回事。它内置了四种传输模式,每种模式解决一类特定问题。

嗯,咱们一个一个来看。

模式一:寄存器到内存(Register-to-Memory)

这个模式最简单,也最容易被忽略。它的作用是用一个固定的颜色值去填充一整块内存区域。

你可能会问:「这跟 memset 有什么区别?」

区别大了。memset 是 CPU 逐字节写,DMA2D 是硬件流水线操作。我在一个 800×480 的屏幕上做清屏测试,memset 大概要 3ms,DMA2D 寄存器到内存模式只需要 0.5ms。帧率一下子就上来了。

典型应用场景: 清屏、背景填充、纯色矩形绘制

配置要点:

  • 源地址:直接填 NULL,因为数据来自寄存器
  • 前景色寄存器(FGPFCCR):设置你要填充的 ARGB 颜色值
  • 目标地址:指向帧缓冲区的起始位置
  • 传输大小:按像素数计算,不是字节数
// 寄存器到内存模式配置示例
DMA2D->CR = 0x00030000;        // 模式选择:寄存器到内存
DMA2D->FGPFCCR = 0xFF0000FF;   // 前景色:蓝色(ARGB)
DMA2D->OMAR = (uint32_t)fb_addr; // 目标地址:帧缓冲
DMA2D->NLR = (height << 16) | width; // 行数 | 每行像素数
DMA2D->CR |= 0x00000001;       // 启动传输
小技巧: 我习惯把常用颜色(黑、白、红、蓝)的寄存器配置做成宏定义,清屏时直接调用,省去每次计算颜色的麻烦。

模式二:内存到内存(Memory-to-Memory)

这个模式就是纯粹的图像搬运工。把一块内存区域的数据,原封不动地复制到另一块区域。

听起来跟 memcpy 一样?没错,功能上确实一样。但 DMA2D 的优势在于:

  • 不占用 CPU 周期,CPU 可以去做别的事
  • 传输速度比 CPU 快,尤其是大块数据
  • 支持硬件双缓冲切换

我在做 LVGL 双缓冲显示时,就用这个模式来切换前后台缓冲。前台显示完了,后台画好了,一个 DMA2D 传输就把后台内容搬到前台,CPU 全程不用管。

// 内存到内存模式配置示例
DMA2D->CR = 0x00000000;        // 模式选择:内存到内存
DMA2D->FGMAR = (uint32_t)src;  // 源地址
DMA2D->OMAR = (uint32_t)dst;   // 目标地址
DMA2D->FGOR = 0;               // 源偏移量(行尾到下一行首的像素数)
DMA2D->OOR = 0;                // 目标偏移量
DMA2D->NLR = (height << 16) | width;
DMA2D->CR |= 0x00000001;       // 启动
注意: 源地址和目标地址不能重叠。DMA2D 不像 CPU 的 memmove 能处理重叠区域,重叠会导致数据错乱。我踩过这个坑,调试了一下午才发现是地址重叠的问题。

模式三:内存到内存带像素格式转换(Memory-to-Memory with PFC)

这个模式是 DMA2D 的核心价值所在。它能在搬运图像的同时,完成像素格式的转换。

你想想看,LVGL 从外部存储(比如 SD 卡、Flash)加载图片时,图片可能是 RGB565 格式的,但你的帧缓冲是 ARGB8888 格式的。如果没有 DMA2D,你得用 CPU 逐像素转换,那效率... 嗯,画面卡顿是必然的。

DMA2D 的 PFC(Pixel Format Converter)模块,可以在一次传输中完成:

  • 像素格式转换(RGB565 → ARGB8888、RGB888 → RGB565 等)
  • Alpha 通道处理(固定 Alpha、每像素 Alpha)
  • 颜色空间转换(YUV ↔ RGB)
  • 抖动处理(减少色彩带)
源格式 目标格式 典型场景
RGB565 ARGB8888 SD 卡图片加载到帧缓冲
RGB888 RGB565 节省帧缓冲空间
ARGB8888 RGB565 低端屏幕显示
L8(8位索引色) ARGB8888 字体/图标渲染
// 内存到内存带格式转换配置示例
DMA2D->CR = 0x00010000;        // 模式选择:内存到内存带PFC
DMA2D->FGMAR = (uint32_t)src;  // 源地址(RGB565图片)
DMA2D->OMAR = (uint32_t)dst;   // 目标地址(ARGB8888帧缓冲)

// 配置源格式:RGB565
DMA2D->FGPFCCR = (0x0C << 0);  // 像素格式:RGB565
DMA2D->FGOR = 0;               // 源行偏移

// 配置目标格式:ARGB8888
DMA2D->OPFCCR = (0x00 << 0);  // 像素格式:ARGB8888
DMA2D->OOR = 0;                // 目标行偏移

// 设置Alpha值(透明度)
DMA2D->FGPFCCR |= (0xFF << 24); // 固定Alpha = 255(不透明)

DMA2D->NLR = (height << 16) | width;
DMA2D->CR |= 0x00000001;       // 启动
性能对比(800×480 图像,RGB565 → ARGB8888):
CPU 逐像素转换:约 12ms
DMA2D 硬件转换:约 1.2ms
性能提升:10 倍

模式四:内存到内存带颜色填充(Memory-to-Memory with Fill)

这个模式是模式一和模式二的结合体。它可以把一块内存区域的数据,叠加到一个固定的颜色背景上。

举个例子:你要在屏幕上显示一个带透明度的图标。图标本身是 ARGB 格式的,背景是纯色。DMA2D 可以在搬运图标数据的同时,把背景色填充进去,并且处理 Alpha 混合。

说白了,这就是硬件加速的「图像合成」操作。LVGL 的图层混合、窗口透明效果,底层就是靠这个模式实现的。

// 内存到内存带颜色填充配置示例
DMA2D->CR = 0x00020000;        // 模式选择:内存到内存带填充
DMA2D->FGMAR = (uint32_t)src;  // 源地址(图标数据)
DMA2D->BGMAR = (uint32_t)bg;   // 背景地址(纯色背景)
DMA2D->OMAR = (uint32_t)dst;   // 目标地址(合成结果)

// 配置前景(图标)
DMA2D->FGPFCCR = (0x00 << 0);  // ARGB8888
DMA2D->FGOR = 0;

// 配置背景(纯色)
DMA2D->BGPFCCR = (0x00 << 0);  // ARGB8888
DMA2D->BGOR = 0;

// 配置目标
DMA2D->OPFCCR = (0x00 << 0);  // ARGB8888
DMA2D->OOR = 0;

DMA2D->NLR = (height << 16) | width;
DMA2D->CR |= 0x00000001;       // 启动
避坑指南: 我曾经在配置这个模式时,忘了设置背景色寄存器的 Alpha 值,结果合成出来的图像背景是透明的,跟预期完全不一样。记住:背景色寄存器(BGPFCCR)的 Alpha 位一定要设置成 0xFF,除非你真的想要透明背景。

四种模式对比总结

模式 功能 典型用途 性能等级
寄存器→内存 纯色填充 清屏、背景 ★★★★★
内存→内存 数据搬运 缓冲切换、图像复制 ★★★★☆
内存→内存+PFC 搬运+格式转换 图片加载、格式适配 ★★★★★
内存→内存+填充 搬运+合成 图标渲染、图层混合 ★★★★☆

嗯,四种模式讲完了。你可能会觉得有点多,但实际项目中常用的就两种:寄存器到内存(清屏)和内存到内存带 PFC(图片加载)。另外两个模式,等你需要做更复杂的图像处理时自然会用到。

我的建议: 先吃透模式三(内存到内存带 PFC),因为 LVGL 外部存储加载图片的核心流程,就是靠这个模式把各种格式的图片转换成帧缓冲需要的格式。后面的章节会详细讲这个流程。
DMA2D 四种传输模式结构图 DMA2D 传输引擎 寄存器→内存 纯色填充 内存→内存 数据搬运 内存→内存+PFC 格式转换 内存→内存+填充 图像合成 典型应用场景 清屏/背景填充 双缓冲切换 外部存储图片加载 图标/图层混合 选择建议 LVGL 外部存储加载图片 → 优先使用「内存→内存+PFC」模式 清屏/背景 → 使用「寄存器→内存」模式