STM32 移植 LVGL -- 教程图解

移植效果,先睹为快:


目录

[一、LVGL 简述](#一、LVGL 简述)

二、准备一个STM32的工程

[三、LVGL 官方下载](#三、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、参数和功能的要求

上述参数和功能,是LVGL最基础的资源需要。

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

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

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

  • 编译:以确保 0 Error;
  • 烧录:以确保能正常运行,触摸和显示都正常(忽视下图中的文字显示,非必要)。

三、下载 LVGL

LVGL尽管已发布了v9.0、v9.1等,但v8.3版,应该是目前玩家们的至爱。

v8.3版本,网上教程众多、移植简单;

更重要的是:好几款主流的可视化设计工具,都支持v8.3版本!

因此,推荐使用v8.3版的LVGL。

官方下载链接: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";

完成后,是这个样子的:

3、删除不需要的文件夹

打开文件夹:LVGL / examples:

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

完成后,是这个样子的:

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

打开 porting 文件夹:

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

完成后,是这个样子的:

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

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


五、工程添加 LVGL 文件

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

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

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

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

完成后,是这个样子的:

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

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

|--------------------|-------------------------|
| 文件夹名称 (Groups) | 存放文件的种类 |
| LVGL_apps | 用户自己的界面代码文件、官方demo等 |
| LVGL_conf | LVGL 的两个h文件 |
| LVGL_porting | LVGL 的接口文件, 如显示、触摸屏、键盘等 |
| LVGL_src | LVGL 的所有底层c文件 |

提示:

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

3、给文件夹(Group),添加文件

这一步,最容易出错。

无聊、枯燥,估计耗时两分钟左右。

很多人在后面的编译中,出现error,提示缺少文件,基本是在这一步把某个文件添加漏了。

务必细心的操作!!

操作过程,步骤如下:

重要:每个文件夹(Group),需要添加的文件,如下表:

文件夹 (Group) 添加文件
LVGL_apps 不用添加
LVGL_conf LVGL 文件夹下的: lv_conf.h、lvgl.h,共2个文件 (要选择文件类型才能看到)
LVGL_porting LVGL/ examples / porting 文件夹下的:lv_port_disp.c 、lv_port_disp.h、 lv_port_indev.c、lv_port_indev.h;共4个文件。(要选择文件类型才能看到 h 文件)
LVGL_src 添加 LVGL / src 下的所有 c 文件; 重点:包括所有子、子子文件夹的 c 文件

关于 LVGL_src 的添加,特别地提醒,:

  • src文件夹下,会有多重的子文件夹,必须慢慢地、把每一个子文件夹的C文件全部添加进来;
  • 只须添加 c 文件,不用添加其它类型的文件,如:h、mk等;
  • 建议每添加完一个 Group, 停一停,放松一下,检查一下,再添加另一个Group。
  • 添加完毕后,必须点击"OK"保存, 不然,你会后悔。

完成后,Keil工程的资源管理器中,是这个样子的:

4、工程中,添加 LVGL 的头文件目录

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

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

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

5、编译验证

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

如果添加文件没有遗漏、添加头文件路径正确,那么,编译后应该是: 0 Error。

会有一大堆 Warning,不用管,不影响的。

如果,编译后,有 Error 报错:

  • 检查是否打勾: C99
  • 先检查头文件路径 ,是否添加完成;
  • 如果头文件路径添加正确,那么,基本是添加文件那一步有遗漏了,删了Group,重新添加。

六、注册 显示

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实际像素。小篇用2.8寸屏,不用修改。

注意,LVGL默认使用横屏的方式,这一点要注意,宽度、高度的值别写反了。

完成后,是这个样子的

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

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

第86行到101行,是创建显示缓冲区的3种方式。

LVGL提供了3种方式,需要3选1。绝大多数情况下,使用第1种方法:创建1个缓冲区;

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

完成后,是这个样子的:

6、给 LVGL一个画点函数

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

  • 第173行,disp_flush( )函数,添加 LCD 的画点函数; 参数:x坐标、y坐标、16位颜色值。

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

完成后,是这个样子的:

这里给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);

如果没有区域填充函数,不用强求,直接使用画点函数吧,先完成,再完善。

至此,显示部分的修改、注册,已完成。

点击保存,再做下一步操作。

提示:

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

为什么步骤明显少了?是不是漏了 disp_init()的那部分?

是的,我们没有为这个函数填入LCD的初始化函数。

没必要这样做。

在下面的第十部分,将会在main.c里直接初始化 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"

完成后,是这样子的:

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

还是在 "lv_port_indev.c" 中,

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

  • 函数内有5种输入方式的任务注册;
  • 保留触摸屏输入的任务注册;
  • 其它4种输入任务的注册,暂时不用,注释掉;

完成后,是这个样子的:

5、添加 触摸检测函数

还是在 "lv_port_indev.c" 中,

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

在函数内添加你的触摸屏状态检测函数;

  • 第212行,即函数内,添加触屏状态检测函数,函数返回必须是:0-未按下、1-按下
  • return XPT2046_IsPressed();

完成后,是这个样子的:

6、添加 坐标获取函数

还是在 "lv_port_indev.c" 中,

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

  • 第221行,即函数内,为坐标 x、y 提供赋值方法,使LVGL能够获取到触摸按下时的坐标;
  • (*x) = XPT2046_GetX();
  • (*y) = XPT2046_GetY();

完成后,是这个样子的:

7、额外的测试预埋

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

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

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

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

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

完成后,是这个样子的:

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

提示:

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

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

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

不需要这样操作!

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

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


八、LVGL 心跳、任务刷新

根据官方移植文档的要求,我们要处理两个关于时间的问题:

  • 间隔精准地,调用时基函数: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,并使能它的周期更新中断。

  • HAL_TIM_Base_Start_IT(&htim6);

完成后,是这个样子的:

然后,编写周期更新中断的回调函数中,在里面调用lv_tick_inc( ),给LVGL提供心跳;

完成后,是这个样子的:

回调函数解释:

这是一个TIM的周期更新中断回调函数,它是定义在***_hal_tim.c中的一个弱定义函数。

所以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产生5ms中断去调用它,因为它的执行时间有点长,不适合霸占中断资源。

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

完成后,是这样子的:

至此,时间需求也处理完毕。

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、显示、触屏

在开启TIM6的那行代码上面,进行LVGL的初始化:

  • lv_init(); // LVGL 初始化
  • lv_port_disp_init(); // 注册LVGL的显示任务
  • lv_port_indev_init(); // 注册LVGL的触屏检测任务

完成后,是这个样子的:

4、显示按钮控件、文本控件

在开启TIM6的那行代码下面,添加LVGL控件 ,开始测试LVGL的显示:

  • 添加一个按钮
  • 为按钮添加文本
  • 添加一个独立的标签文本

具体代码如下:

cpp 复制代码
    // 按钮
    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 占用 = Code + RO-data + RW-data = 163172 + 31808 + 592 = 190K
  • 程序 RAM 占用 = RW-data + ZI-data = 592 + 80504 = 80K

现在,你要回到所用芯片资源的焦点上了。

常用的几款STM32芯片资源:

芯片型号 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了。

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

直接烧录试试,最有效!

好了,现在开始烧录吧!

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

运行效果如下:

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

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

恭喜你,运气太TM的好了。

但是,一次就成功的机率太低太低了。

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

5、触摸没反应的排查

就如上面动图所显示的,点击按钮时,按下、释放,按钮的状态是不一样的。

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

  • 触摸检测:lv_port_indev.c 第209行:touchpad_is_pressed(),状态判断有问题;
  • 坐标获取:lv_port_indev.c 第217行:touchpad_get_xy();
  • 坐标不符:触摸屏坐标与显示屏坐标不符,需要重新校准;

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

那么,可以这样测试:在显示屏空白的地方,用指甲,慢慢地,划几道线。

  • 如果划不出黑线(正常是断断续续的黑线),那么就是触摸检测函数有问题了;
  • 如果划出了黑线,但是坐标不对,那就是触摸屏需要重新校准了;

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

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

关于第二种,重新校准

  • 不同的开发板,各施各法,问一问屏的商家,如何重新校准触摸屏;
  • 本篇使用的"魔女开发板"在这个工程中,使用串口助手发送:XPT2046, 即可进入重新校准。

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

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

这里,以按钮的点击处理为例,展示控件的:事件添加、响应处理。

回到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();

cpp 复制代码
// 按钮的事件回调函数
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
    }
}

完成后,是这个样子的:

编译,烧录,运行效果如下:


十 一、进阶

本部分,探讨一些轻松一点的、可能对你有用的玩法。

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

6、中文显示

LVGL源文件中,带了一个中文字库,但使用起来,会有些麻烦。

网上的各种方法和教程也很多,但实现起来,一点也不爽。

建议还是使用Gui Guider实现。

具体的方法,有点啰嗦,后面找时间另更一篇记录下来吧。

相关推荐
sukalot9 小时前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(二)
驱动开发
zwhSunday9 小时前
Linux驱动开发(1)概念、环境与代码框架
linux·运维·驱动开发
sukalot20 小时前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(三)
驱动开发
sukalot1 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(一)
驱动开发
cxr8282 天前
基于Claude Code的 规范驱动开发(SDD)指南
人工智能·hive·驱动开发·敏捷流程·智能体
zwhSunday3 天前
Linux驱动开发(2)进一步理解驱动
linux·驱动开发
被遗忘的旋律.3 天前
Linux驱动开发笔记(十)——中断
linux·驱动开发·笔记
路溪非溪3 天前
Linux驱动如何向应用层提供sysfs操作接口
linux·arm开发·驱动开发
sukalot4 天前
window显示驱动开发—监视筛选器驱动程序(三)
驱动开发
墨染天姬4 天前
【android 驱动开发九】生产者-消费者模型
android·驱动开发