赞
踩
目录
- 丰富且强大的模块化图形组件:按钮 、图表 、列表、滑动条、图片等
- 高级的图形引擎:动画、抗锯齿、透明度、平滑滚动、图层混合等效果
- 支持多种输入设备:触摸屏、 键盘、编码器、按键等
- 不依赖特定的硬件平台
- 配置可裁剪,最低资源占用:64 kB Flash,16 kB RAM
- 基于UTF-8的多语种支持,例如中文、日文、韩文、阿拉伯文等
- 可以通过类CSS的方式来设计、布局图形界面(例如:Flexbox、Grid)
- 支持操作系统、外置内存、以及硬件加速(已内建支持STM32 DMA2D)
- 即便仅有单缓冲区(frame buffer)的情况下,也可保证渲染如丝般顺滑
支持模拟器仿真,可以无硬件依托进行开发
准备好一个STM32的工程,这个工程要求如下:
1、硬件的要求
2、软件的环境
3、STM32工程的要求
上述,是LVGL最基础的资源需要。
如果有一项你没看懂,就先把它盘透,回头再盘LVGL。
本篇,复制了开发板的一个示例作移植的基础工程:"显示屏_2.8寸_触摸检测_XPT2046"。
4、复制源工程后,测试是否可用
尽管LVGL已发布了v9.0、v9.1等,但v8.3版,是目前最广泛使用的版本。
v8.3版本,网上教程资源众多、移植简单;
更重要的:多款主流可视化设计工具,都支持LVGL的v8.3版本!
因此,强烈推荐使用v8.3版本。
官方下载链接:https://github.com/lvgl/lvgl
1、选择版本
2、下载
3、下载后,解压缩得到文件夹:lvgl-release-v8.3
上一步得到的源文件夹: lvgl-release-v8.3。
里头文件众多:源代码、帮助文档、官方示例等等。
不用发晕,需要用到的,仅仅是:3个文件夹 + 2个h文件。
1、新建一个文件夹
因为LVGL源代码中的头文件,使用了相对路径,如在 "lvgl.h" 中:
为了令移植后的文件能直接使用这些相对路径,我们复制文件时,按下方目录结构来操作:
完成后,"LVGL" 文件夹,是这个样子的:
提醒:
2、修改 lv_conf.h 文件名
在 "LVGL" 文件夹中,有 h文件:"lv_conf_template.h",是LVGL配置参数的重要文件。
完成后, "LVGL" 文件夹,是这个样子的:
3、删除不需要的文件夹
打开文件夹:"LVGL / examples":
完成后,文件夹"LVGL / examples",是这个样子的:
4、修改 porting 里面的文件名称
打开刚才的 "porting" 文件夹:
完成后,"porting" 文件夹,是这个样子的:
好了,现在 "LVGL" 文件夹,已经是我们需要的效果。
这个 "LVGL" 文件夹,以后可以复制给各类的工程使用,不限于STM32的工程,通用。
现在,我们开始给STM32工程添加LVGL源文件。
1、复制 LVGL 文件夹,粘贴到STM32工程目录下。
每个人的工程文件夹,几乎都不一样,没关系的。
完成后,是这个样子的:
2、打开Keil,在工程里,添加4个文件夹(Groups);
文件夹名称 (Groups) | 用于存放什么文件 |
LVGL_myGui | 用户自己的界面代码文件、官方demo等 |
LVGL_conf | LVGL 的两个h文件 |
LVGL_porting | LVGL 的接口文件, 如显示、触摸屏、键盘等 |
LVGL_src | LVGL 的所有底层c文件 |
操作过程、完成后,是这个样子的:
提示:
添加完后,再编译一次,确认:0 Error, 不要觉得事多。
3、为每一个文件夹组(Groups),添加需要的文件
特别地:这一步,是整个移植里,最容易出错的步骤!务必在开始操作前,反复看两次本步图解。
很多人在后面的编译中,出现error,提示缺少文件,基本是在这一步把某个文件添加漏了。
提醒:一点也不难,但务必细心地操作。
操作过程,步骤如下:
重要:每个文件夹(Group),需要添加的文件,如下表:
文件夹 (Group) | 添加文件 |
---|---|
LVGL_myGUI | 不用添加。 |
LVGL_conf | 共2个文件:"LVGL"下的: lv_conf.h、lvgl.h(要选择文件类型才能看到h文件) |
LVGL_porting | 共4个文件:"LVGL/ examples / porting" 下的:lv_port_disp.c 、lv_port_disp.h、 lv_port_indev.c、lv_port_indev.h;(要选择文件类型才能看到 h 文件) |
LVGL_src | 近200+的c文件:"LVGL / src" 下的所有 c 文件; 重点:包括src里所有子、子子文件夹的 c 文件. 不用添加h和mk文件. |
在操作 LVGL_conf 和 LVGL_porting 添加h文件时,需要在选择窗口中,把文件类型设置 *.*:
细细解释一下 LVGL_src 的添加,:
完成后,Keil的工程文件管理器,是这个样子的:
4、添加头文件路径
打勾C99,并,添加3个头文件路径:
操作过程、完成后,是这个样子的:
5、编译验证
来到这一步,需要用到的文件,已经添加完毕。
我们在这里必须先编译一次,以验证文件是否都添加完整。
正常情况下,编译后: 0 Error。会有一大堆 Warning,不用管,不影响的。
如果,编译后有 Error 报错:
1、启用 lv_conf.h
双击打开 lv_conf.h,对以下内容进行修改,以启用此文件。
完成后,是这个样子的:
2、启用 lv_port_disp.h
双击打开 lv_port_disp.h,修改以下内容,以启用此文件:
完成后,是这个样子的:
3、启用 lv_port_disp.c
双击打开 lv_port_disp.c,修改以下内容,以启用此文件:
完成后,是这个样子的:
4、添加 LCD 驱动的头文件
在 lv_port_disp.c中:
插入LCD的头文件,目的是为了让这个c文件,能调用LCD的: 画点函数;
注意一个:LVGL默认使用横屏的方式,这一点要注意,别写反了。
完成后,是这个样子的
5、选择创建缓存的方式,3选1
还是在 lv_port_disp.c 中,向下滚动,
(会出现很多错误提示,不用管。也可以先编译一次,让刚才启用的h文件生效,错误就会消失)
第86行到101行,LVGL 提供了创建显示缓冲区的3种方式,这里,必须3选1。
绝大多数情况下,使用第1种方法,即:只创建1个缓冲区;
完成后,是这个样子的:
6、关联 画点函数
还是在 lv_port_disp.c 中,向下滚动,找到disp_flush( )函数:
你的画点函数,可能和小篇所用的不一样,照样画瓢即可。
完成后,是这个样子的:
这里给LVGL一个画点函数后, LVGL就能完成需要的显示操作了。
进阶技巧:提高刷屏效率
一般地,画点函数的底层操作:发送X坐标指令、X值、Y坐标指令、Y值、颜色值。
假如要刷320x240的整屏,至少传输14万次指令、14万次坐标值,7万次颜色值。
相当地耗时。
要是你的LCD驱动文件中,有区域填充颜色的函数,就能大量地减少指令、坐标值的发送次数。
下面是使用 魔女开发板 LCD驱动文件中所提供的 区域填充 函数,可以效仿参考。
- LCD_DispFlush(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_p);
如果没有区域填充函数,不用强求,直接使用画点函数吧,先完成,再完善。
至此,显示部分的修改、注册,已完成。
点击编译:0 Erros。
提示:
细心的朋友,如果参考过其它LVGL教程,可能会有疑问。
为什么操作这么少?是不是漏了 disp_init()的那部分?
是的,我们没有为 disp_init()函数填入LCD的初始化函数。
没必要这样做。
在第九部分,将会在main.c的 main( ) 里直接调用LCD初始化函数。
这样更符合开发习惯,也使思路更清晰。
1、启用 "lv_port_indev.h"
打开"lv_port_indev.h", 修改以下内容,以启动此文件:
完成后,是这个样子的:
2、启动 "lv_port_indev.c"
打开"lv_port_indev.c", 修改以下内容,以启动此文件:
完成后,是这个样子的:
3、添加 触屏 的驱动头文件
还是在 "lv_port_indev.c" 中:
目的是为了让这个c文件,能调用触屏的:触摸状态检测函数、坐标获取函数;
完成后,是这样子的:
4、注释掉不需要的输入任务注册
还是在 "lv_port_indev.c" 中,
向下滚动至大约70行,找到输入注册函数:lv_port_indev_init( ),
函数内有5种输入方式的任务注册:触屏、鼠标、键盘、编码器、物理按键;
完成后,是这个样子的:
5、添加 触摸检测函数
还是在 "lv_port_indev.c" 中,
向下滚动到大约209行,找到触摸检测函数:touchpad_is_pressed(),
这个是:触屏状态检测函数,函数返回:0-未按下、1-按下;
这个是魔女开发板所提供的触屏检测函数,返回值已符合:0-未按下、1-按下;
你可以替换成你的方式,如原子哥的变量值方式。各施各法,只要符合函数要求,都行;
完成后,是这个样子的:
6、添加 坐标获取函数
还是在 "lv_port_indev.c" 中,
在刚才触摸检测函数的下方,找到坐标获取函数:touchpad_get_xy();
本函数的作为:使LVGL能够获取到触摸按下时的x、y坐标;
同上,可以各施各法,用各种方法赋值。
完成后,是这个样子的:
7、额外的测试预埋
(这一步,是非必须的,可以选择跳过。)
在后续的按钮测试中,有可能发生触摸坐标与显示坐标不对应的情况。
我们在这里先预埋一个操作,当后面发生问题,不用傻傻的盲猜原因。
就在刚才的那个 touchpad_get_xy( ) 函数中,增加加一行画点操作:
- 第222行下方,插入新行,画点操作:LCD_DrawPoint( *x, *y, BLACK);
完成后,是这个样子的:
这样操作的目的,是令LVGL在获取坐标时,也在这个坐标上画一个黑点。
如果发生触摸坐标与显示坐标不对应,就能直观地发现问题。
至此,触摸屏的注册,已经完成。
点击编译:0 Error。
提示:
参考过其它教程的细心的朋友,这里又会发现,相比其它教程,这里又少了步骤!
在其它教程中,会把其它几种输入方式的相关获取函数,都注释掉。
即:大约第230行~408行,鼠标输入、键盘输入、编码器输入....,统统注释掉。
不需要这样操作!
你没有为那些输入方式进行任务注册,也不调用它们,它们就不起任何作用。
而且,编译器聪明着,编译时将自动忽略死代码(即使是Level 0,死代码也不会产生影响)。
之前的几个部分,已修改完成了LVGL显示、触摸支持。
现在,正式在工程中“应用” LVGL。
1、给工程,添加 LVGL 的头文件
打开 main.c,在顶部, #include 三个头文件(复制下面三行):
- #include "lvgl.h" // 它为整个LVGL提供了更完整的头文件引用
- #include "lv_port_disp.h" // LVGL的显示支持
- #include "lv_port_indev.h" // LVGL的触屏支持
完成后,是这个样子的:
2、初始化LCD、触摸屏
在main函数内、 while 循环之前,调用LCD初始化函数、触摸屏初始化函数。
删除原示例中多余的显示测试代码、触摸测试代码。
下图,是小篇所用开发板的LCD驱动函数:
- LCD_Init(); // 初始化 LCD
- LCD_SetDir(1); // 设置LCD的显示方向:横屏
- XPT2046_Init(xLCD.width, xLCD.height, xLCD.dir); // 初始化触摸屏
记得前几步时,我们没有像其它教程那样,给disp_init() 填入LCD的初始化函数。
现在,随着其它的设备,把初始化放在一起,更符合习惯、更直观。
完成后,是这个样子的:
提示:
在上图的第187行:W25Q128_Init();
它是外部Flash设备W25Q128的的初始化函数。
开发板的触摸屏校准数据,存储在它里面。每次上电,要从它里面读取之前的校准数据。
如果你用的不是魔女开发板,或者,有其它的储存渠道,不用对它初始化。
3、初始化LVGL、显示、触屏
在硬件的初始化代码之后,进行LVGL的初始化(复制下面3行):
- lv_init(); // LVGL 初始化
- lv_port_disp_init(); // 注册LVGL的显示任务
- lv_port_indev_init(); // 注册LVGL的触屏检测任务
完成后,是这个样子的:
4、显示按钮控件、文本控件
在LVGL的初始化之后,添加LVGL控件 ,以测试LVGL的显示:
具体代码如下:
- // 按钮
- lv_obj_t *myBtn = lv_btn_create(lv_scr_act()); // 创建按钮; 父对象:当前活动屏幕
- lv_obj_set_pos(myBtn, 10, 10); // 设置坐标
- lv_obj_set_size(myBtn, 120, 50); // 设置大小
-
- // 按钮上的文本
- lv_obj_t *label_btn = lv_label_create(myBtn); // 创建文本标签,父对象:上面的btn按钮
- lv_obj_align(label_btn, LV_ALIGN_CENTER, 0, 0); // 对齐于:父对象
- lv_label_set_text(label_btn, "Test"); // 设置标签的文本
-
- // 独立的标签
- lv_obj_t *myLabel = lv_label_create(lv_scr_act()); // 创建文本标签; 父对象:当前活动屏幕
- lv_label_set_text(myLabel, "Hello world!"); // 设置标签的文本
- lv_obj_align(myLabel, LV_ALIGN_CENTER, 0, 0); // 对齐于:父对象
- lv_obj_align_to(myBtn, myLabel, LV_ALIGN_OUT_TOP_MID, 0, -20); // 对齐于:某对象
完成后,是这个样子的:
好了,已经编写好让LVGL显示控件的代码,LVGL马上就要绽放了,还只差一步!
不急,先编译一下!
0 Error, 35 Warning。
没有 Error,可以哦!那35个警告,不用管它。
额外的裁剪探讨:程序Flash和RAM的资源占用
按上面的编译信息,我们这个程序:Flash占用190K, RAM占用80K
- FLASH 占用 = Code + RO-data + RW-data = 163172 + 31808 + 592 = 190K
- RAM 占用 = RW-data + ZI-data = 592 + 80504 = 80K
常用的几款STM32芯片的Flash和RAM资源大小:
芯片型号 Flash Ram STM32F103RC 256 K 48 K STM32F103VE 512 K 64 K STM32F407VE 512 K 192 K STM32H750VB 128 K 1056 K 如果你用的是STM32F407VE,Flash和RAM都是妥妥的足够。
而 F103RC、F103VE,RAM就远远不够。H750VB, Flash也是远远的不够。
怎么办?
第一:程序RAM > 硬件RAM,修改LVGL的内存池大小
- lv_conf.h中,第52行,LV_MEM_SIZE,LVGL管理的内存池大小,48U, 改为12U
修改后,重新编译,一般,程序RAM占用会降至40K内.
如果修改后,还是 >硬件RAM,再来:
- lv_port_disp.c中,第87、88行,显存大小(刷屏用),原10行,修改为2行到5行左右;
修改后,重新编译,一般,程序的RAM占用,会再减小几K;
如果,还是 >硬件RAM,细心检查一下程序中,是不是定义了全局有效的大数组。
第二:程序Flash占用 > 硬件Flash
常用芯片中,就F103C8、H750VB这两款,硬件Flash是比较小的。
不过呢,它俩通常都有“隐藏”的Flash。
即在芯片生产中,Flash不止这么小,但各种原因,参数上定为128K了。
注意,这种情况,只是“通常”,而非“肯定”,具体情况,可以上百度八卦一下网友的验证。
直接烧录试试,最有效!
根据官方移植文档的要求,我们还要处理两个关于时间的问题:
间隔精准地,调用时基函数:lv_tick_inc(),俗称心跳,让LVGL精准地知道时间的流逝;
间隔5ms左右,调用周期性任务函数: lv_timer_handler() ,它的作用是检查所有注册任务的时间戳,执行那些已经到期的任务,如:屏显更新、动画更新、触控、定时器事件等;
1、给LVGL一个心跳时基
LVGL心跳函数(时基函数):lv_tick_inc(),每隔1ms调用一次;
这个函数对于图形界面的流畅运行比较重要,它令 LVGL 知道执行任务时流逝的时间。
如果 lv_tick_inc() 调用间隔不准确,可能会导致显示卡顿、任务处理不及时。
特别地,不建议使用滴答时钟SysTick产生这个时基,因为它常常需要被用于RTOS等。
建议使用TIM产生1ms中断,设置它的中断为高优先级,通过中断函数调用LVGL心跳时基。
可以使用各个TIM、各种方法,产生1ms中断,如寄存器操作、标准库、手撸HAL等等。
本篇,通过CubeMX配置TIM6,产生1ms中断:
打勾TIM6的中断,并设置中断抢占级为:0,(默认也是0);
让CubeMX重新生成,令配置更新到工程代码后。
在main.c中调用HAL函数:启动TIM6,并使能它的周期更新中断。
注意,这一行,建议插入在LVGL的初始化之后。
完成后,是这个样子的:
然后,编写周期更新中断的回调函数中,在里面调用lv_tick_inc( ),给LVGL提供心跳;
本篇,在main.c的尾部,在/* USER CODE BEGIN 4 */ 的配对注释内,编写这个函数;
完成后,是这个样子的:
回调函数解释:
这是一个TIM的周期更新中断回调函数,它是定义在***_hal_tim.c中的一个弱定义函数。
CubeMX、CubeID生成的工程,TIM发生周期更新中断时,都会统一调用它。
我们需要在期待的位置,重写这个函数。本篇写在了main.c的尾部。
在回调函数中,我们调用:lv_tick_inc(1),参数为1,即让LVGL知道,1ms已经过去了。
如果你设置TIM产生的是2ms的中断,也可以:lv_tick_inc(2),效果是一样的。
另外 :
在这个中断回调函数中,我们额外地添加了LED每0.5ms闪烁的代码。
目的是为了调试时,可以肉眼判断定时器TIM是否按预期正常工作。
只有TIM6按预期正常工作了,才能给 LVGL 一个准确的心跳时基。
2、每隔5ms左右,调用任务刷新函数: lv_timer_handler()
这个函数的作用:让LVGL检查所有已注册任务的时间戳,执行那些已经到期的任务,如刷屏、检测触摸等;
官方描述:大约5ms左右、在while循环中调用;
特别地:切勿通过TIM中断调用它,因为它的执行时间有点长,避免霸占中断资源。
完成后,是这样子的:
至此,时间需求也处理完毕。
LVGL的移植,已全部完成 。
点击编译:0 Erros !
好了,现在开始烧录吧!
一百多K的程序,烧录时间有点长,大约耗时十来秒。
运行效果如下:
显示正常,显示部分已移植成功!
触摸正常,按下时按钮的状态生产了变化,触摸部分也移植成功!
恭喜你,运气太TM的好了。
但是,一次就成功的机率太低太低了。
更大可能出现的情况是:显示正常,触摸没反应!
5、触摸没反应的排查
正常情况下:如上图gif 所显示的,点击按钮时,按下、释放,按钮的状态是不一样的。
如果按钮在按下时没有反应、不会产生状态变化 ,主要排查3项:
如果已按上面(七-7)的步骤,在touchpad_get_xy()函数预埋了画点函数,如下图:
那么,可以这样测试:在显示屏空白的地方,用指甲,慢慢地,划几道线。
关于第一种错误,检查:触摸检测函数返回值,是否正确。
关于第二种,重新校准
当上述问题都解决了,按钮能正常触控后,再操作这一步部分。
LVGL的学习,可以大概地为分两部分:界面绘制、事件处理。
1、界面的绘制
我们这里不啰嗦,上面的示范代码,你能看个明白即可,无需深挖。
因为LVGL的界面绘制,更常规的操作:使用可视化工具进行设计,再把界面工程移植回STM32。
2、事件处理
可视化工具,能帮我们处理好:控件生成、布局、屏幕切换等;
但是,不能处理STM32上的事,如按下按钮,发送某CAN通信等;
这些,都是需要回到Keil或者CubeIDE里,自行增加编写的。
所以,特意增加一章,示范:事件的添加、编写响应处理函数。
明白了响应和处理的操作,后面使用可视化工具时,能有更好的理解。
下面,以按钮的点击为例,示范:事件添加、响应处理。
回到main函数,在添加按钮的那三行代码下方,增加一行,为控件添加事件:
这行有点复杂,对参数稍作解释:
myBtn:控件的名称(不限于按钮);
myBtn_event:事件响应时,LVGL调用的处理函数 (等一会儿要手动编写这个函数);
LV_EVENT_CLICKED:点击事件; 不同的控件,有不同的事件类型;
NULL:传递给回调函数的可选用户数据,这里暂时不用;
完成后,是这个样子的:
然后,开始编写刚才说的那个事件回调函数。
在main函数的上方,注释BEGIN 0 与 END 0之间,编写回调函数:myBtn_event();
- // 按钮的事件回调函数
- static void myBtn_event(lv_event_t *event)
- {
- lv_obj_t *btn = lv_event_get_target(event); // 获得调用这个回调函数的对象
- if (event->code == LV_EVENT_CLICKED)
- {
- static uint8_t cnt = 0;
- cnt++;
- lv_obj_t *label = lv_obj_get_child(btn, NULL); // 获取第1个子对象(我们在设计时,已安排了它的第1个子对象是一个label对象)
- lv_label_set_text_fmt(label, "Button: %d", cnt); // 设置标签的文本,写法类似printf
- }
- }
对函数内的代码行,上面已有注释,比较简单,这里也就不啰嗦了。
完成后,是这个样子的:
编译,烧录,运行效果如下:
上面,LVGL的移植,已经完成的。
备份好这个工程吧,它能作为后续可视化设计移植的基础工程。
本部分,探讨一些轻松一点的、可能对你有用的玩法。
1、在右下角,显示CPU使用率、FPS帧数
2、在左下角,显示LVGL的内存使用率
3、黑色主题
4、获取控件的各种玩法
LVGL的控件玩法,最好的网站,没有之一:
LVGL 中文开发手册 -- https://lvgl.100ask.net/master/index.html
有啥控件,控件的实现效果如何,都可以在这里找到答案。
如,想实现一个下拉列表:
打开上面网址, 找到 EXamples / Widgets / Dropdown(下拉列表)。
点击后,右侧会展示各种下拉列表的效果(有点延时,要稍等),还可以通过鼠标点击它。
先找到我们想要的控件效果,
在效果的下方,点击 “Show C code",可以展开这个效果的代码,复制到工程中,即可测试。
5、查询 LVGL 某个函数、某个变量的解释
直接问AI,没有更快了,下面是Kimi的网址:
如何显示中文呢?如果使用代码的方法,不外乎两个方法:
这两种方法,使用过程都很烦琐,都不建议使用。
小篇的建议是:通过Gui Guider实现。
Gui Guider除了能对界面进行可视化设计,还能轻松实现中文显示,根本无需理会转换的问题。
在我们移植的这个LVGL工程基础上,就能直接使用Gui Guider的移植。
Gui Guider 教程链接:LVGL_可视化设计 (Gui Guider)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。