3. DMA2D传输异常:DMA2D在TouchGFX中的作用、传输中断导致的花屏、DMA2D配置检查清单
好,咱们来聊聊DMA2D。说实话,我刚开始用TouchGFX那会儿,对DMA2D的理解也就停留在“它是个硬件加速器”这个层面。直到有一次,我调试一个客户的项目,屏幕时不时闪一下,有时候还出现那种撕裂的条纹——嗯,就是那种看着像屏幕被撕开的感觉。查了三天,最后发现是DMA2D的配置出了问题。从那以后,我对这个模块就格外上心了。
3.1 DMA2D在TouchGFX中到底干了什么?
说白了,DMA2D就是一块专门干“像素搬运”和“像素处理”的硬件。CPU干这事太慢了,你想想看,一个800×480的屏幕,全屏刷新一次就要处理38万多个像素点。要是让CPU一个个去填,那帧率就别想要了。
在TouchGFX里,DMA2D主要干这几件事:
- 图层混合:把前景和背景按透明度叠在一起
- 颜色格式转换:比如从ARGB8888转成RGB565
- 图像拷贝:把显存里的数据搬到LCD接口
- 填充操作:快速清屏或画纯色区域
我个人习惯把DMA2D看作是TouchGFX的“后勤部长”。CPU只管发指令,具体怎么搬数据、怎么混合,全交给DMA2D去干。这样CPU就能腾出手来处理触摸、动画逻辑这些事。
核心理解:TouchGFX的渲染管线中,DMA2D承担了90%以上的像素级操作。一旦它出问题,屏幕表现就是“花屏”或“撕裂”。
3.2 传输中断导致的花屏——我踩过的坑
我记得有一次,项目都快交付了,客户突然反馈说屏幕在切换页面时会闪一下。我一开始以为是UI资源加载太慢,后来用逻辑分析仪抓了LTDC的时序,才发现问题出在DMA2D上。
DMA2D传输中断,说白了就是它正在干活的时候,被别的任务打断了。比如一个高优先级的中断服务程序跑进来,把DMA2D的寄存器给改了,或者把它的数据总线给占了。这时候DMA2D要么传输到一半停在那,要么把数据写到了错误的位置。
结果就是:帧缓冲里出现了一部分新数据、一部分旧数据,甚至还有乱码。LTDC读到这些数据,显示出来就是花屏。
典型场景:当DMA2D正在执行图层混合时,如果此时发生了一个高频率的定时器中断,且中断服务程序里调用了HAL_DMA2D_Start(),就会导致当前传输被异常终止。帧缓冲中残留的数据就是“半成品”,屏幕自然就花了。
3.3 DMA2D配置检查清单——照着查,少走弯路
我后来整理了一份检查清单,每次遇到花屏问题,先过一遍这个清单。说实话,90%的问题都能在这里找到答案。
| 检查项 | 说明 | 常见错误 |
|---|---|---|
| 时钟配置 | DMA2D时钟是否使能?频率是否在规格内? | 忘记在RCC中使能DMA2D时钟 |
| 中断优先级 | DMA2D中断优先级是否低于LTDC? | 优先级设置过高,导致频繁打断显示刷新 |
| 传输模式 | 使用的是寄存器模式还是存储器模式? | 模式选择错误导致数据错位 |
| 地址对齐 | 源地址和目标地址是否4字节对齐? | 非对齐地址导致传输效率下降或数据错误 |
| 颜色格式 | 源和目标颜色格式是否匹配? | ARGB8888转RGB565时未正确设置PF |
| 传输长度 | 总传输字节数是否超过DMA2D最大限制? | 一次传输超过64KB导致截断 |
| 中断标志 | 传输完成中断标志是否及时清除? | 未清除标志导致无法触发下一次传输 |
| 总线锁定 | 是否有其他外设长时间占用AHB总线? | FMC或SDIO长时间占用总线导致DMA2D等待 |
我的调试习惯:遇到花屏时,我会先检查DMA2D的ISR寄存器。如果看到TCIF(传输完成标志)一直没置位,说明传输根本没完成。这时候再去看中断优先级和总线占用情况,基本就能定位问题。
3.4 一个典型的配置代码示例
下面是我常用的DMA2D初始化配置。注意看注释里标注的几个关键点,都是容易翻车的地方。
// DMA2D初始化配置示例
void MX_DMA2D_Init(void)
{
hdma2d.Instance = DMA2D;
hdma2d.Init.Mode = DMA2D_M2M; // 存储器到存储器模式
hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565; // 输出颜色格式
hdma2d.Init.OutputOffset = 0; // 输出行偏移
hdma2d.Init.AlphaInverted = DISABLE; // 不反转Alpha
hdma2d.Init.RedBlueSwap = DISABLE; // 不交换红蓝分量
// 注意:这里一定要检查HAL_DMA2D_Init的返回值
if (HAL_DMA2D_Init(&hdma2d) != HAL_OK)
{
Error_Handler(); // 初始化失败,直接停住
}
// 配置中断优先级——我习惯设成比LTDC低一级
HAL_NVIC_SetPriority(DMA2D_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA2D_IRQn);
}
// 启动一次DMA2D传输
void Start_DMA2D_Transfer(uint32_t srcAddr, uint32_t dstAddr, uint32_t width, uint32_t height)
{
// 检查地址对齐——我吃过亏,现在每次都检查
if ((srcAddr & 0x3) || (dstAddr & 0x3))
{
// 地址未4字节对齐,做特殊处理或报错
return;
}
// 启动传输,使用阻塞模式方便调试
HAL_DMA2D_Start(&hdma2d, srcAddr, dstAddr, width, height);
HAL_DMA2D_PollForTransfer(&hdma2d, 1000);
}
3.5 避坑指南——我曾经犯过的错
我曾经在一个项目里,把DMA2D的中断优先级设成了0(最高优先级)。结果每次DMA2D传输完成时,中断服务程序直接抢占了LTDC的刷新线程。屏幕就开始闪,而且是那种有规律的闪——每次DMA2D传完一批数据就闪一下。
后来我把优先级降到3,问题就消失了。你想想看,LTDC刷新屏幕是周期性的,如果DMA2D中断频繁打断它,帧缓冲的读取时序就会乱掉。
还有一个坑:DMA2D的传输长度。我记得有一次做全屏拷贝,图像分辨率是1024×600,每个像素2字节,总大小就是1.2MB。但DMA2D的单次传输最大只能64KB。我没做分块传输,结果只传了前64KB,后面全是乱的。屏幕显示出来就是上半部分正常,下半部分花屏。
经验总结:DMA2D不是万能的,它有自己的限制。单次传输不要超过64KB,地址必须4字节对齐,中断优先级要低于显示刷新。这三条记住了,能省去很多调试时间。