本文介绍如何在成功移植LVGL的基础之上,编写自己的LVGL GUI程序。
文章目录
- 1. LVGL组件简介与LVGL仿真
- 1.1 LVGL组件
- 1.2 LVGL仿真
- 2. 代码结构
- 3. 编程目标
- 4. 编程前的准备
- 5. LVGL编程基础
- 5.1 简单示例代码
- 5.2 设置组件位置
- 5.3 图片的显示
- 5.4 组件的事件响应
- 5.5 设置定时任务
- 6. 各个组件编程的实现
- 6.1 文本标签组件(lv_label)
- 6.2 开关组件(lv_switch)
- 6.3 图片按键组件(lv_btn)
- 6.4 设置定时任务(lv_timer)
- 6.5 显示其他组件
- 7. 代码测试
- 8. 工程源码
- 参考文档
1. LVGL组件简介与LVGL仿真
1.1 LVGL组件
与绝大多数GUI组件相似,LVGL的GUI组件主要包括Lable(标签)、Button(按键)、Image(图片)、Image Button(图片按钮)、Keyboard(键盘)、Calendar(日历表)、Chart(数据表)等。LVGL组件的官方文档如下:
Welcome to the documentation of LVGL! — LVGL documentation
LVGL编程采用了类似QT的面向对象的编程方法,但是使用的编程语言是C而不是C++,创建LVGL组件对象的方法如下:
lv_obj_t *my_obj = lv_XXX_create(lv_scr_act());
LVGL中所有的组件对象都是由lv_obj_t来定义的,并通过对应的组件创建函数lv_XXX_create来创建相关的对象。lv_scr_act()表示对组件赋予显示功能。
1.2 LVGL仿真
直接在Linux系统中进行LVGL GUI界面编程不方便,也不利于时刻显示编程结果,这里建议在CodeBlock中使用LVGL模拟仿真器,来实时知晓自己编程的结果,对应的教程如下:
在CodeBlock中实现LVGL模拟仿真
本文先在CodeBlock中进行LVGL的仿真,然后再将源码放在Linux下进行编译与移植,使用的显示屏分辨率为800*480。
2. 代码结构
编程的代码结构如下:
官方的demo代码都是写在lv_demo_widgets.c中的,所以我们主要对其中的lv_demo_widgets.c文件进行编程和修改。assets目录中的两个文件是将图像数组化之后生成的C文件,这两个C文件里面包含了图像的像素信息,图像数组化转换的方法会在之后的导入图片步骤中进行介绍。lv_events文件夹是新创建的文件夹,其中的文件声明并定义了各个组件的事件响应函数。
3. 编程目标
本文设计的LVGL GUI界面如下:
我们只实现上述图片中LED灯控制开关、蜂鸣器控制开关、温湿度实时显示的三个相关功能。其中LED开关开启后,外接的LED灯会被点亮,开关关闭之后灯会熄灭;Buzzer开关开启之后,板载的蜂鸣器会响,
4. 编程前的准备
上一章展示的GUI界面涉及到不同大小的字体,所以在构建我们的程序之前,需要将所有大小的字体使能,我们先打开源码根目录下的文件lv_conf.h,看到第326行,我们将所有字体大小的宏定义全部改为1:
/*Montserrat fonts with ASCII range and some symbols using bpp = 4*https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 1
#define LV_FONT_MONTSERRAT_10 1
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 1
/*... ...*/
#define LV_FONT_MONTSERRAT_48 1
5. LVGL编程基础
5.1 简单示例代码
下面是一个简单的LVGL编程实例,在一个Label中显示”Hello World!”:
#include "lv_demo_widgets.h"
#include "lv_events/lv_events.h"lv_obj_t *label_example;void lv_demo_widgets(void){label_example = lv_label_create(lv_scr_act());lv_label_set_text(label_example, "Hello World!");lv_obj_set_style_text_font(label_example, &lv_font_montserrat_24, LV_STATE_DEFAULT);lv_obj_align(label_example,LV_ALIGN_CENTER,0,0);return ;
};
将上述代码在LVGL仿真器中运行,效果如下:
简要说一下其中代码的逻辑:
-
label_example = lv_label_create(lv_scr_act());
这句代码的作用是创建一个Label组件的对象,lv_label_create
就是创建它的函数,其返回值是lv_obj_t *
类型,即所有组件对象的指针,其中函数lv_scr_act
的作用是让创建的该组件能在屏幕上显示; -
lv_label_set_text(label_example, "Hello World!");
这句代码的作用是设置Label组件文本的内容,其中第一个参数是创建好的Label对象指针,第二个参数是需要设置的文本内容; -
lv_obj_set_style_text_font(label_example, &lv_font_montserrat_24, LV_STATE_DEFAULT);
这句代码的作用是设置Label文本的字体大小,如第4章中头文件所展示的那样,这里的宏lv_font_montserrat_24表示将文本字体大小设置为24; -
lv_obj_align(label_example,LV_ALIGN_CENTER,0,0);
这句代码的作用是设置组件对齐。
5.2 设置组件位置
在LVGL的实际运用中,我们有两中方法来设置组件的位置。
- 第一种是直接指定其在屏幕上的位置坐标,来确定组件的绝对位置,例如:
lv_obj_set_pos(calendar, 511, 32);
上面的代码是设置一个日历(Calendar)组件的坐标位置,函数lv_obj_set_pos
原型为void lv_obj_set_pos(lv_obj_t * obj, lv_coord_t x, lv_coord_t y)
,其中第一个参数是已经创建好的组件对象指针,第二和第三个参数分别是横向坐标值和纵向坐标值。
- 第二种则是通过对齐的方式,来确定组件的相对位置,这时使用的函数为:
void lv_obj_align(lv_obj_t * obj, lv_align_t align, lv_coord_t x_ofs, lv_coord_t y_ofs)
或者
void lv_obj_align_to(lv_obj_t * obj, const lv_obj_t * base, lv_align_t align, lv_coord_t x_ofs, lv_coord_t y_ofs)
其中第一个函数是组件相对于显示屏对齐,第二个则是组件相对于其他组件进行对齐,形参align表示对齐的方式,LVGL中所有的对齐方式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0twYRi9V-1685537889428)(/align.png)]
这些对齐方式所对应宏的通用形式为:LV_ALIGN_OUT_XXX
。函数lv_obj_align
与 lv_obj_align_to
中共有的形参lv_coord_t x_ofs, lv_coord_t y_ofs
表示相对于屏幕上的(x_ofs, y_ofs)
坐标对齐。
5.3 图片的显示
本节我们讲述将图片lvgl_shallwing.jpg显示到LCD屏上的方法,图片lvgl_shallwing.jpg如下图所示:
在LVGL显示图片需要先将图片转化为像素数组,其转化的方法如下:
-
打开网页链接:LVGL官网;
-
点击网页中的”Tools“,并选择其中的Image converter:
- 在弹出的网页中,可以看到在线图片转换器,图片转换器里面有许多选项,如下图所示:
-
我们现在Imgae file中选择需要转化的图片,然后选择图片的色彩格式为
CF_TRUE_COLOR
(真彩格式),Output format保持为C array不变,然后点击Convert进行转换; -
点击“Convert”之后,网页会自动下载生成好的带有C array的C文件:
我们将生成好的lvgl_shallwing.c文件放在下列LVGL源码中的目录下,以便之后调用该文件中的生成的数组:
./lv_port_linux_frame_buffer/lvgl/demos/widgets/assets
以上就是利用LVGL官方的图片转换器进行图片像素数字化的方法。
先给出在LVGL中显示图片的代码:
void lv_demo_widgets(void){lv_obj_t *image_shallwing = lv_img_create(lv_scr_act());//Set the image size and the positionlv_img_set_src(image_shallwing, &lvgl_shallwing);lv_obj_set_pos(image_shallwing, 54, 52);return ;
}
这段代码的逻辑很简单,函数lv_img_create
的作用是创建一个image图片组件,其使用方法和lv_label_create
类似。函数lv_img_set_src
用于设置图片的内容,函数lv_img_set_src
的第二个参数是图片的像素数组名,图片lvgl_shallwing.jpg转换生成的C文件名称为lvgl_shallwing.c
,所以该图片生成的像素数组的数组名叫lvgl_shallwing
。函数lv_obj_set_pos
的作用是设置图片左上角在界面上的坐标位置,这里我们将坐标设置为(54,52);
将上面的代码编译之后,可以在LVGL模拟器中看到如下效果:
5.4 组件的事件响应
与其他图形库的编程一样,LVGL也有自己的事件响应机制,LVGL中设置组件事件响应的函数为:
struct _lv_event_dsc_t * lv_obj_add_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb, lv_event_code_t filter,void * user_data)
其中第一个参数obj为创建好的组件对象指针;event_cb是事件处理函数,当组件出发了事件时,就会执行此函数;lv_event_code_t
表示事件码,事件码用来指代事件的类型,此形参需要通过具体组件来进行设置;事件响应函数中运行用户自己传入参数,这在形参user_data中有所体现。
LVGL组件中的事件码如下:
/*** Type of event being sent to the object.*/
typedef enum {LV_EVENT_ALL = 0,/** Input device events*/LV_EVENT_PRESSED, /**< The object has been pressed*/LV_EVENT_PRESSING, /**< The object is being pressed (called continuously while pressing)*/LV_EVENT_PRESS_LOST, /**< The object is still being pressed but slid cursor/finger off of the object */LV_EVENT_SHORT_CLICKED, /**< The object was pressed for a short period of time, then released it. Not called if scrolled.*/LV_EVENT_LONG_PRESSED, /**< Object has been pressed for at least `long_press_time`. Not called if scrolled.*/LV_EVENT_LONG_PRESSED_REPEAT, /**< Called after `long_press_time` in every `long_press_repeat_time` ms. Not called if scrolled.*/LV_EVENT_CLICKED, /**< Called on release if not scrolled (regardless to long press)*/LV_EVENT_RELEASED, /**< Called in every cases when the object has been released*/LV_EVENT_SCROLL_BEGIN, /**< Scrolling begins. The event parameter is a pointer to the animation of the scroll. Can be modified*/LV_EVENT_SCROLL_END, /**< Scrolling ends*/LV_EVENT_SCROLL, /**< Scrolling*/LV_EVENT_GESTURE, /**< A gesture is detected. Get the gesture with `lv_indev_get_gesture_dir(lv_indev_get_act());` */LV_EVENT_KEY, /**< A key is sent to the object. Get the key with `lv_indev_get_key(lv_indev_get_act());`*/LV_EVENT_FOCUSED, /**< The object is focused*/LV_EVENT_DEFOCUSED, /**< The object is defocused*/LV_EVENT_LEAVE, /**< The object is defocused but still selected*/LV_EVENT_HIT_TEST, /**< Perform advanced hit-testing*//** Drawing events*/LV_EVENT_COVER_CHECK, /**< Check if the object fully covers an area. The event parameter is `lv_cover_check_info_t *`.*/LV_EVENT_REFR_EXT_DRAW_SIZE, /**< Get the required extra draw area around the object (e.g. for shadow). The event parameter is `lv_coord_t *` to store the size.*/LV_EVENT_DRAW_MAIN_BEGIN, /**< Starting the main drawing phase*/LV_EVENT_DRAW_MAIN, /**< Perform the main drawing*/LV_EVENT_DRAW_MAIN_END, /**< Finishing the main drawing phase*/LV_EVENT_DRAW_POST_BEGIN, /**< Starting the post draw phase (when all children are drawn)*/LV_EVENT_DRAW_POST, /**< Perform the post draw phase (when all children are drawn)*/LV_EVENT_DRAW_POST_END, /**< Finishing the post draw phase (when all children are drawn)*/LV_EVENT_DRAW_PART_BEGIN, /**< Starting to draw a part. The event parameter is `lv_obj_draw_dsc_t *`. */LV_EVENT_DRAW_PART_END, /**< Finishing to draw a part. The event parameter is `lv_obj_draw_dsc_t *`. *//** Special events*/LV_EVENT_VALUE_CHANGED, /**< The object's value has changed (i.e. slider moved)*/LV_EVENT_INSERT, /**< A text is inserted to the object. The event data is `char *` being inserted.*/LV_EVENT_REFRESH, /**< Notify the object to refresh something on it (for the user)*/LV_EVENT_READY, /**< A process has finished*/LV_EVENT_CANCEL, /**< A process has been cancelled *//** Other events*/LV_EVENT_DELETE, /**< Object is being deleted*/LV_EVENT_CHILD_CHANGED, /**< Child was removed, added, or its size, position were changed */LV_EVENT_CHILD_CREATED, /**< Child was created, always bubbles up to all parents*/LV_EVENT_CHILD_DELETED, /**< Child was deleted, always bubbles up to all parents*/LV_EVENT_SCREEN_UNLOAD_START, /**< A screen unload started, fired immediately when scr_load is called*/LV_EVENT_SCREEN_LOAD_START, /**< A screen load started, fired when the screen change delay is expired*/LV_EVENT_SCREEN_LOADED, /**< A screen was loaded*/LV_EVENT_SCREEN_UNLOADED, /**< A screen was unloaded*/LV_EVENT_SIZE_CHANGED, /**< Object coordinates/size have changed*/LV_EVENT_STYLE_CHANGED, /**< Object's style has changed*/LV_EVENT_LAYOUT_CHANGED, /**< The children position has changed due to a layout recalculation*/LV_EVENT_GET_SELF_SIZE, /**< Get the internal size of a widget*/_LV_EVENT_LAST, /** Number of default events*/LV_EVENT_PREPROCESS = 0x80, /** This is a flag that can be set with an event so it's processedbefore the class default event processing */
} lv_event_code_t;
可以看到,LVGL中的事件大致分为输入设备事件(Input device events)、绘图事件(Drawing events)、特殊事件(Special events)以及其他事件四种,且事件码由枚举类型定义。本文需要实现的开关功能则对应于特殊事件中的 LV_EVENT_VALUE_CHANGED
,意思就是此枚举与开关事件相关联时,开关组件的状态若发生了改变,系统就会调用事件响应函数进行处理。
Button组件按下对应的事件码为LV_EVENT_PRESSED
,表示有组件被点击的事件。
我们也可以在自己的程序中某时刻撤销组件的事件响应,撤销事件响应的函数为:
bool lv_obj_remove_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb)
此函数的返回值类型是bool,若返回false,表示没有在组件中找到形参中的事件响应函数,当返回true时,则表示此时已经成功撤销该事件的响应机制。
后面的章节里会就开关组件事件响应编写对应的代码。
5.5 设置定时任务
在LVGL里,我们可以利用其中的定时机制来周期性完成某些任务,比如后面将会实现的定时20s显示环境温湿度值。
当前现存的一些中文教程中会告诉读者设置定时任务的函数是lv_task_create()
,但这是LVGL 7.0以前的版本使用的,目前的V8.2版本已经将其改为了lv_timer_create()
:
这在LVGL源码的./lvgl/docs/overview/timer.md
文件中也有说明:
# TimersLVGL has a built-in timer system. You can register a function to have it be called periodically. The timers are handled and called in `lv_timer_handler()`, which needs to be called every few milliseconds.
See [Porting](/porting/task-handler) for more information.Timers are non-preemptive, which means a timer cannot interrupt another timer. Therefore, you can call any LVGL related function in a timer. ## Create a timer
To create a new timer, use `lv_timer_create(timer_cb, period_ms, user_data)`. It will create an `lv_timer_t *` variable, which can be used later to modify the parameters of the timer.
`lv_timer_create_basic()` can also be used. This allows you to create a new timer without specifying any parameters.
V8.2 版本中设置定时任务的函数lv_timer_create
原型为:
lv_timer_t * lv_timer_create(lv_timer_cb_t timer_xcb, uint32_t period, void * user_data);
其中第一个参数timer_xcb是定时任务所对应的回调函数,第二个参数period是定时周期,第三个参数是用户传入的数据。
6. 各个组件编程的实现
本章节主要就第3章展示的目标,逐个讲述其中各个组件的创建和功能实现。
6.1 文本标签组件(lv_label)
第3章所展示的界面中有如下部分是文本标签组件:
在第5章已经介绍了文本标签组件的创建和显示,以及字体大小的设置,这里直接给出上面所有文本标签组件相应的代码:
/* The txt label for temperature and humidty are global variables,
which are used for define event handler. */
static lv_obj_t *label_temper;
static lv_obj_t *label_humidity;//...
//...
//...void lv_demo_widgets(void){
/* Create the Label widgets and display on the LCD */ lv_obj_t *label_lvgl_demo = lv_label_create(lv_scr_act());lv_obj_t *label_author = lv_label_create(lv_scr_act());lv_obj_t *label_studio = lv_label_create(lv_scr_act());lv_obj_t *label_led = lv_label_create(lv_scr_act());lv_obj_t *label_buzzer = lv_label_create(lv_scr_act()); label_temper = lv_label_create(lv_scr_act());label_humidity = lv_label_create(lv_scr_act());/* Set the position and the text and its size *///"Donald Shallwing"lv_obj_set_pos(label_author, 193, 61);lv_label_set_text(label_author, "Donald Shallwing");lv_obj_set_style_text_font(label_author, &lv_font_montserrat_24, LV_STATE_DEFAULT);//"donaldshallwing@gmail.com"lv_obj_set_pos(label_lvgl_demo, 193, 108);lv_label_set_text(label_lvgl_demo, "donaldshallwing@gmail.com");lv_obj_set_style_text_font(label_lvgl_demo, &lv_font_montserrat_14, LV_STATE_DEFAULT);//"IoT-Yun"lv_obj_set_pos(label_studio, 193, 135);lv_label_set_text(label_studio, "IoT-Yun / CCNU");lv_obj_set_style_text_font(label_studio, &lv_font_montserrat_14, LV_STATE_DEFAULT);// "LED" & "Buzzer"lv_obj_set_pos(label_led, 90, 270);lv_label_set_text(label_led, "LED");lv_obj_set_style_text_font(label_led, &lv_font_montserrat_20, LV_STATE_DEFAULT);lv_obj_set_pos(label_buzzer, 80, 382);lv_label_set_text(label_buzzer, "Buzzer");lv_obj_set_style_text_font(label_buzzer, &lv_font_montserrat_20, LV_STATE_DEFAULT);// Label for temperature & humidty display, set " " for primarylv_obj_set_pos(label_temper, 662, 284);lv_label_set_text(label_temper, " ");lv_obj_set_style_text_font(label_temper, &lv_font_montserrat_22, LV_STATE_DEFAULT);lv_obj_set_pos(label_humidity, 662, 329);lv_label_set_text(label_humidity, " ");lv_obj_set_style_text_font(label_humidity, &lv_font_montserrat_22, LV_STATE_DEFAULT);return ;
}
温湿度需要使用定时任务机制来进行实时获取,而且我们要求温湿度的数值显示在文本标签组件中,所以这里把温湿度对应的文本标签组件变量label_temper与label_humidity设置为全局变量,为了与其他文件中的命名产生冲突,必须在定义时加上static关键字。
6.2 开关组件(lv_switch)
创建显示LED和Buzzer两个开关组件、并设置事件响应的代码如下:
static lv_obj_t *sw_led;
static lv_obj_t *sw_buzzer;static void led_event_cb(lv_event_t *e){lv_event_code_t code = lv_event_get_code(e);if(LV_EVENT_VALUE_CHANGED == code){if(lv_obj_has_state(sw_led, LV_STATE_CHECKED))led(true);elseled(false);}return ;
}static void buzzer_event_cb(lv_event_t *e){lv_event_code_t code = lv_event_get_code(e);if(LV_EVENT_VALUE_CHANGED == code){if(lv_obj_has_state(sw_buzzer, LV_STATE_CHECKED))buzzer(true);elsebuzzer(false);}return ;
}void lv_demo_widgets(void){sw_led = lv_switch_create(lv_scr_act());sw_buzzer = lv_switch_create(lv_scr_act());lv_obj_set_pos(sw_led, 83, 234);lv_obj_set_size(sw_led, 60, 30);lv_obj_add_event_cb(sw_led, led_event_cb, LV_EVENT_VALUE_CHANGED, NULL);lv_obj_set_pos(sw_buzzer, 83, 335);lv_obj_set_size(sw_buzzer, 60, 30);lv_obj_add_event_cb(sw_buzzer, buzzer_event_cb, LV_EVENT_VALUE_CHANGED, NULL);return ;
}
LVGL中所有事件的回调函数都具有相同的形参,即lv_event_t *e
,在一个会掉函数里,我们首先通过函数lv_event_get_code
取得事件码(lv_event_code_t
),如果事件码与lv_obj_add_event_cb
中添加的事件码一直,则运行后面的处理程序。我们可以看到在回调函数led_event_cb
中,通过函数lv_obj_has_state
取得开关的状态之后,如果此时开关是开启的,则通过自定的函数led
来点亮LED灯,函数led
的代码在第二章中所述的文件lv_events.c中。
蜂鸣器的事件响应函数逻辑与LED灯是一样的。
6.3 图片按键组件(lv_btn)
第3章里展示的温湿度logo是一个图片按键组件,从本质上来看,它和普通的Button没什么区别,其创建显示,以及事件响应的代码如下:
static lv_obj_t *image_temper;#if USE_IMGBTN_EVENT
static void sht20_event_cb(lv_event_t *e){double temperature, humidity;char buffer[32] = {0};lv_event_code_t code = lv_event_get_code(e);if(LV_EVENT_PRESSED == code){sht20(&temperature, &humidity);sprintf(buffer, "%.2lf 'C", temperature);lv_label_set_text(label_temper, buffer);memset(buffer, 0, sizeof(buffer));sprintf(buffer, "%.2lf %%", humidity);lv_label_set_text(label_humidity, buffer);}return ;
}
#endifvoid lv_demo_widgets(void){//Image for temperature & humiditylv_imgbtn_set_src(image_temper, LV_IMGBTN_STATE_RELEASED,NULL, &lvgl_temper, NULL);lv_obj_set_size(image_temper, 80, 100);lv_obj_set_pos(image_temper, 557, 267);#if USE_IMGBTN_EVENTlv_obj_add_event_cb(image_temper, sht20_event_cb, LV_EVENT_PRESSED, NULL);#endifreturn ;
}
上述代码中回调函数的逻辑是:当该图片按键按下之后,系统会通过函数sht20
读取当前sht20温湿度传感器中的温湿度值,然后在文本标签组件label_temper
和label_humidity
中进行显示。
我们这里只是演示一下图片按键的用法,在这个项目里面,我们并不是通过图片按键按下的方式获取温湿度值,而是设置定时任务实时显示,所以这里我们将上述代码中的宏USE_IMGBTN_EVENT定义为0。
6.4 设置定时任务(lv_timer)
由于在5.5小节已经讲述了定时任务的设置方法,这里直接给出其中温湿度实时显示的代码:
static void sht20_task(lv_timer_t *timer){double temperature, humidity;char buffer[32] = {0};sht20(&temperature, &humidity);sprintf(buffer, "%.2lf 'C", temperature);lv_label_set_text(label_temper, buffer);memset(buffer, 0, sizeof(buffer));sprintf(buffer, "%.2lf %%", humidity);lv_label_set_text(label_humidity, buffer);return ;
}void lv_demo_widgets(void){timer_sht20 = lv_timer_create(sht20_task, 1000*20, NULL);return ;
}
我们将温湿度读取的时间间隔设置为20s。
6.5 显示其他组件
第三章中所展示的日历(lv_calendar)和图表(lv_chart)组件我们只做了显示,没有实现其事件响应,此工作留在之后的文档中完成。显示日历和图表的代码如下:
static lv_obj_t *calendar = lv_calendar_create(lv_scr_act());
static lv_obj_t *temper_chart = lv_chart_create(lv_scr_act());void lv_demo_widgets(void){//Calendarlv_obj_set_pos(calendar, 511, 32);lv_obj_set_size(calendar, 205, 155);//Chartlv_obj_set_pos(temper_chart, 245, 222);lv_obj_set_size(temper_chart, 254, 190);return ;
}
7. 代码测试
将上述代码在Linux下通过源码的主Makefile交叉编译之后,然后通过TFTP将可执行文件传输到IMX6ULL开发板上,进行如下测试:
- 若执行之后显示的效果和仿真一样,则说明组件的创建与显示部分正常;
- 若在点击LED的开关之后,LED灯被点亮,且再次按下开关之后,LED灯熄灭,则说明LED事件响应函数设置正确;
- 若在点击蜂鸣器的开关之后,板载蜂鸣器发出声响,且再次按下开关之后,不再发声,则说明蜂鸣器事件响应函数设置正确;
- 若在屏幕上每隔20s能看到温湿度在更新,则说明实时过去温湿度的功能已经实现。
8. 工程源码
本工程的所有源码已在Gitee上给出,链接如下:
lvgl-lcd
由于点亮LED灯和使能蜂鸣器的代码不属于LVGL编程的范围,故此代码未在文档中展示。
参考文档
【LVGL事件(Events)】事件代码
在CodeBlock中实现LVGL模拟仿真