4、DMA2D硬件加速原理:寄存器配置、颜色格式转换与内存操作

DMA2D 这个外设,说白了就是 ST 给 Cortex-M 系列配的一个「画图加速器」。我刚开始接触时也觉得它不过是个高级 DMA,后来在项目里被刷屏卡得头皮发麻,才真正体会到——没有 DMA2D,LVGL 在高分辨率屏上根本跑不动。

这一节我们就把 DMA2D 的底裤扒干净。寄存器怎么配、颜色格式怎么转、内存拷贝和填充怎么用硬件做,一条一条说清楚。

4.1 DMA2D 到底在加速什么?

先看一张整体结构图,帮你建立直观印象。

DMA2D 硬件加速核心流程 LVGL 绘制请求 像素数据 + 坐标 DMA2D 引擎 寄存器配置 → 启动 硬件自动搬运 + 转换 内存拷贝 P2P 模式 颜色格式转换 M2M_PFC 模式 填充 R2M 模式 帧缓冲 💡 三种模式对应三种场景: • P2P(内存到内存):直接拷贝,不转换格式,用于帧缓冲滚动、双缓冲切换 • M2M_PFC(带格式转换):从外部存储加载图片时,把 JPEG/PNG 解码后的 ARGB8888 转成 RGB565 • R2M(寄存器到内存):用固定颜色值填充一块区域,清屏、画纯色矩形时效率极高

DMA2D 的核心价值就三个字:不占 CPU。你配好寄存器,写个启动位,它就自己吭哧吭哧把活干完了。CPU 可以趁这个时间去跑 LVGL 的任务调度、处理触摸事件,或者干脆睡一觉省电。

4.2 寄存器配置:别被手册吓到

DMA2D 的寄存器其实没几个,但手册写得又臭又长。我帮你提炼出最常用的几个,配一张速查表。

寄存器 作用 关键位域 我常用的值
CR 控制寄存器,启动传输、选择模式 MODE[1:0]、START、TCIE 0x00030001(M2M_PFC + 启动)
NDTR 传输像素总数 NDT[16:0] 图像宽 × 高
FGMAR 前景层内存地址(输入) 32位地址 解码后的图像缓冲区地址
FGOR 前景层行偏移(字节) LO[13:0] 图像宽度 × 像素字节数
FGPFCCR 前景层像素格式 PF[2:0] 0x2(ARGB8888)或 0x1(RGB565)
OMAR 输出内存地址 32位地址 帧缓冲地址
OPFCCR 输出像素格式 PF[2:0] 0x1(RGB565,如果你的屏是 16 位色)
OOR 输出行偏移(字节) LO[13:0] 屏幕宽度 × 2(RGB565 每像素 2 字节)
我的习惯:每次配完寄存器,先读一遍回读确认。DMA2D 的寄存器写入有时序要求,连续写太快会丢配置。我踩过这个坑——图像花了一半,查了两天才发现是寄存器没写进去。

4.3 颜色格式转换:ARGB8888 → RGB565

这是 LVGL 加载外部存储图片时最常用的场景。解码库(比如 libjpeg、PNGlib)吐出来的通常是 ARGB8888,但你的屏大概率是 RGB565。用 CPU 逐像素转换?太慢了。DMA2D 一行代码都不用写,配好寄存器就自动转。

配置要点:

  • 模式选 M2M_PFC(CR 寄存器的 MODE 位 = 10)
  • 前景格式设 ARGB8888(FGPFCCR.PF = 010)
  • 输出格式设 RGB565(OPFCCR.PF = 001)
  • NDTR 填像素总数,不是字节数

举个例子,一张 320×240 的图片:

// 假设:
// src_buf  — 解码后的 ARGB8888 数据
// fb_buf   — 帧缓冲(RGB565)
// width    = 320
// height   = 240

DMA2D->CR      = 0x00000000;          // 先复位
DMA2D->FGMAR   = (uint32_t)src_buf;
DMA2D->FGOR    = 0;                   // 连续存储,无行偏移
DMA2D->FGPFCCR = 0x2;                // ARGB8888
DMA2D->OMAR    = (uint32_t)fb_buf;
DMA2D->OOR     = 0;                   // 连续存储
DMA2D->OPFCCR  = 0x1;                // RGB565
DMA2D->NDTR    = width * height;      // 像素总数
DMA2D->CR      = 0x00030001;          // M2M_PFC + 启动

// 等待完成
while (DMA2D->CR & DMA2D_CR_START);
性能对比:同样转换一张 320×240 的图片,CPU 逐像素转换大约需要 8~12ms(取决于主频),DMA2D 只需要 1.2ms。在 LVGL 的刷屏循环里,这 7~10ms 的差距就是卡顿与流畅的分界线。

4.4 内存拷贝:P2P 模式

有时候你不需要转换格式,只是想把一块内存搬到另一块。比如双缓冲切换、帧缓冲滚动。这时候用 P2P 模式。

配置更简单:

  • MODE = 00(P2P)
  • 前景和输出格式设成一样
  • NDTR 填像素数
DMA2D->CR      = 0x00000000;
DMA2D->FGMAR   = (uint32_t)src;
DMA2D->OMAR    = (uint32_t)dst;
DMA2D->NDTR    = pixel_count;
DMA2D->CR      = 0x00000001;   // P2P + 启动
while (DMA2D->CR & DMA2D_CR_START);

嗯,这里要注意:P2P 模式下 FGOR 和 OOR 可以设行偏移,实现「非连续区域拷贝」。比如只拷贝屏幕中间一块矩形区域,而不是整帧。这在 LVGL 的局部刷新里很有用。

4.5 内存填充:R2M 模式

清屏、画纯色背景、填充矩形——这些操作在 LVGL 里非常频繁。用 CPU 写循环填充?太浪费了。R2M 模式直接从寄存器拿颜色值往内存里填,速度是 memcpy 的 3~5 倍。

配置要点:

  • MODE = 11(R2M)
  • 不需要设 FGMAR,颜色值写在 FGPFCCR 的高 24 位
  • OPFCCR 决定输出格式
// 用红色填充整个帧缓冲(RGB565 格式)
uint16_t color = 0xF800;  // 红色

DMA2D->CR      = 0x00000000;
DMA2D->OMAR    = (uint32_t)fb_buf;
DMA2D->OOR     = 0;
DMA2D->OPFCCR  = 0x1;                // RGB565
DMA2D->NDTR    = width * height;
DMA2D->FGPFCCR = (color << 16) | 0x1; // 颜色值放在高16位,低3位是格式
DMA2D->CR      = 0x00030001;          // R2M + 启动
while (DMA2D->CR & DMA2D_CR_START);
我曾经踩过的坑:R2M 模式下,FGPFCCR 的高位颜色值必须和 OPFCCR 的格式匹配。如果你设输出格式为 RGB565,但颜色值给的是 ARGB8888 的 0xFFFF0000,DMA2D 不会帮你转换——它直接截断高位,结果颜色完全不对。正确做法:先算好目标格式的颜色值再填进去。

4.6 中断与同步:别让 CPU 空等

上面例子都用了 while 轮询等待。实际项目中,我更推荐用中断:

  1. 使能 CR 寄存器的 TCIE 位(传输完成中断)
  2. 在中断服务函数里清标志位,然后通知 LVGL 刷新完成
  3. CPU 在 DMA2D 干活期间可以去处理其他任务
// 启动时使能中断
DMA2D->CR = 0x00030003;  // M2M_PFC + TCIE + START

// 中断服务函数
void DMA2D_IRQHandler(void) {
    if (DMA2D->ISR & DMA2D_ISR_TCIF) {
        DMA2D->IFCR = DMA2D_IFCR_CTCIF;  // 清标志
        // 通知 LVGL:传输完成,可以刷新了
        lv_disp_flush_ready(&disp_drv);
    }
}

我个人习惯在 LVGL 的 disp_flush_cb 回调里启动 DMA2D 传输,然后在中断里调用 lv_disp_flush_ready。这样整个刷屏流程完全不阻塞 CPU,帧率能再提一档。

4.7 避坑指南:DMA2D 的「脾气」

DMA2D 虽然快,但有几个地方容易翻车:

问题 现象 原因 解决办法
图像花屏 颜色错乱、条纹 FGOR/OOR 行偏移算错 行偏移 = 图像宽度 × 每像素字节数,不是像素数
传输卡死 START 位一直为 1 NDTR 填了 0 或地址不对齐 检查 NDTR 是否 > 0,地址是否 4 字节对齐
颜色偏色 整体色调不对 前景/输出格式不匹配 确认 FGPFCCR 和 OPFCCR 的 PF 位
中断不触发 ISR 标志位一直为 0 NVIC 没使能或优先级配置错 检查 NVIC_EnableIRQ(DMA2D_IRQn)
一个小技巧:调试 DMA2D 时,先用小尺寸图像(比如 16×16)测试。配好寄存器后,用调试器读回 DMA2D 的 CR、NDTR、FGMAR、OMAR 寄存器,确认写入值和你预期一致。没问题了再上大图。