如何一步一步地优化LVGL的丝滑度

经过一番周折将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显示优化—基本优化 - 哔哩哔哩

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/415031.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】学习和总结标量子查询

🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-kLo6jykc7AcEVEQk {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

JMeter--9.录制脚本

录制步骤 1.新建线程组:测试计划->线程->线程组 测试计划下,至少要有1个线程组,因为在录制器中需要选择【目标控制器】 2. 新建录制器:测试计划->非测试原件->HTTP(S)测试脚本记录器(HTTP代理服务器&…

Linux磁盘如何分区?

首先需要先给虚拟机添加磁盘 sblk #查看磁盘设备 得到以下内容: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 20G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 19G 0 pa…

毕业后的那两年,我是怎么从一个啥也不会的小白成长为成熟职场人的?

对于2023应届生而言,从毕业到踏入职场也许正是你人生中很大的一个变化,但在初入职场的期间,很多同学很容易因为一些经验问题而误入弯路。 笔者从一个职场萌新到如今的职场老人,一路走来也经历了不少社会毒打。在职场生涯中&#…

kubectl 命令行管理K8S(上)

目录 陈述式资源管理方式 介绍 命令 项目的生命周期 创建 kubectl create命令 发布 kubectl expose命令 更新 kubectl set 回滚 kubectl rollout 删除 kubectl delete 应用发布策略 金丝雀发布 陈述式资源管理方式 介绍 1.kubernetes 集群管理集群资源…

Nest.js权限管理系统开发(八)jwt登录

安装相关依赖 虽然仅使用nestjs/jwt就能实现身份验证的功能,但是使用passport能在更高层次上提供更多便利。Passport 拥有丰富的 strategies 生态系统,实现了各种身份验证机制。虽然概念简单,但你可以选择的 Passport 策略集非常丰富且种类繁…

kotlin与java的相互转换

Kotlin转java 将kotlin代码反编译成java Tools -> Kotlin -> Show Kotlin Bytecode 然后点击 【Decompile】 生成java代码 java转kotlin Code -> Convert Java File To Kotlin File

Netty入门指南:从零开始的异步网络通信

欢迎来到我的博客,代码的世界里,每一行都是一个故事 Netty入门指南:从零开始的异步网络通信 前言Netty简介由来:发展历程:异步、事件驱动的编程模型: 核心组件解析通信协议高性能特性异步编程范式性能优化与…

2055041-59-1,NH-(PEG4-acid)2,能将基因和蛋白质导入到细胞内

您好,欢迎来到新研之家 文章关键词:2055041-59-1,NH-bis(PEG4-acid) HCl salt,NH-(PEG4-acid)2,NH-bis(PEG4-acid),NH-BIS(四聚乙二醇-羧酸) 盐酸盐 一、基本信息 【产品简介】:NH bis (PEG4…

全新抖音视频下载软件|批量视频下载工具

随着抖音平台上精彩视频的不断涌现,许多用户希望能够方便地保存自己喜欢的视频内容,以便随时观看或分享给朋友。为了满足这一需求,我们基于C#开发了一款全新的视频下载软件,为您提供便捷、高效的视频获取体验。 主要功能模块&…

redis-Redis主从,哨兵和集群模式

一,Redis的主从复制 ​ 主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。这样做的好处是读写分离,性能扩展,容灾快速恢复。 1.1 环境搭建 如果你的redi…

Unity(第六部)向量的理解和算法

标量:只有大小的量。185 888 999 (类似坐标) 向量:既有大小,也有方向。(类似以个体为主体的方向,前方一百米) 向量的模:向量的大小。(类似以个体为主体的方向,前方一百米、只取一百米…

计算机设计大赛 深度学习实现语义分割算法系统 - 机器视觉

文章目录 1 前言2 概念介绍2.1 什么是图像语义分割 3 条件随机场的深度学习模型3\. 1 多尺度特征融合 4 语义分割开发过程4.1 建立4.2 下载CamVid数据集4.3 加载CamVid图像4.4 加载CamVid像素标签图像 5 PyTorch 实现语义分割5.1 数据集准备5.2 训练基准模型5.3 损失函数5.4 归…

软件测试笔记(三):黑盒测试

1 黑盒测试概述 黑盒测试也叫功能测试,通过测试来检测每个功能是否都能正常使用。在测试中,把程序看作是一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,对程序接口进行测试,只检查程序功能是否按…

nginx实现http反向代理及负载均衡

目录 一、代理概述 1、代理概念 1.1 正向代理(Forward Proxy) 1.2 反向代理(Reverse Proxy) 1.3 正向代理与反向代理的区别 2、同构代理与异构代理 2.1 同构代理 2.2 异构代理 2.3 同构代理与异构代理的区别 二、四层代…

【Web安全靶场】sqli-labs-master 21-37 Advanced-Injection

sqli-labs-master 21-37 Advanced-Injection 第一关到第二十关请见专栏 文章目录 sqli-labs-master 21-37 Advanced-Injection第二十一关-Cookie注入第二十二关-Cookie注入第二十三关-注释符过滤的报错注入第二十四关-二次注入第二十五关-过滤OR、AND双写绕过第二十五a关-过滤…

【SpringCloudAlibaba系列--OpenFeign组件】OpenFeign的配置、使用与测试以及OpenFeign的负载均衡

步骤一 准备两个服务,provider和consumer 本文使用kotlin语言 provider是服务的提供者,由provider连接数据库 RestController RequiredArgsConstructor RequestMapping("/provider/depart") class DepartController(private val departServ…

常用对象的遍历方法

var obj [{name: 1111,account: {01: { name: 1.1 },02: { name: 1.2 },03: { name: 1.3 },04: { name: 1.4 },05: { name: 1.5 },}} ]var nowObj obj[0].account;1、for…in 任意顺序遍历对象所有的可枚举属性(包括对象自身的和继承的可枚举属性,不含…

flutter 封装webview和使用本地网页

最先看到flutter_webview_plugin 用法特别简单 flutter_webview_plugin | Flutter PackagePlugin that allow Flutter to communicate with a native Webview.https://pub-web.flutter-io.cn/packages/flutter_webview_plugin缺点: 没有实现js sdk的功能 没有办法 …

力扣简单递归:左叶子之和

思路:重点在于每层都记录val的值以减少递归调用次数 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int sumOfLeftLeaves(struct TreeNode* root){ if(rootNULL) {re…