项目经验分享:LVGL编程举例

本文介绍如何在成功移植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仿真器中运行,效果如下:

在这里插入图片描述

简要说一下其中代码的逻辑:

  1. label_example = lv_label_create(lv_scr_act());这句代码的作用是创建一个Label组件的对象,lv_label_create就是创建它的函数,其返回值是lv_obj_t *类型,即所有组件对象的指针,其中函数lv_scr_act的作用是让创建的该组件能在屏幕上显示;

  2. lv_label_set_text(label_example, "Hello World!");这句代码的作用是设置Label组件文本的内容,其中第一个参数是创建好的Label对象指针,第二个参数是需要设置的文本内容;

  3. lv_obj_set_style_text_font(label_example, &lv_font_montserrat_24, LV_STATE_DEFAULT); 这句代码的作用是设置Label文本的字体大小,如第4章中头文件所展示的那样,这里的宏lv_font_montserrat_24表示将文本字体大小设置为24;

  4. 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_alignlv_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显示图片需要先将图片转化为像素数组,其转化的方法如下:

  1. 打开网页链接:LVGL官网;

  2. 点击网页中的”Tools“,并选择其中的Image converter:

在这里插入图片描述

  1. 在弹出的网页中,可以看到在线图片转换器,图片转换器里面有许多选项,如下图所示:

在这里插入图片描述

  1. 我们现在Imgae file中选择需要转化的图片,然后选择图片的色彩格式为CF_TRUE_COLOR(真彩格式),Output format保持为C array不变,然后点击Convert进行转换;

  2. 点击“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 position
    lv_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 processed
                                      before 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文件中也有说明:

# Timers

LVGL 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 primary
    lv_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);
        else
            led(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);
        else
            buzzer(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 ;
}
#endif

void lv_demo_widgets(void){
    //Image for temperature & humidity
    lv_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_EVENT
		lv_obj_add_event_cb(image_temper, sht20_event_cb, LV_EVENT_PRESSED, NULL);
	#endif
    return ;
}

上述代码中回调函数的逻辑是:当该图片按键按下之后,系统会通过函数sht20读取当前sht20温湿度传感器中的温湿度值,然后在文本标签组件label_temperlabel_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){
    
	//Calendar
    lv_obj_set_pos(calendar, 511, 32);
    lv_obj_set_size(calendar, 205, 155);

    //Chart
    lv_obj_set_pos(temper_chart, 245, 222);
    lv_obj_set_size(temper_chart, 254, 190);
    
    return ;
}

7. 代码测试

将上述代码在Linux下通过源码的主Makefile交叉编译之后,然后通过TFTP将可执行文件传输到IMX6ULL开发板上,进行如下测试:

  1. 若执行之后显示的效果和仿真一样,则说明组件的创建与显示部分正常;
  2. 若在点击LED的开关之后,LED灯被点亮,且再次按下开关之后,LED灯熄灭,则说明LED事件响应函数设置正确;
  3. 若在点击蜂鸣器的开关之后,板载蜂鸣器发出声响,且再次按下开关之后,不再发声,则说明蜂鸣器事件响应函数设置正确;
  4. 若在屏幕上每隔20s能看到温湿度在更新,则说明实时过去温湿度的功能已经实现。

8. 工程源码

本工程的所有源码已在Gitee上给出,链接如下:
lvgl-lcd
由于点亮LED灯和使能蜂鸣器的代码不属于LVGL编程的范围,故此代码未在文档中展示。

参考文档

【LVGL事件(Events)】事件代码
在CodeBlock中实现LVGL模拟仿真

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

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

相关文章

【论文阅读】Twin Neural Network Regression

论文下载 GitHub bib: ARTICLE{SebastianKevin2022Twin,title {Twin neural network regression},author {Sebastian Johann Wetzel and Kevin Ryczko and Roger Gordon Melko and Isaac Tamblyn},journal {Applied AI Letters},year {2022},volume {3},number …

【LeetCode】260. 只出现一次的数字 III

260. 只出现一次的数字 III&#xff08;中等&#xff09; 思路 这道题是136. 只出现一次的数字 的进阶版&#xff0c;需要找出两个仅出现一次的元素。有了上一题的基础&#xff0c;我们很容易就想到要用异或来解决&#xff0c;但是由于这题最终会剩下两个不同的元素&#xff0…

5.31串讲Spring、Vue相关问题

5.31串讲 SSM相关问题 文章目录 5.31串讲 SSM相关问题Spring Security&#xff08;Shiro&#xff09;Security框架认证流程Security流程图展示 Vue相关指令四个阶段 axios Spring Security&#xff08;Shiro&#xff09; Spring Security是一个基于Spring 的安全框架&#xff…

软考A计划-电子商务设计师-电子商务系统规划

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

ChatGPT浪潮席卷,维智科技以时空AI赋能数实融合的未来城市

作者 | 伍杏玲 出品 | CSDN 每个时代都有新的技术浪潮&#xff0c;但在短短两年时间里见证两项颠覆全球的技术发展&#xff0c;实在出人意料之外&#xff1a;2021年以来&#xff0c;元宇宙成为互联网产业新风口&#xff0c;今年ChatGPT成为IT圈“顶流”&#xff0c;这两者为地…

数据在内存中的存储

目录 简介数据在内存中的存储方式 整形 有符号整形(signed) 无符号整形(unsigned) 原码、反码、补码 大端小端 整形提升 数据截断 浮点数在内存中的存储 S、E、M S M E double和float的存储模型 简介数据在内存中的存储方式 在讨论数据在内存中的存储方式之前&am…

类脑计算讲解

当前&#xff0c;人工智能的发展有两个主要路径&#xff0c;一个是沿计算机科学发展而来的深度学习途径&#xff0c;另一个是沿着模仿人脑发展而来的类脑计算途径。 类脑计算途径 这个方向是以模拟人脑神经网络计算为基础而发展出的一种新型芯片&#xff0c;通过模拟神经元和…

在线教育机构的视频如何做防下载和防盗录?

在线教育平台付费课程、企业内训的培训课程&#xff0c;这类视频课程内容是如何做防下载和防盗录的&#xff1f; 1.AI隐形溯源水印 这个功能能够将水印隐藏在视频中&#xff0c;不会影响观看体验&#xff0c;但却能够帮助企业很好的视频版权保护。更重要的是&#xff0c;对于盗…

【优化调度】基于改进遗传算法的公交车调度排班优化的研究与实现(Matlab代码实现)

目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码 1 概述 本文对当前公交企业调度系统进行了分析&#xff0c;建立了公交排班的数学模型。本文基于数据挖掘分析的结果上&#xff0c;使用截面客流量数据对模型进行约束&#xff0c;得出了公交客流出行的空间分布规律。再以…

ShareX_一款好用的截图工具安装- Window

择心】向大家介绍and安装ShareX ShareX 免费、开源、轻量多区域截图无缝处理截图屏幕录制、文件共享各种实用工具&#xff08;如拾色器&#xff0c;屏幕拾色器&#xff0c;尺子&#xff0c;图像编辑器&#xff0c;图像合并&#xff0c;图像分割器&#xff0c;生成图像缩略图&am…

三波混频下的相位失配原理

原理推导 在四波混频情况下&#xff0c;实现零相位失配是一件很困难的事情。因为在四波混频中&#xff0c;相位调制和增益都依赖于相同的参数&#xff0c;即克尔非线性 γ \gamma γ。这个问题可以用嵌入在传输线上的辅助共振元件的复杂色散工程来部分解决。 但是在三波混频中…

离散数学_十章-图 ( 5 ):连通性 - 上

&#x1f4f7;10.5 图的连通性 1. 通路1.1 通路1.2 回路1.3 其他术语 2. 无向图的连通性2.1 无向图的连通与不连通2.2 定理2.3 连通分支 3. 图是如何连通的3.1 割点&#xff08; 关节点&#xff09;3.2 割边&#xff08; 桥&#xff09;3.3 不可分割图3.4 &#x1d458;(&#…

华为OD机试真题 Java 实现【跳格子2】【2023 B卷 100分】,附详细解题思路

一、题目描述 小明和朋友玩跳格子游戏&#xff0c;有n个连续格子组成的圆圈&#xff0c;每个格子有不同的分数&#xff0c;小朋友可以选择从任意格子起跳&#xff0c;但是不能跳连续的格子&#xff0c;不能回头跳&#xff0c;也不能超过一圈。 给定一代表每个格子得分的非负整…

3.9 流水作业调度问题

博主简介&#xff1a;一个爱打游戏的计算机专业学生博主主页&#xff1a; 夏驰和徐策所属专栏&#xff1a;算法设计与分析 1.我对流水调度问题的理解 流水作业调度问题是动态规划中的一个经典问题&#xff0c;它涉及将一系列作业分配给多个工作站以最小化总完成时间。该问题的…

练习:有限状态机测试

练习&#xff1a;有限状态机测试 1 FSM 示例 在练习中&#xff0c;我们将使用两个 FSM。 两者都有输入字母 X {a, b} 和输出字母 Y {0,1}。 第一个 FSM 将称为 M1 并由以下有向图表示。 对于上面给出的每个 FSM Mi&#xff1a; 1.确定以下值&#xff0c;显示您的工作。 (a…

内存对齐原则

struct &#xff08;1&#xff09;结构体第一个数据成员放在offset为0的地方&#xff0c;后面每个成员相对于结构体首地址的偏移量&#xff08;offset&#xff09;都是成员大小&#xff08;该变量类型所占字节&#xff09;的整数倍&#xff0c;如有需要编译器会在成员之间加上填…

非煤矿山电子封条系统算法方案 opencv

非煤矿山电子封条系统算法部署方案是基于pythonopencv网络模型Ai视频图像识别技术&#xff0c;非煤矿山电子封条系统算法部署方案对出入井人员、人员变化及非煤矿山生产作业状态等状况&#xff0c;及时发现处理异常动态将自动发出警报。OpenCV的全称是Open Source Computer Vis…

研报精选230528

目录 【行业230528华金证券】传媒行业深度研究&#xff1a;AIGC最新应用与场景研究 【行业230528国海证券】电动船舶行业深度报告&#xff1a;绿色智能大势已至&#xff0c;驶向电化百亿蓝海 【行业230528华西证券】纺织服装行业周报&#xff1a;5月增长放缓无碍中长期出清逻辑…

Vue.js 中的过滤器和计算属性

Vue.js 中的过滤器和计算属性 Vue.js 是一款流行的 JavaScript 框架&#xff0c;它提供了一种简单而灵活的方式来构建交互式 Web 应用程序。在 Vue.js 中&#xff0c;过滤器和计算属性是两个常用的概念。它们可以帮助开发者更方便地处理数据&#xff0c;提高代码的可读性和可维…

【Linux】进程状态与进程优先级

目录 一、什么是进程二、进程状态1、Linux下的进程状态2、两个特殊进程1、僵尸进程2、孤儿进程 三、进程优先级 一、什么是进程 进程就是程序的一个执行实例&#xff0c;也就是正在执行的程序&#xff0c;然后由操作系统帮助我们将程序转化为进程&#xff0c;完成特定的任务。…