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,说白了就是填几个关键寄存器。我习惯按这个顺序来:
- 控制寄存器(CR):启动、停止、设置模式
- 源地址寄存器(SAR):数据从哪里来
- 目标地址寄存器(DAR):数据到哪里去
- 帧缓冲区寄存器(FGPFCCR / BGPFCCR):设置像素格式
- 行偏移寄存器(OOR):处理图像行与行之间的间隔
- 行数和列数寄存器(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);
这里要注意的是OPFCCR和OCOLR寄存器。前者设置输出像素格式,后者设置填充颜色值。颜色值的格式必须跟输出格式一致。
我曾经犯过一个低级错误:用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的刷新机制结合起来。嗯,到时候会有更多实战经验分享。