经过一番周折将LVGL移植到了STM32F407单片机上,底层驱动的LCD是st7789,移植时的条件和环境如下:
●LVGL用的是单缓冲,一次刷新10行;
●刷新函数用的是最原始的一个一个打点的方式;
●ST7789底层发送数据用的是软件spi;
在这些环境下,刷屏就是在拉窗帘,特别慢。
接下来,一步一步地进行优化。
软件SPI→硬件SPI
将硬件SPI改成软件SPI,其实效果并没有什么提升,甚至可以说毫无改进。
注意,这里SPI虽然只用发送,但是接收的代码也不能省略,要不然没效果,千万注意。
虽然硬件SPI相比软件SPI没什么改善,但是硬件SPI可以结合DMA使用。
硬件SPI+DMA
结合LCD屏幕的指针自增进行区域批量赋值,不再一个一个地打点了,而是一次性填充整个区域。
Lvgl的打点调用如下
刷屏函数如下
使用DMA之后,效果提升比较明显,页面切换几乎是瞬间完成,但是还是有闪烁的感觉。
这里有一些问题需要注意下。
不知道为什么,DMA操作8位时能用,操作16位时就不好用了,虽然也把spi和DMA都改成了操作16位,还是不行,所以暂时使用8位;
理论上来说SPI的位数和DMA的位数应该是独立的,一个是发送的字节单位,一个是搬运的字节单位;暂时没搞明白,就统一使用8位吧。
另外,spi的时钟不能太低
估计是太慢的话,spi发送的速度跟不上DMA搬运数据的速度,spi的数据会被冲掉;
再就是SPI的发送和DMA的发送是可以共存的。
ST7789的驱动里涉及到发送指令和发送数据,如果是一次性的数据发送,可以使用DMA,也可以使用SPI
使用spi发送
使用dma发送
考虑到使用DMA来刷新屏幕数据可以结合lvgl的缓冲,所以上面选用spi的发送方式,然后DMA专门用来刷数据;
前面我是独立于lvgl,在LCD的驱动里给DMA专门又开了个内存空间
但其实,可以直接使用lvgl开辟的显存空间,重复申请就浪费了加倍的空间了,也省去了数据复制的步骤了。
但这样又有个问题,那就是颜色像素是个16位的数组,我DMA操作的是8位的数据,前面说了,DMA操作16位的不知道为啥不好使,所以这里面肯定要有些转化,或者解决DMA不能搬运16位数据的原因。怎么办呢?
继续往下看。
SPI+DMA+LVGL双缓冲
参考:LVGL非全尺寸双显存—SPI+DMA(中断刷新) - 哔哩哔哩
配置lvgl为双缓冲
注意下面的别忘了同步修改。
然后将上面三个静态变量的声明放到外面去,因为这里是在函数内部,属于局部静态变量,后面需要全局使用,就会识别不到,因此要上升成全局变量。
在使用LVGL的双缓冲时,DMA初始化时的发送地址可以先设置为双缓存的第一个空间,此时,将LCD的初始化函数直接放在lv_port_disp_template.c中,并且传递进入第一个缓冲区的地址。
之前单独为DMA开辟的发送空间可以去掉。
接下来开始改造刷新函数
Lvgl双缓冲的切换是lvgl自动进行的,我们不用去处理指针切换和数据交替填充的操作,只需要在刷新函数里获取对应的颜色数据的地址,就是当前要发送的数据地址,是lvgl已经帮我们切换好了双缓冲的指针。
在disp_flush函数里我们直接填充数据,接口不变。
同时有一个考虑:DMA发送数据时,之前是强行死循环等待DMA发送完成
我们现在将其改成DMA传输完成中断的方式来进行,然后在DMA传输完成中断里通知lvgl进行下一次刷新,也就是将lv_disp_flush_ready(disp_drv);函数放到DMA中断里去,这样就又有了问题,那就是这个通知函数里的disp_drv是个局部变量,想要挪到外部,就得借助一个中间的全局变量,因此,再定义一个全局的变量,类型和disp_drv的类型一致
同时,考虑到DMA一次只能发送一个字节的数据,因此我们将color_p强行转换成8位数据类型的指针。
于是,刷新函数disp_flush如下:
接下来,就要继续改造打点函数了
我们先计算下,DMA一次最多能发送65535个数据,我配置的是8位的,也就是一次最多能发送65535个字节,一个像素2个字节,那么就是说一次最多能发送32767个像素,如果屏幕是320*240的,那么一次最多能发送102行,所以,设置双缓冲时,我们的缓冲大小不要超过102行,我们这里先将缓冲设置成20行,也就是每次刷新,可以直接使用DMA发完,不必让DMA也分次发送。
DMA使能函数调整
另外,别忘了配置DMA的传输完成中断
然后按照上面说过的思路在lv_port_disp_template.c文件中写上DMA的传输完成中断,并在里面放置通知函数。
在这一过程中,其实有个隐含的答案,那就是上面提过的,关于像素是16位的,但是DMA只能处理8位数据的问题。
其实,DMA所识别的起始地址,就是个32位的地址,不管目标数据本身是什么类型,只要把首地址传给DMA即可,DMA从首地址开始,按照DMA配置的位数来搬运数据,DMA并不关心这个地址原来本身是什么类型的数据。
所以,LVGL传过来的是地址,我们只要把地址转成uint32_t然后传递给DMA,DMA就会按照初始化时配置的位数来搬运,它才不管你原来的数据是多少位的。
至此,DMA+LVGL双缓冲已完成,编译下载运行看看效果。
运行后,发现颜色不大对,原来是红绿蓝,现在变成了青紫黄,这种情况,就想到了一个颜色配置项LV_COLOR_16_SWAP,把它改成1。
再编译下载运行
颜色正常,速度也还挺好。
当然,如果想要再快,可以使用全屏幕双缓冲,此时,SRAM空间不够的话就可以将其放在外部SRAM中,用空间来换时间。我这个项目到这里帧率为35,已经够用了,就不再优化了。后续有特别的需要再说吧。
更多优化参考
LVGL显示优化—基本优化 - 哔哩哔哩