【快速入门 LVGL】-- 1、STM32 工程移植 LVGL


目录

一、LVGL 简述

二、复制一个STM32工程

三、下载 LVGL

四、裁剪 源文件

五、工程添加 LVGL 文件 

六、注册 显示

七、注册 触摸屏

八、LVGL 心跳、任务刷新

九、开跑 LVGL 

十、控件的事件添加、响应处理

十 一、几个好玩小事情

十 二、显示中文


~~ 约定 ~~

在文中的各个操作阶段,会适当提示你:编译。

请在各阶段提示位,谨遵执行。这样操作,能清晰地定位移植过程中的问题所在。

只要操作没错漏,都能成功开跑LVGL。


一、LVGL 简述

  • 丰富且强大的模块化图形组件:按钮 、图表 、列表、滑动条、图片等
  • 高级的图形引擎:动画、抗锯齿、透明度、平滑滚动、图层混合等效果
  • 支持多种输入设备:触摸屏、 键盘、编码器、按键等
  • 不依赖特定的硬件平台
  • 配置可裁剪,最低资源占用:64 kB Flash,16 kB RAM
  • 基于UTF-8的多语种支持,例如中文、日文、韩文、阿拉伯文等
  • 可以通过类CSS的方式来设计、布局图形界面(例如:Flexbox、Grid)
  • 支持操作系统、外置内存、以及硬件加速(已内建支持STM32 DMA2D)
  • 即便仅有单缓冲区(frame buffer)的情况下,也可保证渲染如丝般顺滑
  • 支持模拟器仿真,可以无硬件依托进行开发


二、复制一个STM32工程

准备好一个STM32的工程,这个工程要求如下:

1、硬件的要求

  • 芯片资源:Flash>128K, RAM>64K; (LVGL至少占用: Flash>64K, RAM>16K); 
  • 与芯片型号无关,F1、F4、H7等系列的芯片,满足上述资源的都行;
  • 不建议使用常用的STM32F103C8,资源太小,裁剪难度大,强行移植了也会很卡。
  • 显示屏:建议使用16位色深的彩屏, 1.44寸、2.8寸、4.3寸等等;
  • 不建议使用常用的0.96寸OLED屏,指甲大小的单色屏,耗100K资源去撑它,没搞头。

2、软件的环境

  • 库支持方式:标准库、寄存器、手撸HAL库、CubeMX生成的HAL库、LL库,都可以;
  • 开发环境:Keil、CubeIDE,都可以; 

3、STM32工程的要求

  • 堆栈大小:Heap、Stack,设置为:0x1000;   ( 链接:如何设置堆栈大小 )  
  • 准备:画点函数,用于后面注册LVGL的显示功能;
  • 准备:触摸检测函数 (返回:0-未按下、1-按下)、坐标获取函数,用于注册LVGL的触屏功能;

上述,是LVGL最基础的资源需要。

如果有一项你没看懂,就先把它盘透,回头再盘LVGL。

本篇,复制了开发板的一个示例作移植的基础工程:"显示屏_2.8寸_触摸检测_XPT2046"。

4、复制源工程后,测试是否可用

  • 编译:以确保 0 Error; 
  • 烧录:以确保能正常运行,触摸和显示都正常(忽视下图中的文字显示,非必要)。
  • 对于这个工程,最低限度,你必须已懂得调用函数,实现如下图画线效果。


三、下载 LVGL

尽管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 
  • 在源文件夹中,把下图选中的 3个文件夹、2个h文件,  复制到新建的 LVGL文件夹中;

完成后,"LVGL" 文件夹,是这个样子的:

提醒:

  • 网上好些教程,在keil工程目录下新建 Middlewares 文件夹,在里面再新建LVGL文件夹。
  • 如果你使用的是标准库的工程,或者是自己手撸建立的HAL库工程,都可以那样操作。
  • 但是,如果使用CubeMX、CubeIDE生成的工程,就不要使用 “Middlewares” 作文件夹名称。
  • 因为 "Middlewares",刚好是CubeMX可能生成的文件夹,用来存放中间件,如:FreeRTOS、FatFS等支持文件。如果你没有使能这些中间件,那么 ,CubeMX重新生成工程时,"Middlewares"文件夹就会被认为不需要了,被删除掉。

 2、修改 lv_conf.h 文件名

在 "LVGL" 文件夹中,有 h文件:"lv_conf_template.h",是LVGL配置参数的重要文件。

  • 原文件名:“lv_conf_template.h”,修改为: "lv_conf.h";

完成后, "LVGL" 文件夹,是这个样子的:

3、删除不需要的文件夹

打开文件夹:"LVGL / examples":

  • 只保留 porting 文件夹,其它的文件夹和文件,都删除掉。

完成后,文件夹"LVGL / examples",是这个样子的:

4、修改 porting 里面的文件名称

打开刚才的  "porting"  文件夹:

  • 6个文件的名称,都删除 "_template" 字样

完成后,"porting"  文件夹,是这个样子的:

好了,现在 "LVGL" 文件夹,已经是我们需要的效果。

这个 "LVGL" 文件夹,以后可以复制给各类的工程使用,不限于STM32的工程,通用。


五、STM32工程添加 LVGL 文件 

现在,我们开始给STM32工程添加LVGL源文件。

1、复制 LVGL 文件夹,粘贴到STM32工程目录下。

每个人的工程文件夹,几乎都不一样,没关系的。

  • 把上一步做好的 LVGL 文件夹,复制到工程目录下

完成后,是这个样子的:

2、打开Keil,在工程里,添加4个文件夹(Groups);

文件夹名称 (Groups)用于存放什么文件
LVGL_myGui用户自己的界面代码文件、官方demo等
LVGL_confLVGL 的两个h文件
LVGL_portingLVGL 的接口文件, 如显示、触摸屏、键盘等
LVGL_srcLVGL 的所有底层c文件

操作过程、完成后,是这个样子的:

提示:

  • 操作完成后,先点击OK,保存操作。
  • 网上好些教程会新建近10个Group, 分开存放各个子功能文件。4个文件夹够了,简单直观。
  • 这里用下划线作名称分界线。尝试过使用“ / ”, 感觉没下划线直观。你可以用自己喜欢风格。

添加完后,再编译一次,确认: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 的添加,:

  • src文件夹下,会有多重的子文件夹,必须慢慢地、把每一个子文件夹的C文件全部添加进来;
  • 只须添加 c 文件,不用添加其它类型的文件,如:h、mk等 (技巧:文件类型配置为 *.c );
  • 添加完毕后,必须点击"OK"保存,  不然,你会后悔。

完成后,Keil的工程文件管理器,是这个样子的:

4、添加头文件路径

打勾C99,并,添加3个头文件路径:

  • 添加:LVGL 文件夹的路径
  • 添加:LVGL\src 文件夹的路径
  • 添加:LVGL\examples\porting 文件夹的路径 

操作过程、完成后,是这个样子的:

5、编译验证

来到这一步,需要用到的文件,已经添加完毕。

我们在这里必须先编译一次,以验证文件是否都添加完整。

正常情况下,编译后: 0 Error。会有一大堆 Warning,不用管,不影响的。

如果,编译后有 Error 报错:

  • 检查是否打勾: C99
  • 先检查头文件路径 ,是否添加完成;
  • 排除上述两个原因后,编译还有报错,那就是添加文件那一步有遗漏:删除4个Group里的文件,再次重新添加。


友情提示:

如果有Error未解决,必须要解决了,再继续操作下文 。

别心急!何况,下文没有你上面Error的解决方法。多点耐心就好。


六、注册 显示

1、启用 lv_conf.h

双击打开 lv_conf.h,对以下内容进行修改,以启用此文件。

  • 第15行,原:#if 0,修改为:#if 1 

完成后,是这个样子的:

2、启用 lv_port_disp.h

双击打开 lv_port_disp.h,修改以下内容,以启用此文件:

  • 第7行,原:#if 0, 修改为:#if 1 
  • 第22行,原:“lvgl/lvgl.h", 修改为:”lvgl.h"

完成后,是这个样子的:

3、启用  lv_port_disp.c 

双击打开 lv_port_disp.c,修改以下内容,以启用此文件:

  • 第7行,原:#if 0, 修改为:#if 1 
  • 第12行,原"lv_port_disp_template.h", 修改为:"lv_port_disp.h"

完成后,是这个样子的:

4、添加 LCD 驱动的头文件

在 lv_port_disp.c中:

  • 第14行,插入你的LCD驱动文件,如:#include "bsp_LCD_ILI341.h",写上你的h文件。
  • 第20行、第25行,是显示屏的宽、高度。查看你的显示屏参数,填写实际像素即可。

插入LCD的头文件,目的是为了让这个c文件,能调用LCD的: 画点函数;

注意一个:LVGL默认使用横屏的方式,这一点要注意,别写反了。

完成后,是这个样子的

5、选择创建缓存的方式,3选1

还是在 lv_port_disp.c 中,向下滚动,

(会出现很多错误提示,不用管。也可以先编译一次,让刚才启用的h文件生效,错误就会消失)

第86行到101行,LVGL 提供了创建显示缓冲区的3种方式,这里,必须3选1。

绝大多数情况下,使用第1种方法,即:只创建1个缓冲区;

  • 注释掉第90~101行,即:不使用第2和第3种方法;

完成后,是这个样子的:

6、关联 画点函数

还是在 lv_port_disp.c 中,向下滚动,找到disp_flush( )函数:

  • 第173行,替换你的 LCD 的画点函数;  参数:x坐标、y坐标、16位颜色值。
  • 小编用的画点函数:LCD_DrawPoint( x, y, color_p->full) ;

你的画点函数,可能和小篇所用的不一样,照样画瓢即可。

完成后,是这个样子的:

这里给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。

切记:如果有Error未解决,必须要解决了,再继续操作下文 。

提示:

细心的朋友,如果参考过其它LVGL教程,可能会有疑问。

为什么操作这么少?是不是漏了 disp_init()的那部分?

是的,我们没有为 disp_init()函数填入LCD的初始化函数。

没必要这样做。

在第九部分,将会在main.c的 main( ) 里直接调用LCD初始化函数。

这样更符合开发习惯,也使思路更清晰。


七、注册 触摸屏

1、启用 "lv_port_indev.h"

打开"lv_port_indev.h", 修改以下内容,以启动此文件:

  • 第8行,原:#if 0,  修改成:#if 1
  • 第20行,原:"lvgl / lvgl.h", 修改成:"lvgl.h"

完成后,是这个样子的:

2、启动 "lv_port_indev.c"

打开"lv_port_indev.c", 修改以下内容,以启动此文件:

  • 第 7行,原:#if 0, 修改为:#if 1
  • 第12行,原:“lv_port_indev_template.h", 修改为:"lv_port_indev.h"
  • 第13行,原:"../../lvgl.h",修改为:"lvgl.h"

完成后,是这个样子的:

3、添加  触屏 的驱动头文件

还是在 "lv_port_indev.c" 中:

  • 第14行,插入:#include "触摸屏的头文件",小编这边是:#include "bsp_XPT2046.h"

目的是为了让这个c文件,能调用触屏的:触摸状态检测函数、坐标获取函数;

完成后,是这样子的:

4、注释掉不需要的输入任务注册

还是在 "lv_port_indev.c" 中,

向下滚动至大约70行,找到输入注册函数:lv_port_indev_init( ),

函数内有5种输入方式的任务注册:触屏、鼠标、键盘、编码器、物理按键;

  • 保留触摸屏输入的任务注册;
  • 其它4种输入任务的注册,注释掉,;

完成后,是这个样子的:

5、添加 触摸检测函数

还是在 "lv_port_indev.c" 中,

向下滚动到大约209行,找到触摸检测函数:touchpad_is_pressed(),

这个是:触屏状态检测函数,函数返回:0-未按下、1-按下;

  • 第213行,原 return false, 注释掉;
  • 第212行,原来是空行,插入:return  XPT2046_IsPressed();

这个是魔女开发板所提供的触屏检测函数,返回值已符合:0-未按下、1-按下;

你可以替换成你的方式,如原子哥的变量值方式。各施各法,只要符合函数要求,都行;

完成后,是这个样子的:

6、添加 坐标获取函数

还是在 "lv_port_indev.c" 中,

在刚才触摸检测函数的下方,大约217行,找到坐标获取函数:touchpad_get_xy();

本函数的作为:使LVGL能够获取到触摸按下时的x、y坐标;

  • 第221行, 修改为:(*x) = XPT2046_GetX();
  • 第222行, 修改为:(*y) = XPT2046_GetY();

同上,可以各施各法,用各种方法赋值。

完成后,是这个样子的:

7、额外的测试预埋

(这一步,是非必须的,可以选择跳过。)

在后续的按钮测试中,有可能发生触摸坐标与显示坐标不对应的情况。

我们在这里先预埋一个操作,当后面发生问题,不用傻傻的盲猜原因:

触摸发生时,画点!!

这样,当发生错误时,, 能更好地排查问题:

1:触摸时,画的点,与触摸位置不对应,那就是触摸校验参数不对,要重新校准;

2:触摸时,屏上任何位置,都没看到有点出现,那就应该是触摸没发生了;

具体的预埋操作:

首先,在 lv_port_indev.c 内的顶部:#include 屏显的头文件 如:

#include "bsp_LCD_ILI9341.h”    // 注意:这里要替换成你的LCD头文件 

然后,就在刚才的那个 touchpad_get_xy( ) 函数中,增加加一行画点操作:

  • 第222行下方,插入新行,画点操作:LCD_DrawPoint( *x, *y, BLACK);

完成后,是这个样子的:

至此,触摸屏的注册,已经完成。

点击编译:0 Error。

提示:

参考过其它教程的细心的朋友,这里又会发现,相比其它教程,这里又少了步骤!

在其它教程中,会把其它几种输入方式的相关获取函数,都注释掉。

即:大约第230行~408行,鼠标输入、键盘输入、编码器输入....,统统注释掉。

不需要这样操作!

你没有为那些输入方式进行任务注册,也不调用它们,它们就不起任何作用。

而且,编译器聪明着,编译时将自动忽略死代码(即使是Level 0,死代码也不会产生影响)。


八、添加 LVGL 的文件引用

之前的几个部分,已修改完成了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的代码,与我们平时写的STM32的代码,风格区别相当大?

特别是刚玩嵌入式的兄弟,很不习惯:函数名称长、参数多......。

没事,不用太在意。

多读,多写写,慢慢就会喜欢上它这种“编码思维”:一致性、模块化、可配置性、可维护性!

好了,至此,已经编写好让LVGL显示控件的代码,离开跑只差两步操作!

不急,先编译一下!

0 Error, 35 Warning !!

只要是 0 Error,就可以了!那些35个警告的,不用管它。

如果出现错误:

1:少量的错误,应该是某个操作错了,向上翻动编译信息,找到第1个 error, 按它的描述排查、对比刚才的操作。

2:数量众多的错误,如几十个!向上翻动编译信息,应该会看到一片相同的错误描述,如下图:

错误描述:Error:L640E:No space in execution regions with .ANY....

这种情况就是内存不足了,程序需要的内存大于芯片RAM。

具体解决:下面的裁剪方法,可以参考。

额外的裁剪探讨:程序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资源大小:

芯片型号FlashRam
STM32F103RC256 K48 K
STM32F103VE512 K 64 K
STM32F407VE512 K192 K
STM32H750VB128 K1056 K

如果你使用的是STM32F407VE,Flash和RAM都是妥妥的足够。

但是:

F103RC、F103VE,芯片硬件的RAM,远远达不到程序运行的需要。

H750VB, Flash也是远远的不够。

怎么办?

就两个路:要么对程序进行裁剪,要么直接更换资源更大的芯片!

1:做项目时,在不缺钱的情况下,优先考虑更换芯片。这样方便于后续扩充功能。

2:如果不更换芯片,就要对程序进行裁剪了,主要是减少内存的占用。

下面,是针对RAM和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了。

注意,这种情况,只是“通常”,而非“肯定”,具体情况,可以上百度八卦一下网友的验证。

我们无视编译统计的大小,只要无Error, 直接烧录! 只要能正常运行程序,基本是有额外的Flash了。


九、LVGL 心跳、任务刷新

按目前的移植进度,我们只差两个时间任务要处理:

  • 第1个时间任务:为LVGL提供准确的心跳。必须间隔精准地,调用时基函数:lv_tick_inc(),俗称心跳,让LVGL精准地知道时间的流逝; 

  • 第2个时间任务:间隔5ms左右,调用周期性任务函数: lv_timer_handler() ,它的作用是检查所有注册任务的时间戳,执行那些已经到期的任务,如:屏显更新、动画更新、触控、定时器事件等;

1、给LVGL一个心跳时基

(概念上,有点类似FreeRTOS的1ms时基。)

LVGL心跳函数(时基函数):lv_tick_inc(),每隔1ms调用一次;

这个函数对于图形界面的流畅运行比较重要,它令 LVGL 知道执行任务时流逝的时间。

如果 lv_tick_inc() 调用间隔不准确,可能会导致显示卡顿、任务处理不及时。

特别地,不建议使用滴答时钟SysTick产生这个时基,因为它常常需要被用于RTOS等。

建议使用TIM产生1ms中断,设置它的中断为高优先级,通过中断函数调用LVGL心跳时基。

可以使用各个TIM、各种方法,产生1ms中断,如寄存器操作、标准库、手撸HAL等等。

本篇,通过CubeMX配置TIM6,产生1ms中断。

( TIM6 1ms中断,操作 5-1:设置TIM6时基 )

  • 使能TIM6,打勾 Activated即可。
  • 设置分频值(PSC):F103是72-1,  F407是84-1。为什么要减1,因为执行时:寄存器值+1。
  • 设置周期值(ARR):1000-1。

通过设置这两个参数,可以把周期设置为1ms,具体的计算方法:

周期时长 = PSC/运行时钟*ARR = 84/84000000*1000 = 0.001秒

( TIM6 1ms中断,操作 5-2:设置TIM6的中断 )

设定好了周期,还要使能中断,令每一周期结束时产生一次中断。

  • 打勾TIM6的中断,并设置中断抢占级为:0,(默认也是0);

TIM6已设置好了。

(TIM6 1ms中断,操作 5-3:CubeMX 生成)

keil,先点击保存,不要嫌麻烦。如果之前最后的操作是编译,那么,它就已自动保存了。

现在点击CubeMX里的生成按钮,令刚才的配置更新到工程里面。

切换为keil,如果刚才没有关闭,这时它就弹窗提示代码有更新,点击“是”, 当前工程即可更新代码。

( TIM6 1ms中断,操作 5-4:开启TIM6 )

回到main.c中,调用HAL函数:启动TIM6,并使能它的周期更新中断。

  • HAL_TIM_Base_Start_IT(&htim6);

注意,这一行,建议插入在LVGL的初始化之后,如下图中位置 。

 完成后,是这个样子的:

( TIM6 1ms中断,操作 5-5:中断回调函数 )

编写周期更新中断的回调函数中,在里面调用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 一个准确的心跳时基。

编写回调函数后,编译!

0 Error, 35 Warning。

2、每隔5ms左右,调用任务刷新函数: lv_timer_handler()

简单地理解,每5ms让LVGL刷新(干活)一次。

让LVGL定期检查所有已注册任务的时间戳,执行那些已经到期的任务,如刷屏、检测触摸等;

官方的描述:大约5ms左右、在while循环中调用;

  • 在msin.c的while中,每隔5ms调用:lv_timer_handler()

特别地:切勿通过TIM中断调用它,因为它的执行时间有点长,避免霸占中断资源。

完成后,是这样子的:

HAL_Delay 的坑 

上面代码中,你或许会对 HAL_Delay(1-1)产生疑惑。

HAL_Delay 函数执行时,它会对传入的参数+1进行计时。

具体原因,请细刨 HAL 源代码的注释。

如果间隔周期小,如 5ms内的,建议对传入的参数-1,能更精准;

如果间隔周期大,如 几十ms、几百ms,不用-1,因为延时的执行误差也远大于1ms。

至此,两个时间任务的,也处理完毕!

LVGL的移植,已全部完成 !

点击编译:0 Erros !

开始烧录吧!

一百多K的程序,烧录时间有点长,大约耗时十来秒。

运行效果如下:

显示正常,显示部分已移植成功!

触摸正常,按下时按钮的状态生产了变化,触摸部分也移植成功!

(已移植成功的老乡,先别走!!建议耐心读完下半部分,干货!!)

5、触摸没反应的排查

一次就移植成功的机率太低太低了。

更大可能出现的情况是:显示正常,触摸没反应!

正常情况下:如上图gif 所显示的,点击按钮时,按下、释放,按钮的状态是不一样的。

如果按钮在按下时没有反应、不会产生状态变化 ,主要排查3项:

  1. 触摸检测:lv_port_indev.c 第209行:touchpad_is_pressed(),返回值是否相符;
  2. 坐标获取:lv_port_indev.c 第217行:touchpad_get_xy();
  3. 坐标不符:触摸屏坐标与显示屏坐标不符,需要重新校准;

如果已按上面(七-7)的步骤,在touchpad_get_xy()函数预埋了画点函数,如下图:

配合这个预埋,现在可以这样测试:在显示屏空白的地方,用指甲,慢慢地,划几道线。

  • 如果划不出黑线(断断续续的黑线),排查硬件初始化,和上面的1、2两项;
  • 如果划出了黑线,但是坐标不对,那就是触摸屏需要重新校准了,即上面的3;

注意:有时候,黑点黑线,是被画在了屏的边上,堆在一起了,要注意细看。

关于第一种错误,检查:触摸检测函数返回值,是否正确。

  • 用printf大法!把触摸返回值、坐标获取值,printf出来,在串口助手上观察;
  • 必须确认:触摸按下时,有返回值,而且返回值正确(0-未按下、1-按下),
  • 如果返回值对了,再确认坐标获取是否正常,查对printf出来的数据。
  • 最后,就是从本篇开头,一步步对归照,是哪一步出现漏做了。

关于第二种,重新校准

  • 不同的开发板,问一问屏的商家,如何重新校准触摸屏;
  • 本篇使用的“魔女开发板”在这个工程中,使用串口助手发送:XPT2046,  即可进入重新校准。进入了校准界面后,用笔尖,点击依次出现的四个十字线,即可完成重新校准。

十、控件的事件添加、响应处理

当上述问题都解决了,按钮能正常触控后,再操作这一步部分。

LVGL的学习,可以大概地为分两部分:界面绘制、事件处理。

1、界面的绘制

我们这里不啰嗦,上面的示范代码,你能看个明白即可,无需深挖。

因为LVGL的界面绘制,更常规的操作:使用可视化工具进行设计,再把界面工程移植回STM32。

2、事件处理

可视化工具,能帮我们处理好:控件生成、布局、屏幕切换等;

但是,不能处理STM32上的事,如按下按钮,发送某CAN通信等;

这些,都是需要回到Keil或者CubeIDE里,自行增加编写的。

所以,特意增加一章,示范:事件的添加、编写响应处理函数。

明白了响应和处理的操作,后面使用可视化工具时,能有更好的理解。

下面,以按钮的点击为例,示范:事件添加、响应处理。

回到main函数,在添加按钮的那三行代码下方,增加一行,为控件添加事件:

  • lv_obj_add_event_cb(myBtn, myBtn_event, LV_EVENT_CLICKED, NULL);     

这行有点复杂,对参数稍作解释:

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帧数

  • lv_conf.h中第282行,找到:LV_USE_PERF_MONITOR,原值:0, 修改为:1

2、在左下角,显示LVGL的内存使用率

  • lv_conf.h中第289行,找到:LV_USE_MEM_MONITOR,原值:0, 修改为:1

3、黑色主题

  • lv_conf.h中第579行:找到:LV_THEME_DEFAULT_DARK, 原值:0, 修改为:1

4、获取控件的各种玩法

LVGL的控件玩法,最好的网站,没有之一:

LVGL 中文开发手册 -- https://lvgl.100ask.net/master/index.html

有啥控件,控件的实现效果如何,都可以在这里找到答案。

如,想实现一个下拉列表:

打开上面网址, 找到 EXamples / Widgets / Dropdown(下拉列表)。

点击后,右侧会展示各种下拉列表的效果(有点延时,要稍等),还可以通过鼠标点击它。

先找到我们想要的控件效果,

在效果的下方,点击 “Show C code",可以展开这个效果的代码,复制到工程中,即可测试。

 5、查询 LVGL 某个函数、某个变量的解释 

直接问AI,没有更快了,下面是Kimi的网址:

https://kimi.moonshot.cn


十 二、显示中文

如何显示中文呢?如果使用代码的方法,不外乎两个方法:

  • LVGL源文件中,自带了一个中文字库。
  • 网上有各种的字模转换方法、程序。

这两种方法,使用过程都很烦琐,都不建议使用。

小篇的建议是:通过Gui Guider实现。

Gui Guider除了能对界面进行可视化设计,还能轻松实现中文显示,根本无需理会转换的问题。

在我们移植的这个LVGL工程基础上,就能直接使用Gui Guider的移植。

Gui Guider 教程链接:LVGL_可视化设计 (Gui Guider)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从【人工智能】到【计算机视觉】,【深度学习】引领的未来科技创新与变革

前几天偶然发现了一个超棒的人工智能学习网站,内容通俗易懂,讲解风趣幽默,简直让人欲罢不能。忍不住分享给大家,点击这里立刻跳转,开启你的AI学习之旅吧! 前言 – 人工智能教程https://www.captainbed.cn/l…

linux文件——用户缓冲区——概念深度探索、IO模拟实现

前言:本篇文章主要讲解文件缓冲区。 讲解的方式是通过抛出问题, 然后通过分析问题, 将缓冲区的概念与原理一步一步地讲解。同时, 本节内容在最后一部分还会带友友们模拟实现一下c语言的printf, fprintf接口&#xff0c…

跨境电商补单秘籍:Lazada、Shopeee、eBay、Wish等平台实战技巧

在跨境电商领域,Lazada、Shopee、eBay、Wish及速卖通等平台为商家提供了广阔的市场空间。为了有效扩大产品的曝光率和提升转化率,商家需充分利用平台活动及营销工具。平台活动不仅是获取流量的关键渠道,还能显著提升品牌知名度。此外&#xf…

Python画笔案例-006 绘制正多边形

1、绘制正多边形 通过 python 的turtle 库绘制一个正多边形的图案,如下图: 2、实现代码 绘制一个正多边形,关键两个因素,一个是边长,决定了图形的大小;另一个就是图形里每个角的角度,绘制多边形…

SSM健康生活博客小程序—计算机毕业设计源码23497

摘 要 本文设计了一种基于SSM框架的健康生活博客小程序,为人们提供了运动视频教学、博客信息分享,用户能够方便快捷地查看资讯、搜索健康方面的相关信息、还能发布个人生活博客等。健康生活博客小程序采取面对对象的开发模式进行软件的开发和硬体的架设&…

Python | Leetcode Python题解之第365题水壶问题

题目&#xff1a; 题解&#xff1a; class Solution:def canMeasureWater(self, x: int, y: int, z: int) -> bool:if x y < z:return Falseif x 0 or y 0:return z 0 or x y zreturn z % math.gcd(x, y) 0

opencv-python图像增强十一:图像强光逆光调整:

文章目录 一&#xff0c;简介&#xff1a;二&#xff0c;方案简述&#xff1a;三&#xff0c;算法实现步骤&#xff1a;3.1 获得图像的阴影区域&#xff1a;3.2 调整阴影区域的亮度和对比度 四&#xff1a;整体代码五&#xff0c;效果&#xff1a; 一&#xff0c;简介&#xff…

UE5 多个类选择界面生成。解决方案思路。

中控器CC 》用户界面控制器UI_CC 》用户界面UI_Inst 生成 CC使用接口&#xff0c;通知UI_CC开始生成UI_Inst。 蓝图函数库编写判断是否存在和创建UI的蓝图。&#xff08;此处略&#xff09; UI_CC生成时&#xff0c;userwidget使用接口&#xff0c;注册UI_CC的用户控件的控件…

系统编程-信号

6 信号与管道 1 目录 6 信号与管道 1 信号 信号的概念 信号的使用 信号的发送 通过函数来实现信号的发送 信号改造函数(重点) 给自己发送信号函数 定时闹钟函数 暂停进程的函数 例题&#xff1a; 代码一&#xff1a; 代码二&#xff1a; 代码分析 -- linux系统下…

获发明专利加持,隆道加速推进企业级AI应用落地

近期&#xff0c;北京隆道网络科技有限公司研发的“基于供应链管理的AI采购业务分析装置及方法”获得国家发明专利授权。该项新专利的取得&#xff0c;证明了隆道在AI产业化应用中的技术前瞻性和创新性&#xff0c;也为隆道加速企业级AI应用落地提供了知识产权保障。 根据IBM发…

将标准输入stdin转换成命令行参数——Unix中的xargs指令

xargs是Unix中的复合指令加工机&#xff0c;联合管道符“|”将制造更加强大的“复杂”指令组合。 (笔记模板由python脚本于2024年08月22日 18:13:51创建&#xff0c;本篇笔记适合喜欢Linux的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.…

开放式耳机哪个品牌好?分享四款开放式蓝牙耳机排行榜前十名

我相信很多人都会有这些问题&#xff0c;不知道入手什么蓝牙耳机品牌、有线耳机不好收纳、有线耳机不方便携带、蓝牙耳机听歌的音质怎么样、蓝牙耳机是否会对大脑有危害、蓝牙耳机有什么品牌型号推荐以及想要不同价位的蓝牙耳机品牌推荐参考&#xff0c;okok问题也是很多&#…

GPU池化技术在油气勘探开发中的应用

01 背景介绍 国内某研究院为实现石油勘探开发专业软件资源的统一管理、统一监控、统一共享和统一计量&#xff0c;自主研发了勘探云管理平台(EPCP)和科研工作业务协同平台。该研究院通过两个平台实现了数十种专业勘探开发软件的共享&#xff0c;种类包括地震资料处理和解释&am…

汽车冷却液温度传感器

1、冷却液温度传感器的功能 发动机冷却液温度传感器&#xff0c;也称为ECT&#xff0c;是帮助保护发动机&#xff0c;提高发动机工作效率以及帮助发动机稳定运行的非常重要的传感器之一。 发动机冷却液温度 &#xff08;ECT&#xff09; 传感器用于测量发动机的冷却液温度&…

【Lecture1】清华大学大模型公开课——大模型绪论

#清华大模型公开课第二季 #OpenBMB 目录 1. The Evolution of Artificial Intelligence --History 人工智能的演变--历史 1.1 Definition of AI --定义 1.2 Conceptualization of AI -- 概念 1.3 Birth of AI as a Discipline 1.4 Development of AI 1.4.1 Symbolic Int…

有哪些好用的AI工具?这些AI工具让工作学习更高效!

大家好&#xff01;今天我要给大家介绍一些非常酷的AI工具&#xff0c;它们可以帮助你在不同领域发挥创意和提高效率。让我们一起来探索吧&#xff01; 我们有AI绘画工具。想象一下&#xff0c;你只需要描述一下你想要的画面&#xff0c;AI就能帮你生成各种风格的艺术作品。无论…

Prometheus 服务发现

目录 1.基于文件的服务发现 2.基于 Consul 的服务发现 3.基于 Kubernetes API 的服务发现 1.基于文件的服务发现 基于文件的服务发现是仅仅略优于静态配置的服务发现方式&#xff0c;它不依赖于任何平台或第三方服务&#xff0c;因而也是最为简单和通用的实现方式。 Prometh…

8月21日笔记

Frp Frp(Fast e Reverse ) Proxy) 是一款简单&#xff0c;好用&#xff0c;稳定的隧道工具。Frp 使用 Go语言开发&#xff0c;支持跨平台&#xff0c;仅需下载对应平台的二进制文件即可执行&#xff0c;没有额外依赖。它是一款高性能的反向代理应用&#xff0c;可以轻松地进行…

解决k8s分布式集群,子节点加入到主节点失败的问题

1.问题情况 Master主节点在 使用 kubeadm init 成功进行初始化后&#xff0c;如下所示 Your Kubernetes control-plane has initialized successfully!To start using your cluster, you need to run the following as a regular user:mkdir -p $HOME/.kubesudo cp -i /etc/k…

CMakeLists.txt模板

#设置编译该CMakeLists.txt文件所需要的最低cmake版本 CMAKE_MINIMUM_REQUIRED(VERSION 最低版本号)# 该项目名称 PROJECT(项目名称)SET(CMAKE_CXX_STANDARD 11)SET(CMAKE_C_STANDARD 11)# 此行可以理解为将路径下所有的文件装载到自定义集合filelist中 FILE(GLOB filelist &q…