LCD撕裂的产生原因和双缓冲的概念
大家好,好久没有发布视频了,原本最近打算开一个新的系列,关于一些嵌入式相关经验分享,其实也不算新的系列了吧,因为我很早以前就尝试过发布一些相关的视频,但是质量不是很高。这次因为平常工作繁忙的原因,再三思考后还是打算以专栏的形式发布这个系列,有空时写写。 由于我接触最多的设备就是LCD了,因此打算以LCD的驱动为切入点开始分享,关于LCD的驱动大概会分为4期,分别是,
《LCD撕裂的产生原因和双缓冲的概念》
、
《MCU如何高效地驱动LCD——RGB篇》
、
《MCU如何高效地驱动LCD——8080篇》
、
《MCU如何高效地驱动LCD——SPI篇》
。
LCD的刷新过程
首先来说一下LCD的刷新过程。为了便于理解,稍微简化一下模型,把LCD看成是非常多个RGB LED的阵列,每个LED要亮什么颜色,都存储在显存GRAM里,LCD控制器需要不停地将GRAM里的颜色数据刷新到LCD面板上,为什么LCD需要刷新,相信玩过数码管和点阵的朋友们不难理解,静态驱动和动态驱动相比,动态驱动是可以节省很多IO的,何况是像素点这么多的LCD(节省寄存器),因此LCD每次只会驱动一行显示,然后快速刷新才达到整个面板显示的效果。在LCD刷新时就涉及到LCD控制器读GRAM,即下文要说的读指针。而我们要更改LCD上显示的内容,就需要修改GRAM数据,这里涉及到主控写GRAM,即下文要说的写指针。同时,LCD还存在无效数据区域和有效数据区域。无效数据区域是我们在面板上看不到的,它不占用显存,是为了对每一行进行消隐和缓冲的,下文称为非可视区,而有效数据区域就是实实在在的占用显存,是我们在LCD面板上可以看到的,下文称为可视区。
LCD的撕裂
了解了LCD的刷新过程后再来说一下LCD的撕裂,什么是撕裂,顾名思义就是在LCD画面产生变化时,如滑动页面时,LCD上会出现图像错位的情况,错位线的上面是一帧,下面又是另外一帧。本质原因就是GRAM的读写指针在可视画面中重合了,于是在刷那一帧时屏幕中就会出现一个错位的现象,而这一帧持续的时间足以让人眼察觉到。当然如果画面不动是看不到撕裂的,因为画面不动时,每一帧都是一样的,肯定看不出错位。
LCD未撕裂的情况
LCD正常没有撕裂现象的情况时,读写指针只会在非可视区重合,这分为3种情况,但这3种情况均有一个大前提,即LCD使用了读写指针同步,即我们平常所了解的垂直同步,这里借用一下网络上的动图解释。
第一种:读指针速率等于2倍的写指针速率
。当一帧同步后,读写指针同时在GRAM首地址出发。在画面从A帧切换为B帧这个过程中,读指针一共读取了2次GRAM,第一次在写指针前面读取,整个过程读取到的全部是原本A帧的数据;第二次读指针从头开始去追 写指针,在写指针后面读取新的B帧数据,整个过程读取到的全部是写指针已写入的B帧数据,当写诗指针写完后,读指针也刚好读取刷新完毕,画面上不存在有任何一帧错位的情况。
第二种:写指针数率等于2倍的读指针速率
。当一帧同步后,读写指针同时在GRAM首地址出发。 在写指针绘制A帧时,读指针刚好跟在屁股后面读A帧数据;而当写指针开始绘制B帧时,读指针刚好读到GRAM的一半,根据追及问题,两者肯定在最后一行相遇,整个过程也不会出现错位的现象。但这种情况要做读写指针同步非常麻烦,更多的情况减小写指针速率与读指针速率相同,然后进行同步。
第三种:双缓冲
。这种情况其实可以看成第二种的变种。注意我这里说的双缓冲是指LCD控制器层面上的双缓冲,而不是仅仅是UI层面使用2个缓冲区,下文会说一下这两者的区别。对于双缓冲而言,就是在读写指针同步之后(常见在非可视区同步),设置新的GRAM缓冲区给读指针刷新,而另一个GRAM缓冲区则是交给主控去更新新内容。为什么说这可以看成是第二种情况的变种呢,其实就相当与读写指针同步后,写指针一瞬间就写完了GRAM,因为对与读指针来说,GRAM相比上一帧的数据,是一瞬间就变了的。这种用法通常在主控自带LCD控制器的情况下见到,比如STM32的LTDC,具体的思路在后面《MCU如何高效地驱动LCD——RGB篇》文章中会详细说明。
LCD撕裂的情况
说完了LCD正常没有撕裂的情况,下面说说会产生撕裂的情况和原因。
第一种:写指针速率太慢,小于读指针的二分之一
,对应与上文无撕裂的第一种情况。这种情况非常好理解,主控写一帧的速率太慢,人家LCD都刷了好几帧了,你一帧还没写完,肯定会出现错位现象。举个极端一点的例子,在使用lvgl时,如果使用的缓冲区比较小,比如LCD面板的十分之一,那么这时候滑动页面时就会看到明显的“拉窗帘”现象,即出现了好几条错位线,疯狂撕裂,就是因为由于缓冲区过小,lvgl不得不将一帧拆分为好几次渲染,因为中间一些重复操作导致整体写速率变慢。
第二种:没有做读写指针同步导致写指针太快读指针跟不上
。最经典最常见的例子就是有些游戏在没有开启垂直同步时,因为帧率太高,显示器刷新率跟不上导致的画面撕裂。人家读指针一帧都没刷玩呢,写指针下一帧都写完了。
第三种:没做读写指针同步导致导致读写指针在可视画面中相撞
。这里区分与上面第二种,上面第二种是写指针太快没做同步导致的,而这一种是即使读写指针满足2倍关系,也会导致撕裂,还记得上文正常未撕裂情况的大前提吗,读写指针同步,这里就说说为什么要同步。处理撕裂不可缺少的就是读写指针同步,一旦少了这个,则上面正常未撕裂的情况也必然会导致撕裂。 举个开发中常见的例子,我们都知道STM32的中高端MCU中有一个叫做DMA2D的外设,它可以用于将处理好的数据快速地拷贝到显存中供LTDC刷新。但是直接使用DMA2D拷贝肯定是会产生撕裂的,尤其是拷贝的数据越多,产生撕裂的概率越大,换句话说就是刷新的画面分辨率越大,产生撕裂的概率越大。根本原因就是没有做读写指针同步,正确的做法应该是计算好DMA2D拷贝这块数据所需的时间,然后在合适的行设置中断,在中断中进行同步,当然最好不要在中断中进行DMA2D拷贝,毕竟中断讲究快进快出。具体的操作在后面《MCU如何高效地驱动LCD——RGB篇》文章中会详细说明。
双缓冲的概念
最后说一下,双缓冲的一个误区,很多人以为双缓冲可以避免撕裂,这是不完全对的。双缓冲分为2种,分别是LCD控制器层面的双缓冲和UI层面的双缓冲,下面说说两者的区别。
LCD控制器层面的双缓冲
对与LCD控制器层面的双缓冲,指的是LCD控制器具有切换显存GRAM的功能。比如STM32的LTDC外设有一个寄存器CFBAR,即帧缓冲区地址寄存器,我们只需要定义2个帧缓冲区,在需要的时候更改这个寄存器的值,就可以达到双缓冲的效果,当然不要忘了读写指针同步,即需要设置行中断在非可视区(消隐区)更改这个寄存器。不过LTDC具有影子寄存器,设置了并不会马上更新,而是会自动在消隐区更新,由于篇幅的原因,这些内容以及思路在后面的文章会详细说明。这一层面的双缓冲的作用是用来
解决画面撕裂的。
UI层面的双缓冲
而对于UI层面的双缓冲,指的是UI具有2个缓冲区可以渲染图像数据,比如lvgl的双缓冲。在lvgl使用单buffer(单缓冲)的情况下,lvgl渲染完这个buffer后,必须等待主控将整个buffer全部拷入LCD的GRAM后,才可以往这个buffer写入新的数据。其中就需要等待一段时间,而这一段等待时间就会导致UI渲染帧间隔变长,帧率变低。但如果采用双buffer(双缓冲),lvgl渲染完其中一个buffer后,该buffer被拷贝到LCD的GRAM的同时,lvgl还可以在另一个buffer中渲染下一帧数据,这样可以缩短帧间隔,提高UI渲染帧率(但通常需要多核多线程或者DMA的支持)。这么看来,UI层面的双缓冲的作用就很明显了,即
提高UI渲染帧率。
两者的主要区别在于作用不同,一个是为了解决画面撕裂,一个是为了提高UI渲染帧率。
可是这两者加起来不就需要4个缓冲区了吗,这样内存开销太大了。那能不能将2者结合在一起只用2个缓冲区呢,答案是可以的,但是是有前提条件的,对与不同驱动类型的LCD也各不相同,详细内容在这个系列后面的文章中会逐个说明。
小插曲
这里插一个小点,帧率率其实也分为LCD帧率和UI帧率,LCD的帧率是固定的,与硬件有关,而UI帧率是根据渲染的画面复杂度而动态变化的。我们往往更关注UI的帧率,但如果UI的帧率平均超过了LCD的帧率后,如果你也跟我一样追求极致的显示效果,那么此时应该将重点放在LCD的撕裂问题上,而不是一味地追求高帧率。换句话说,如果LCD的撕裂和UI帧率都保证了之后,应该降低UI帧率保证二者同步或接近同步,以
释放更多的CPU性能
来处理其他事务。 总结一下,文章讲解了LCD撕裂的本质原因、各种常见的情况以及两个层面的双缓冲的区别和作用。内容有点多,感谢各位能看到这里的伙伴们。如果觉得对你有帮助的话还望点点大拇指,给Up一份支持。如果喜欢这个系列的可以点个关注,后面还会发布新的文章。