STM32 移植 LVGL -- 教程图解

( 编辑状态中,已完成80%,估计清明假期后完成更新 )

移植效果,先睹为快:


目录

一、LVGL 简述

二、准备一个STM32的工程

三、LVGL 官方下载

四、裁剪 源文件

五、添加 源文件

六、注册 显示

七、注册 触摸输入

八、提供 LVGL 心跳、任务处理

九、开跑 LVGL 

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

十 一、进阶


一、LVGL 简述

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


二、复制一个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; 
  • 显示:画点函数;
  • 触摸:触摸状态检测函数 (返回:0-未按下、1-按下)、坐标获取函数;  
  • 1ms时基:可以用Systick、TIM6、TIM1等,只要能产生1ms的都行,建议TIM6。

上述四项功能,是玩LVGL最基本要求。

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

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

此示例已带上述前三项功能,最后那个1ms时基,在文中增加。

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_confLVGL 的两个h文件
LVGL_portingLVGL 的接口文件, 如显示、触摸屏、键盘等
LVGL_srcLVGL 的所有底层c文件

提示:

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

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

这一步,最容易出错。

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

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

务必细心的操作!!

操作过程,步骤如下:

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

文件夹 (Group)添加文件
LVGL_apps不用添加
LVGL_confLVGL 文件夹下的: lv_conf.h、lvgl.h,共2个文件 (要选择文件类型才能看到)
LVGL_portingLVGL/ 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的显示:

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

具体代码如下:

    // 按钮
    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。

没有错误。那35个警告,不用管它。

这里要探讨的重点是:Flash和RAM的资源占用!

  • 程序 FLASH 占用 = Code + RO-data + RW-data  = 163172 + 31808 + 592 = 190K
  • 程序 RAM 占用    =  RW-data + ZI-data = 592 + 80504 = 80K

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

常用的几款STM32芯片资源:

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

很明显,程序需要80K 的RAM,F103RC、F103VE,不够哗。

如何办?

( 放完假 回来后再更新吧,顶不住了,太累人了)

好了,现在烧录程序到开发板!!

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

运行效果如下:

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

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

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

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

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

5、触摸没反应的排查

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

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

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

如果,已按上面步骤在touchpad_get_xy()函数预埋了画点函数:

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

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

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

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

关于第二种,重新校准

  • 不同的开发板,各施各法,问一问商家,如何重新校准触摸屏
  • 如果和本篇同款的“魔女开发板”,打开串口助手,发送: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();

// 按钮的事件回调函数
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、显示内存使用率

2、显示刷屏帧数

3、查询 LVGL 某个函数、某个变量的作用

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

https://kimi.moonshot.cn

4、获取控件的各种用法

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

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

想实现某个功能,都可以在里面找到答案。如,想实现一个下拉列表:

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

点击,右侧会展示各种下拉列表的效果(有点延时,要稍等),通用鼠标点击操作它。

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

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

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

相关文章

【flutter封装图片/视频选择控件】

引入库 wechat_assets_picker: ^6.0.5 、video_player: ^2.5.1 # 视频播放、 flutter_screenutil: ^5.7.0 import dart:async; import dart:io; import package:generated/l10n.dart; import package:jade/configs/PathConfig.dart; import package:jade/customWidget/addImag…

The Sandbox 的伙伴们| K-verse 3: Eternal Fandom 的合作伙伴介绍

准备好参加韩国合作伙伴最盛大的聚会吧,就在The Sandbox!这是一个前所未有的与你最喜爱的品牌建立联系的绝佳机会。 Otherworld Web 3.0 社交 "Otherworld "的首个数字空间 国内领先网络动漫和娱乐 IP 汇聚并扩展的元宇宙 Otherworld 正在为 …

高级DBA带你解决Mysql主从集群主库产生过多binlog文件引起生产服务器硬盘爆满处理方法实战全网唯一

高级DBA带你解决Mysql主从集群产生过多binlog文件引起生产服务器硬盘爆满处理方法实战全网唯一 一、事故描述 生产环境数据库服务器突然硬盘爆满报警,业务停止,监控短信过来了,一看硬盘满了,再看数据库文件路径一大堆binlog文件…

Redis 主从复制,哨兵模式,集群

目录 主从复制 主从复制 作用 缺陷 主从复制流程 实现Redis主从复制 哨兵模式 主从复制切换的缺点 哨兵的核心功能 哨兵模式原理 哨兵模式的作用 哨兵结构组成 故障转移机制 主节点的选举 实现哨兵模式 集群(Cluster) redis群集有三种模式,主从复制…

Leetcode-894-所有可能的真二叉树-c++

题目详见https://leetcode.cn/problems/all-possible-full-binary-trees/ 主搞动态规划,因为这玩意儿我还不是很懂 关于节点个数为奇数偶数的证明请见官方题解方法一中的如下内容: 这里DP的一个主要思想是:对于任何一个满二叉树&#xff…

算法学习——LeetCode力扣动态规划篇9(1035. 不相交的线、53. 最大子数组和、392. 判断子序列、115. 不同的子序列)

算法学习——LeetCode力扣动态规划篇9 1035. 不相交的线 1035. 不相交的线 - 力扣(LeetCode) 描述 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#x…

网站可扩展架构设计——中台

从公众号转载,关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、中台简介 1.传统项目架构的痛点 (1)重复造轮子 各项目相对独立,许多项目在重复造轮子,让项目本身越来越臃肿&#xf…

外卖配送时间预测项目

注意:本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 ([www.aideeplearning.cn]) 项目背景 外卖服务的兴起: 随着互联网技术和移动应用的发展,外卖成为一种日益普及的餐饮服务方式。顾客通过餐厅、杂货店的网站或移…

OpenHarmony Neptune开发板-MQTT连接华为IoT平台

本示例将演示如何在Neptune开发板上使用MQTT协议连接华为IoT平台,使用的是ATH20温湿度传感器模块与Neptune开发板 本示例实现AHT20温湿度数据上报华为IoT平台,IoT平台下发命令控制LED灯的开关 使用W800 SDK功能包中libemqtt来实现连接华为IoT平台 程序设计 初始化 一、MQT…

Stable Diffusion 模型下载:CyberRealistic(真实)

本文收录于《AI绘画从入门到精通》专栏,订阅后可阅读专栏内所有文章,专栏总目录•点这里 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 这是经过严格测试过程的结果,该过程混合了各种模型…

存储故障处理流程演变

存储作为存放金融企业数据中心各类生产数据的重要载体,其日常的安全平稳运行至关重要。特别是应对若干存储的大量告警,如何从大量告警中提取关键告警消息并及时处理异常,可谓对存储平台的稳定运行起到保驾护航的作用。 存储告警处理作为常规…

如何监控特权帐户,保护敏感数据

IT基础设施的增长导致员工可以访问的凭据和资源数量急剧增加。每个组织都存储关键信息,这些信息构成了做出关键业务决策的基石。与特权用户共享这些数据可以授予他们访问普通员工没有的凭据的权限。如果特权帐户凭证落入不法分子之手,它们可能被滥用&…

2024最新AI创作系统ChatGPT源码+Ai绘画网站源码,GPTs应用、AI换脸、插件系统、GPT文档分析、GPT语音对话一站式解决方案

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统,支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图文教程吧。已支持GPT…

Ai音乐大师演示(支持H5、小程序)独立部署源码

Ai音乐大师演示(支持H5、小程序)独立部署源码

Python网络爬虫(三):Selenium--以携程酒店为例

1 Selenium简介 Selenium是一个用于网站应用程序自动化的工具,它可以直接运行在浏览器中,就像真正的用户在操作一样。它相当于一个机器人,可以模拟人类在浏览器上的一些行为,比如输入文本、点击、回车等。Selenium支持多种浏览器&…

Linux结构目录详解

Linux 在Linux中,系统默认的用户是root,其实和 windows 的 administrator 类似,root 用户可以操作操作系统的任何文件和设备,所以在生产环境就不要乱用root了,权利越大,责任越大。 学习Linux,…

C++ 项目:使用 GSL 数学运算库 C++ 调用Python

文章目录 Part.I IntroductionChap.I CMakeListsChap.II ExportLibGSL.hChap.III test_python.cpp Part.II GSL 使用方法Part.III C 调用 Python 使用方法相关博客 Part.I Introduction 本文是一个项目的使用教程,此项目是一个使用 GSL 的小项目,还有 C…

Solana 线下活动回顾|多方创新实践,引领 Solana“文艺复兴”新浪潮

Solana 作为在过去一年里实现突破式飞跃的头部公链,究竟是如何与 Web3 行业共振,带来全新的技术发展与生态亮点的呢?在 3 月 24 日刚结束的「TinTin Destination Moon」活动现场,来自 Solana 生态的的专家大咖和 Web3 行业的资深人…

基于lora技术微调Gemma(2B)代码实践

一、前置条件 获得模型访问权,选择Colab运行时,配置训练环境。 先在Kaggle上注册,然后获得Gemma 2B 的访问权; 然后在Google colab 配置环境,主要是GPU的选择,免费的是T4,建议采用付费的A100…

【Linux】详解动静态库的制作和使用动静态库在系统中的配置步骤

一、库的作用 1、提高开发效率,让开发者所有的函数实现不用从零开始。 2、隐藏源代码。 库其实就是所有的.o文件用特定的方式进行打包形成一个文件,各个.o文件包含了源代码中的机器语言指令。 二、动态库和静态库的制作和使用 2.1、静态库的制作和使用…