Lvgl使用RGB接口屏动画拖尾噪声调试

Creative Commons
本作品采用知识共享署名

本文记录处理lvgl使用RGB接口屏动画拖尾噪声的调试过程。

由于实际的需求需要将LCD驱动和lvgl脱离出Zephyr,重写LCD驱动和重新移植了lvgl。但在移植完成后运行lvgl demo时发现控件动画在水平方向有噪声,在群里和论坛上请教过不少同学,大家都给出了很多很好的调试和建议方向,虽然最后找到问题的原因不是大家所猜测的但是这些调试和建议方向是很宝贵的,因此做此纪录。

环境

硬件环境:rt1052+166M SDRAM, 野火/正点原子 RGB接口屏 800X480分辨率频
驱动配置:使用rt1052自带lcd控制器,配置为16bit RGB565,lcd刷新率为60fps
软件配置:lvgl采用双buffer,lvgl的buffer开在SDRAM上,将lvgl的buffer直接作为LCD的framebuffer,由LCD控制器直接转换为RGB硬件接口信号。

现象

lvgl控件动画时会在水平方向出现类似于拖尾的噪声,动画速度越快噪声越明显

调试方向

以下为网友的建议和自己的一些调试方法,

  1. 没有清cache:这个在写驱动的时候已经明确清了cache,如果没有开cache,应该会比较大面积的出现噪声。
  2. 刷新率太高:降低刷新率问题任然存在。
  3. 数据对齐或者多写数据:framebuffer是lvgl添的,相信lvgl库,但同时也用SDL模拟验证没问题。另外静态显示图像没问题,因此排除该点。
  4. 没用双buffer: 无双buffer应该会看到比较明显的刷新过程,而不是局部噪声。
  5. 消隐等屏参配置不好:静态图像没问题,可以排除。
  6. malloc内存被其它覆盖:换成全局数组问题一样存在,可以排除该点。
  7. 连线太长,接地不好:静态图像没问题,可以排除
  8. 时钟极性配置反了:查看spec没有问题,修改极性也无改观。
  9. SDRAM频宽不够,LCD刷新读内存事务被其它内存读写事务中断:这一点曾经是最怀疑的,最后综合其它实验和SDRAM测试的速度排除了该点。

调试方法和过程

首先排除lvgl问题

排除lvgl的问题采用了两个手段,第一是在PC上模拟,没有发现有噪声。第二是直接使用驱动画移动的矩形观察是否噪声。最开始是画了一个50宽100高的矩形,从左到右跑了一遍没发现有噪声,调试陷入困境。后来突然抽筋将矩形的高低提高到400,在移动过程中可以发现矩形中有部分缺失或者闪烁,这就基本可以确认是屏幕的问题了。

调试

调试前先看有问题的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void mcux_elcdif_isr(void *arg)
{
swift_rgblcd_data_t *data = (swift_rgblcd_data_t *)arg;

u32_t status;

status = ELCDIF_GetInterruptStatus(data->elcdinfo.base);
ELCDIF_ClearInterruptStatus(data->elcdinfo.base, status);
if (s_framePending == 1)
{
if(status & kELCDIF_CurFrameDone){
s_framePending = 0;
// frame done时发送sem通知frame已经render完成
k_sem_give(&data->sem);
}
}

__DSB();
}

int swiftHal_RGBLCDUpdate(void *buf, u32_t size)
{

DCACHE_CleanByRange((uint32_t) buf, size);
//等待上一帧render完后再render当前帧
k_sem_take(&rgblcd_data.sem, K_FOREVER);
ELCDIF_SetNextBufferAddr(rgblcd_data.elcdinfo.base, buf);
s_framePending = 1;
return 0;

}

1. 加大移动矩形的间隔时间

也就是在调用swiftHal_RGBLCDUpdate后做延时,当延时加大到10ms时问题消失。此时怀疑是刷新率高了导致,因此将刷新率降到30,问题任然存在。

2. 驱动内增加一层专用framebuffer

代码如下,这也是zephyr的模式,我为了减少buffer用量,将这部分内存移除,直接使用lvgl的framebuffer做render

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int swiftHal_RGBLCDUpdate(void *buf, u32_t size)
{
char *pbuf = buf[widx];
widx = !widx;
memcpy(pbuf, buf, size);

DCACHE_CleanByRange((uint32_t) pbuf, size);
//等待上一帧render完后再render当前帧
k_sem_take(&rgblcd_data.sem, K_FOREVER);
ELCDIF_SetNextBufferAddr(rgblcd_data.elcdinfo.base, pbuf);
s_framePending = 1;
return 0;

}

增加专用buffer后问题消失,由于memcpy一屏数据大约会花11ms,所以怀疑是sem失效,不是添加sem监控未发现sem失效。于是只做memcpy, 还是将传入的buf作为lcd的framebuffer,虽然多花了memcpy的11ms,但还是会出问题。

3. 最终修改

代码如下,修改为该流程后,问题解决

1
2
3
4
5
6
7
int swiftHal_RGBLCDUpdate(void *buf, u32_t size)
{
ELCDIF_SetNextBufferAddr(rgblcd_data.elcdinfo.base, pbuf);
s_framePending = 1;
k_sem_take(&rgblcd_data.sem, K_FOREVER);
return 0;
}

一开始百思不得其解,在本次的update中等sem和在下一次开始等sem有什么不是一样吗,下面我们来分析。

分析

问题及原因

LVGL的送显示逻辑:LVGL维护A/B buffer时,当buffer B通过swiftHal_RGBLCDUpdate送显后函数返回,LVGL认为此时B已经开始显示,A buffer空闲,之后LVGL开始向A buffer写数据
分析前要搞清楚LCD驱动的工作原理,RT1052 LCD驱动中维护了两个硬件地址指针,LCDIF_CUR_BUF_ADDR和LCDIF_NEXT_BUF_ADDR,LCD控制器自动从LCDIF_CUR_BUF_ADDR指向的内存读出数据并送到RGB接口上,当一帧送完后LCD控制器会将LCDIF_NEXT_BUF_ADDR的值加载到LCDIF_CUR_BUF_ADDR内然开始新一轮的刷新。硬件每次刷新完一帧就会产生一个中断。症结点就再这里用ELCDIF_SetNextBufferAddr设置LCDIF_NEXT_BUF_ADDR后,此时LCD控制器刷新的还是上一片内存。我们以A,B两片buffer为例:

  1. 此时LCD控制器正在送显Buffer A的内容
  2. 我们准备好Buffer B的内容调用ELCDIF_SetNextBufferAddr告诉LCD要送显Buffer B, 但注意此时Buffer A还在被送显示。
  3. 但函数返回后,LVGL认为现在已经是在送显B,就对A开始写下一帧数据,此时A还在被送显,从而导致看到变化的部分出现噪声(相当于是单buffer了)。

调试现象解释

对于”加大移动矩形的间隔时间”的情况,其实就是通过等待延时,让LCD切到B buffer送显。
对于”驱动内增加一层专用framebuffer”,相当于是增加了C buffer,函数返回后修改的是A buffer,因此不会看见噪声

教训

出现该问题,是没有充分理解LVGL显示接口的语义,移植的显示接口语义和LVGL不一致导致问题:
LVGL显示接口的语义: 将显示切换到A buffer,上一次的buffer不再显示。
有问题的显示接口语义:确保上一次buffer已经切换完正在显示,通知驱动要切换A buffer。
因此在移除过程中一定要搞清楚porting API的语义。

参考

下面是在lvgl社区讨论的信息供参考
https://forum.lvgl.io/t/garbage-appears-in-the-horizontal-direction-of-the-widgets/2813/11