4、DMA2D基础与配置:DMA2D工作原理、寄存器配置、内存到内存拷贝、内存到内存填充、PFC(像素格式转换)

好,咱们开始聊DMA2D。说实话,这个外设在LVGL的硬件加速里,属于最基础也最核心的一环。你想想看,如果没有它,CPU得亲自去搬砖——搬像素数据、做格式转换、填充显存。那画面太美我不敢看。

我个人习惯把DMA2D叫做「图形搬运工」。它不负责计算,只负责搬数据。但搬得快、搬得巧,就能让CPU腾出手来干正事。

4.1 DMA2D工作原理

DMA2D的全称是Direct Memory Access 2D,说白了就是专门为图形数据设计的DMA。它跟普通DMA最大的区别在于:它懂像素格式。

普通DMA搬数据,就是从一个地址搬到另一个地址,字节对字节。DMA2D不一样,它知道什么是RGB565,什么是ARGB8888,甚至能在搬运过程中做格式转换。

它的内部结构大致是这样的:

  • 前端通道(Foreground):处理前景图层数据
  • 后端通道(Background):处理背景图层数据
  • 输出通道(Output):将处理后的数据写入目标地址
  • PFC单元:像素格式转换器,负责格式转换和颜色调整

我记得第一次看STM32的参考手册时,被这些通道绕晕了。后来发现,其实大部分场景只用到一个通道——输出通道。前端和后端是给图层混合用的,咱们后面再聊。

核心要点:DMA2D的工作模式分为三种:

  • 寄存器到内存(R2M):用固定颜色填充一块内存区域
  • 内存到内存(M2M):从源地址拷贝数据到目标地址
  • 内存到内存带PFC(M2M_PFC):拷贝的同时做像素格式转换

你可能会问:为什么没有内存到寄存器?嗯,因为DMA2D是单向的,只能往内存写数据,不能从外设读数据。这个限制要注意。

4.2 寄存器配置

配置DMA2D,说白了就是填几个关键寄存器。我习惯按这个顺序来:

  1. 控制寄存器(CR):启动、停止、设置模式
  2. 源地址寄存器(SAR):数据从哪里来
  3. 目标地址寄存器(DAR):数据到哪里去
  4. 帧缓冲区寄存器(FGPFCCR / BGPFCCR):设置像素格式
  5. 行偏移寄存器(OOR):处理图像行与行之间的间隔
  6. 行数和列数寄存器(NLR):要处理多少行、每行多少像素

这里有个坑,我曾经踩过:行偏移。如果你要拷贝的源图像和目标图像在内存中不是连续排列的(比如从摄像头缓冲区拷贝到显存),行偏移必须算对。否则图像会斜着走。

避坑指南:我曾经在做一个双缓冲切换时,忘了设置行偏移,结果图像每行都错位了16个像素。查了整整一个下午才发现是OOR寄存器没配。记住:行偏移 = 图像宽度(像素) × 每像素字节数。

来看一个最简单的配置示例——内存到内存拷贝:

// 假设源地址 src_addr,目标地址 dst_addr,拷贝 320x240 像素,RGB565格式
DMA2D->CR = 0x00000000;          // 先复位控制寄存器
DMA2D->CR = DMA2D_CR_MODE_0;     // 设置为 M2M 模式(Mode = 00)
DMA2D->SAR = (uint32_t)src_addr; // 源地址
DMA2D->DAR = (uint32_t)dst_addr; // 目标地址
DMA2D->OOR = 0;                  // 连续内存,行偏移为0
DMA2D->NLR = (320 << 16) | 240; // 每行320像素,共240行
DMA2D->CR |= DMA2D_CR_START;     // 启动传输

// 等待传输完成
while (DMA2D->CR & DMA2D_CR_START);

嗯,就这么简单。但实际项目中,我建议你加上超时机制,防止DMA2D卡死。

4.3 内存到内存拷贝

这是DMA2D最基础的功能。说白了就是把一块内存的数据原封不动搬到另一块内存。

什么时候用?比如你要把一张图片从Flash搬到SRAM,或者把帧缓冲区的内容复制到另一个缓冲区做双缓冲。

性能怎么样?我实测过,在STM32F429上,DMA2D拷贝一个800x480的RGB565图像(约750KB),只需要不到2ms。如果用CPU的memcpy,大概要10ms以上。差距很明显。

小技巧:如果你只是做简单的内存拷贝,而且数据量不大(比如几十个字节),用CPU的memcpy反而更快。因为DMA2D的启动开销大约有几百个时钟周期。我一般建议数据量超过1KB才用DMA2D。

4.4 内存到内存填充

这个功能更常用。用固定颜色填充一块内存区域,比如清屏、画矩形背景。

配置方式跟拷贝类似,但源地址不用了,改成设置填充颜色:

// 用红色(0xF800)填充整个屏幕,320x240,RGB565
DMA2D->CR = 0x00000000;
DMA2D->CR = DMA2D_CR_MODE_1;     // R2M模式(Mode = 01)
DMA2D->DAR = (uint32_t)dst_addr; // 目标地址
DMA2D->OOR = 0;
DMA2D->NLR = (320 << 16) | 240;
DMA2D->OPFCCR = 0x02;            // 输出格式为RGB565
DMA2D->OCOLR = 0xF800;           // 填充颜色:红色
DMA2D->CR |= DMA2D_CR_START;

while (DMA2D->CR & DMA2D_CR_START);

这里要注意的是OPFCCROCOLR寄存器。前者设置输出像素格式,后者设置填充颜色值。颜色值的格式必须跟输出格式一致。

我曾经犯过一个低级错误:用RGB565格式填充,但颜色值写成了ARGB8888的格式。结果屏幕一片花。嗯,这种错误查起来特别费劲,因为代码逻辑完全正确,就是颜色值不对。

4.5 PFC(像素格式转换)

这是DMA2D最强大的功能。它可以在搬运数据的同时,把像素格式从一种转换成另一种。

常见的转换场景:

  • 从Flash读取的图片是RGB888,但显示需要RGB565
  • 摄像头输出是YUYV,但LVGL需要ARGB8888
  • 从网络接收的JPEG解码后是RGB888,需要转成RGB565再显示

配置方式是在M2M模式基础上,加上PFC使能:

// 从RGB888源数据转换到RGB565目标数据
DMA2D->CR = 0x00000000;
DMA2D->CR = DMA2D_CR_MODE_2;     // M2M_PFC模式(Mode = 10)
DMA2D->SAR = (uint32_t)src_addr; // 源地址(RGB888数据)
DMA2D->DAR = (uint32_t)dst_addr; // 目标地址(RGB565数据)
DMA2D->OOR = 0;
DMA2D->NLR = (320 << 16) | 240;

// 设置源格式和目标格式
DMA2D->FGPFCCR = 0x04;           // 源格式:RGB888(4表示24位RGB)
DMA2D->OPFCCR = 0x02;            // 目标格式:RGB565

DMA2D->CR |= DMA2D_CR_START;
while (DMA2D->CR & DMA2D_CR_START);

你可能会问:转换质量怎么样?说实话,从RGB888到RGB565会有颜色损失,因为RGB565只有16位,丢失了部分颜色信息。但人眼基本看不出来,除非你显示的是渐变图像。

性能对比:用CPU做像素格式转换,每个像素都要做移位和掩码操作。320x240的图像,CPU大概要花5ms。DMA2D只需要1ms左右,而且不占用CPU时间。

我个人建议:能用DMA2D做的格式转换,就别用CPU做。这不仅是性能问题,更是代码简洁性的问题。你想想看,用DMA2D只需要配几个寄存器,用CPU要写一堆循环和位操作,哪个更香?

好了,DMA2D的基础就聊到这里。下一节咱们会讲DMA2D的中断和错误处理,以及如何跟LVGL的刷新机制结合起来。嗯,到时候会有更多实战经验分享。