跳到主要内容

LVGL 移植教程

在本教程中,我们将详细介绍如何利用 LVGL 图形库实现简易控件,以及移植应用程序到 Luckfox Pico Ultra W 开发板来展示运行效果

1.源码结构简介

LVGL( LittlevGL )是一款轻量级、开源的嵌入式图形库,专注于提供灵活易用的图形用户界面( GUI )解决方案。通过 LVGL ,开发者可以实现丰富的图形界面设计,包括按钮、滑块、文本框等各种常用的 GUI 控件,以及动画和触摸交互等功能。接下来,我们将分析其源码目录中的核心文件和目录,使得在移植过程中更加清晰其运行流程。

  1. lvgl源码结构

    文件/目录名称描述
    CMakeLists.txtCMake 构建系统的配置文件,用于定义如何编译和链接 LVGL 库。它包含了源文件、库依赖和构建选项的设置。
    src存放 LVGL 的源代码,包括库的核心实现和功能模块。这里包含了 LVGL 的主要逻辑和 API。
    examples示例代码,用于演示如何使用 LVGL 创建图形用户界面。可以帮助开发者了解 LVGL 的用法和功能。
    demos提供实际的演示程序,展示 LVGL 在不同场景和设备上的应用。这些演示通常包含可视化效果和交互功能,帮助用户理解库的能力。
    tests包含单元测试和集成测试代码,用于验证 LVGL 的功能和稳定性。确保在不同版本和平台上的一致性。
    lv_conf_template.h配置模板文件,用于用户自定义 LVGL 的参数和设置。用户可以复制并重命名此文件以适应他们的项目需求。
    lvgl.hLVGL 的主头文件,包含了库的主要接口和数据结构的定义。用户在使用 LVGL 时通常会包括此文件。

  2. lv_drivers 源码结构

    lv_drivers 是 LVGL 项目中的一个子模块,它包含了用于与各种硬件设备通信的驱动程序。这些驱动程序使得 LVGL 可以适配不同的显示和输入设备,如 LCD 显示器、触摸屏、按键等。

    目录名称描述
    display包含多种显示屏驱动的实现文件,每个文件通常对应一种特定的显示器型号。
    indev包含输入设备驱动的实现文件,这些文件处理各种触摸屏和输入设备的输入。
    gtkdrv包含针对 GTK 图形界面的驱动实现,可能用于在 GTK 应用中使用 LVGL。包含的文件如 gtkdrv.cgtkdrv.h 提供了与 GTK 交互的功能。
    sdl包含与 SDL 相关的驱动,主要用于在使用 SDL 的应用程序中实现 LVGL。文件如 sdl.c 提供了与 SDL 相关的绘图和事件处理功能。
    wayland包含与 Wayland 显示服务器协议相关的驱动。包含的文件(如 wayland.c)提供了与 Wayland 的集成。
    win32drv包含 Windows 平台相关的驱动实现,可能用于在 Windows 环境中运行 LVGL。相关的源文件和头文件提供了在 Windows 上运行 LVGL 所需的功能。

  3. 官方资源链接

    资源名称仓库地址描述
    lvglhttps://github.com/lvgl/lvgl.gitLVGL图形界面控件的源码
    lv_drivershttps://github.com/lvgl/lv_drivers.git驱动LVGL图形界面的驱动接口源代码
    lv_port_linux_frame_bufferhttps://github.com/lvgl/lv_port_linux_frame_buffer.git适配有Framebuffer的linux系统的示例项目

2.LVGL项目搭建

  1. 创建目录,存放官方源码:

    luckfox@luckfox:~$ mkdir lvgl
    luckfox@luckfox:~$ cd lvgl
  2. 获取 lvgl 源码:

    git clone -b release/v8.3 https://github.com/lvgl/lvgl.git
    git clone -b release/v8.3 https://github.com/lvgl/lv_drivers.git
    git clone --branch release/v8.2 --single-branch https://github.com/lvgl/lv_port_linux_frame_buffer.git
  3. 创建待移植的工程目录:

    luckfox@luckfox:~/lvgl$ mkdir -p lvgl_project
    luckfox@luckfox:~/lvgl$ cd lvgl_project
  4. 复制 lvgl 核心库, lvgl 是LVGL的核心库,包含了所有绘图、组件、和核心逻辑。

    cp -r ~/lvgl/lvgl ./
  5. 复制 lv_drivers 驱动库,lv_drivers 提供了硬件接口驱动,例如 Frame Buffer(帧缓冲区)、输入设备(鼠标、触摸屏)等。

    cp -r ~/lvgl/lv_drivers ./
  6. 复制 lv_conf_template.h 核心配置文件,它提供了设置库的基本行为、禁用未使用的模块、在编译时调整内存缓冲区的大小等功能。重命名为 lv_conf.h 便于编译时引用。

    cp ~/lvgl/lvgl/lv_conf_template.h ./lv_conf.h
  7. 复制 lv_drv_conf_template.h 驱动配置文件,它提供了读取驱动的配置选项,里面包含了各种驱动相关的设置,如:触摸屏、显示器等驱动。重命名为 lv_drv_conf.h 便于编译时引用。

    cp ~/lvgl/lv_drivers/lv_drv_conf_template.h ./lv_drv_conf.h
  8. 复制 main.c 文件, 它提供了 LVGL 库的基本状态初始化, Frame Buffer设备初始化,设置Tick接口,设置显示接口,设置输入设备接口。

    cp ~/lvgl/lv_port_linux_frame_buffer/main.c ./
  9. 复制 mouse_cursor_icon.c 文件, mouse_cursor_icon.c 是鼠标光标图片转换后的 C 文件,方便显示鼠标图标。

    cp ~/lvgl/lv_port_linux_frame_buffer/mouse_cursor_icon.c ./
  10. 复制 Makefile 文件,LVGL官方提供的 Makefile 包含了必要的设置和依赖项,确保了正确的构建配置。

    cp ~/lvgl/lv_port_linux_frame_buffer/Makefile ./
  11. 至此,所需的工程文件已复制完成, lvgl_project 工程目录下的文件如下:

    luckfox@luckfox:~/lvgl/lvgl_project$ ls -l
    total 88
    -rw-rw-r-- 1 luckfox luckfox 26765 13 17:04 lv_conf.h
    drwxrwxr-x 11 luckfox luckfox 4096 13 20:52 lv_drivers
    -rw-rw-r-- 1 luckfox luckfox 15131 12 19:00 lv_drv_conf.h
    drwxrwxr-x 11 luckfox luckfox 4096 12 17:21 lvgl
    -rw-rw-r-- 1 luckfox luckfox 2637 13 17:31 main.c
    -rw-rw-r-- 1 luckfox luckfox 2155 13 17:52 Makefile
    -rw-rw-r-- 1 luckfox luckfox 21242 12 17:42 mouse_cursor_icon.c
  12. 修改 lv_conf.h 配置,将一开始的 #if 0 改为 #if 1 整个文件的配置内容才会生效,将 LV_TICK_CUSTOM 置1,使用户自定义的时钟源使能,LVGL 需要系统不断的提供一个毫秒级( ms )的 Tick 维持 LVGL 的心跳。

    /* clang-format on */
    #if 1 /*Set to '1' to launch content*/

    ...

    #define LV_TICK_CUSTOM 1
    #if LV_TICK_CUSTOM
    // #define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
    // #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
    #define LV_TICK_CUSTOM_INCLUDE <stdint.h> /*Header for the system time function*/
    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get()) /*Expression evaluating to current system time*/
    #endif /*LV_TICK_CUSTOM*/
  13. 修改 lv_drv_conf.h 配置,将一开始的 #if 0 改为 #if 1 整个文件的配置内容才会生效,将 USE_FBDEV 置1,使程序支持 Framebuffer 设备。

    /* clang-format on */
    #if 1 /*Set to '1' to launch content*/

    ...

    /*-----------------------------------------
    * Linux frame buffer device (/dev/fbx)
    *-----------------------------------------*/
    #ifndef USE_FBDEV
    # define USE_FBDEV 1
    #endif

    #if USE_FBDEV
    # define FBDEV_PATH "/dev/fb0"
    #endif
  14. 注释以下 main.c 代码,未使用官方 demo 库。

    // #include "lv_demos/lv_demo.h"
    ···
    /*Create a Demo*/
    // lv_demo_widgets();
  15. 修改 makefile 方便管理编译过程文件。

    CC = gcc
    LVGL_DIR_NAME ?= lvgl
    LVGL_DIR ?= .
    WARNINGS := -Wall -Wshadow -Wundef -Wmissing-prototypes \
    -Wno-discarded-qualifiers -Wextra -Wno-unused-function \
    -Wno-error=strict-prototypes -Wpointer-arith \
    -fno-strict-aliasing -Wno-error=cpp -Wuninitialized \
    -Wmaybe-uninitialized -Wno-unused-parameter \
    -Wno-missing-field-initializers -Wtype-limits \
    -Wsizeof-pointer-memaccess -Wno-format-nonliteral \
    -Wno-cast-qual -Wunreachable-code -Wno-switch-default \
    -Wreturn-type -Wmultichar -Wformat-security \
    -Wno-ignored-qualifiers -Wno-sign-compare \
    -Wno-error=missing-prototypes -Wdouble-promotion \
    -Wclobbered -Wdeprecated -Wempty-body -Wshift-negative-value \
    -Wstack-usage=2048 -Wno-unused-value \
    -std=c99
    CFLAGS ?= -O3 -g0 -I$(LVGL_DIR)/ $(WARNINGS)
    LDFLAGS ?= -lm
    BIN = demo
    BUILD_DIR = ./build
    BUILD_OBJ_DIR = $(BUILD_DIR)/obj
    BUILD_BIN_DIR = $(BUILD_DIR)/bin

    prefix ?= /usr
    bindir ?= $(prefix)/bin

    #Collect the files to compile
    MAINSRC = $(wildcard ./*.c)

    include $(LVGL_DIR)/lvgl/lvgl.mk
    include $(LVGL_DIR)/lv_drivers/lv_drivers.mk

    # CSRCS +=$(LVGL_DIR)/mouse_cursor_icon.c

    OBJEXT ?= .o

    AOBJS = $(ASRCS:.S=$(OBJEXT))
    COBJS = $(CSRCS:.c=$(OBJEXT))

    MAINOBJ = $(MAINSRC:.c=$(OBJEXT))

    SRCS = $(ASRCS) $(CSRCS) $(MAINSRC)
    SRC_DIR = $(LVGL_DIR)/source/multex
    CSRCS += $(wildcard $(SRC_DIR)/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/components/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/fonts/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/images/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/screens/*.c)

    CSRCS +=$(LVGL_DIR)/source/button/button.c
    CSRCS +=$(LVGL_DIR)/source/slider/slider.c
    CSRCS +=$(LVGL_DIR)/source/switch/switch.c

    OBJS = $(AOBJS) $(COBJS) $(MAINOBJ)
    TARGET = $(addprefix $(BUILD_OBJ_DIR)/, $(patsubst ./%, %, $(OBJS)))

    ## MAINOBJ -> OBJFILES

    all: default

    $(BUILD_OBJ_DIR)/%.o: %.c
    @mkdir -p $(dir $@)
    @$(CC) $(CFLAGS) -c $< -o $@
    @echo "CC $<"

    default: $(TARGET)
    @mkdir -p $(dir $(BUILD_BIN_DIR)/)
    $(CC) -o $(BUILD_BIN_DIR)/$(BIN) $(TARGET) $(LDFLAGS)

    clean:
    rm -rf $(BUILD_DIR)

    install:
    install -d $(DESTDIR)$(bindir)
    install $(BUILD_BIN_DIR)/$(BIN) $(DESTDIR)$(bindir)

    uninstall:
    $(RM) -r $(addprefix $(DESTDIR)$(bindir)/,$(BIN))OBJ_DIR := $(BUILD_DIR)/obj
    BIN_DIR := $(BUILD_DIR)/bin

    # Ensure build directories exist
    $(OBJ_DIR) $(BIN_DIR):
    mkdir -p $@

    all: default

    %.o: %.c
    @$(CC) $(CFLAGS) -c $< -o $(OBJ_DIR)/$@
    @echo "CC $<"

    default: $(AOBJS) $(COBJS) $(MAINOBJ)
    $(CC) -o $(BIN_DIR)/$(BIN) $(OBJ_DIR)/$(MAINOBJ) $(addprefix $(OBJ_DIR)/,$(AOBJS)) $(addprefix $(OBJ_DIR)/,$(COBJS)) $(LDFLAGS)

    clean:
    rm -f $(BUILD_DIR)
  16. 执行 make 指令。

    make
  17. 检查生成文件。

    tree build/ -L 2
    • 生成文件如下即执行成功:

3.示例程序

示例程序:lvgl_project.tar.gz

3.1 按键例程

  1. 创建并移动到例程文件存储目录。

    mkdir -p source/button
    cd source/button
  2. 编辑 button.c 文件。

    vi button.c

    复制粘贴以下内容,代码创建了一个带有标签的按钮居中于屏幕,并对点击事件做出计数+1响应:

    #include "button.h"
    #include "../../lvgl/lvgl.h"

    static void btn_event_cb(lv_event_t * e)
    {
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * btn = lv_event_get_target(e);
    if(code == LV_EVENT_CLICKED) {
    static uint8_t cnt = 0;
    cnt++;

    /*Get the first child of the button which is the label and change its text*/
    lv_obj_t * label = lv_obj_get_child(btn, 0);
    lv_label_set_text_fmt(label, "Button: %d", cnt);
    }
    }

    /**
    * Create a button with a label and react on click event.
    */
    void lv_example_get_started(void)
    {
    /*Add a button the current screen*/
    lv_obj_t * btn = lv_btn_create(lv_scr_act());
    /* Align the button to the center of the screen */
    lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
    /*Set its size*/
    lv_obj_set_size(btn, 120, 50);
    /*Assign a callback to the button*/
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
    /*Add a label to the button*/
    lv_obj_t * label = lv_label_create(btn);
    /*Set the labels text*/
    lv_label_set_text(label, "Button");
    lv_obj_center(label);
    }

    void button_demo_init(){
    lv_obj_clean(lv_scr_act());
    lv_example_get_started();
    }
  3. 编辑 button.h 文件。

    vi button.h

    复制粘贴以下内容,在头文件声明函数方便外部调用:

    #ifndef __BUTTON_H__
    #define __BUTTON_H__

    void button_demo_init();

    #endif // __BASEDEMO_H__
  4. 修改 main.c 调用 button_demo_init 函数,以及设置 disp_drv.hor_res 水平分辨率和 disp_drv.ver_res 垂直分辨率适配屏幕。

    #include "source/button/button.h"

    #define DISP_BUF_SIZE (480 * 480 * 2) //Buffer size for LVGL's screen drawing. A larger buffer reduces update frequency to the display and increases memory usage.

    int main(void)
    {
    ...
    /*According to the actual screen resolution used, the following is the setting for a screen resolution of 480x480 specification*/
    disp_drv.hor_res = 480; //Set horizontal resolution
    disp_drv.ver_res = 480; //Set vertical resolution
    ...
    /*Create a Demo*/
    // lv_demo_widgets();
    button_demo_init();
    ...
    }

3.2 滑动条例程

  1. 创建并移动到例程文件存储目录。

    mkdir -p source/slider
    cd source/slider
  2. 编辑 slider.c 文件。

    vi slider.c

    复制粘贴以下内容,代码创建了一个滑块控制背光并将其值写在标签上:

    #include "slider.h"
    #include <dirent.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include "../../lvgl.h"

    static lv_obj_t * label;

    int set_backlight(unsigned int duty)
    {
    char path[120] = {0};
    char buff[10];
    int value;
    int fd;
    int ret = -1;

    memset(path, 0x0, sizeof(path));
    sprintf(path, "/sys/class/backlight/backlight/max_brightness");
    fd = open(path, O_RDONLY);
    if (fd < 0) return -2;

    memset(buff, 0x0, sizeof(buff));
    ret = read(fd, buff, sizeof(buff));
    close(fd);
    value = atoi(buff);
    if (ret < 0)
    {
    return -1;
    }

    value = value * ((double)duty / 100.0);
    memset(path, 0x0, sizeof(path));
    sprintf(path, "/sys/class/backlight/backlight/brightness");
    fd = open(path, O_WRONLY | O_CREAT, 0777);
    if (fd < 0) return -2;

    memset(buff, 0x0, sizeof(buff));
    int len = sprintf(buff, "%d", value);
    ret = write(fd, buff, len);
    close(fd);
    return ret;
    }

    int get_backlight()
    {
    char path[120] = {0};
    char buff[10];
    int value;
    int max;
    int fd;
    int ret = -1;

    memset(path, 0x0, sizeof(path));
    sprintf(path, "/sys/class/backlight/backlight/max_brightness");
    fd = open(path, O_RDONLY);
    if (fd < 0) return -2;

    memset(buff, 0x0, sizeof(buff));
    ret = read(fd, buff, sizeof(buff));
    close(fd);
    max = atoi(buff);
    printf("max:%d\n",max);
    if (ret < 0)
    {
    return -1;
    }

    memset(path, 0x0, sizeof(path));
    sprintf(path, "/sys/class/backlight/backlight/brightness");
    fd = open(path, O_RDONLY);
    if (fd < 0) return -2;

    memset(buff, 0x0, sizeof(buff));
    ret = read(fd, buff, sizeof(buff));
    if (ret < 0)
    {
    return -1;
    }
    value = atoi(buff);
    printf("value:%d\n",value);
    ret = value * (100.0 / (double)max);
    close(fd);
    return ret;
    }

    static void slider_event_cb(lv_event_t * e)
    {
    lv_obj_t * slider = lv_event_get_target(e);

    /*Refresh the text*/
    lv_label_set_text_fmt(label, "%"LV_PRId32, lv_slider_get_value(slider));
    /*Align top of the slider*/
    lv_obj_align_to(label, slider, LV_ALIGN_OUT_TOP_MID, 0, -15);
    int ret = set_backlight(lv_slider_get_value(slider));
    if (ret < 0)
    {
    perror("set_backlight");
    }
    }

    /**
    * Create a slider and write its value on a label.
    */
    void lv_example_slider(void)
    {
    char bl_label[3];
    int backlight_value = get_backlight();
    if(backlight_value < 0){
    perror("get_backlight");
    }
    printf("backlight_value:%d\n",backlight_value);
    sprintf(bl_label,"%d",backlight_value);
    /*Create a slider in the center of the display*/
    lv_obj_t * slider = lv_slider_create(lv_scr_act());
    /*Set the width*/
    lv_obj_set_width(slider, 200);
    /*Align to the center of the parent (screen)*/
    lv_obj_center(slider);
    /*Assign an event function*/
    lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
    lv_slider_set_value(slider, backlight_value, LV_ANIM_OFF);

    /*Create a label above the slider*/
    label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, bl_label);
    /*Align top of the slider*/
    lv_obj_align_to(label, slider, LV_ALIGN_OUT_TOP_MID, 0, -15);
    }

    void slider_demo_init(){
    lv_obj_clean(lv_scr_act());
    lv_example_slider();
    }
  3. 编辑 slider.h 文件。

    vi slider.h

    复制粘贴以下内容,在头文件声明函数方便外部调用:

    #ifndef __SLIDER_DEMO_H__
    #define __SLIDER_DEMO_H__

    void slider_demo_init();
    int set_backlight(unsigned int duty);
    int get_backlight();

    #endif // __BASEDEMO_H__
  4. 修改 main.c 调用 slider_demo_init 函数,以及修改 disp_drv.hor_res 水平分辨率和 disp_drv.ver_res 垂直分辨率适配屏幕。

    #include "source/slider/slider.h"

    #define DISP_BUF_SIZE (480 * 480 * 2) //Buffer size for LVGL's screen drawing. A larger buffer reduces update frequency to the display and increases memory usage.

    int main(void)
    {
    ...
    /*According to the actual screen resolution used, the following is the setting for a screen resolution of 480x480 specification*/
    disp_drv.hor_res = 480; //Set horizontal resolution
    disp_drv.ver_res = 480; //Set vertical resolution
    ...
    /*Create a Demo*/
    // lv_demo_widgets();
    slider_demo_init();
    ...
    }

3.3 开关例程

  1. 创建并移动到例程文件存储目录。

    mkdir -p source/switch
    cd source/switch
  2. 编辑 switch.c 文件。

    vi switch.c

    复制粘贴以下内容,代码创建了一个开关控件用于控制GPIO_B1_d:

    #include "switch.h"
    #include <fcntl.h>
    #include <stdio.h>
    #include "../../lvgl.h"

    int gpio_export(int gpio_pin)
    {
    FILE *export_file = fopen("/sys/class/gpio/export", "w");
    if (export_file == NULL) {
    perror("Failed to open GPIO export file");
    return -1;
    }
    fprintf(export_file, "%d", gpio_pin);
    fclose(export_file);
    return 0;
    }

    int gpio_out_direction(int gpio_pin)
    {
    char direction_path[50];
    snprintf(direction_path, sizeof(direction_path), "/sys/class/gpio/gpio%d/direction", gpio_pin);
    FILE *direction_file = fopen(direction_path, "w");
    if (direction_file == NULL) {
    perror("Failed to open GPIO direction file");
    return -1;
    }
    fprintf(direction_file, "out");
    fclose(direction_file);
    return 0;
    }

    int set_gpio(int gpio_pin, int val)
    {
    int len;
    char buff[10];
    char filename[64];
    int ret,result;

    memset(filename, 0x0, sizeof(filename));
    sprintf(filename, "/sys/class/gpio/gpio%d/value", gpio_pin);
    FILE *value_file = fopen(filename, "w");
    if (value_file == NULL)
    {
    ret = gpio_export(gpio_pin);
    if (ret < 0)
    {
    return ret;
    }
    else
    {
    result = gpio_out_direction(gpio_pin);
    if(result < 0)
    return result;
    FILE *value_file = fopen(filename, "w");
    if (value_file == NULL)
    return ret;
    }
    }
    memset(buff, 0x0, sizeof(buff));
    len = sprintf(buff, "%s", val ? "1" : "0");
    fprintf(value_file,buff);
    if (ret != len)
    {
    fclose(value_file);
    return ret;
    }
    fclose(value_file);
    return 0;
    }

    static void anim_x_cb(void * var, int32_t v)
    {
    lv_obj_set_x(var, v);
    }

    static void sw_event_cb(lv_event_t * e)
    {
    lv_obj_t * sw = lv_event_get_target(e);
    lv_obj_t * label = lv_obj_get_user_data(sw);
    if(lv_obj_has_state(sw, LV_STATE_CHECKED)) {
    int ret = set_gpio(41,1);
    if(ret == 0){
    lv_label_set_text(label, "GPIO1_B1_d set High level");
    lv_obj_align_to(label,sw,LV_ALIGN_CENTER,0,-30);
    }else{
    lv_label_set_text(label, "GPIO1_B1_d set fail");
    lv_obj_align_to(label,sw,LV_ALIGN_CENTER,0,-30);
    }
    }
    else {
    int ret = set_gpio(41,0);
    if(ret == 0){
    lv_label_set_text(label, "GPIO1_B1_d set Low level");
    lv_obj_align_to(label,sw,LV_ALIGN_CENTER,0,-30);
    }else{
    lv_label_set_text(label, "GPIO1_B1_d set fail");
    lv_obj_align_to(label,sw,LV_ALIGN_CENTER,0,-30);
    }
    }

    }

    /**
    * Created a switch control to control GPIO-B1_d
    */
    void lv_demos_switch(void)
    {
    lv_obj_t * sw = lv_switch_create(lv_scr_act());
    lv_obj_center(sw);
    lv_obj_add_state(sw, LV_STATE_CHECKED);

    lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "GPIO Control");
    lv_obj_align_to(label,sw,LV_ALIGN_CENTER,0,-30);

    lv_obj_set_user_data(sw, label);
    lv_obj_add_event_cb(sw, sw_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
    }

    void switch_demo_init(){
    lv_obj_clean(lv_scr_act());
    lv_demos_switch();
    }
  3. 编辑 switch.h 文件。

    vi switch.h

    复制粘贴以下内容,在头文件声明函数方便外部调用:

    #ifndef __SLIDER_DEMO_H__
    #define __SLIDER_DEMO_H__

    void switch_demo_init();
    int gpio_export(int gpio_pin);
    int gpio_out_direction(int gpio_pin);
    int set_gpio(int gpio_pin, int val);

    #endif // __BASEDEMO_H__
  4. 修改 main.c 调用 switch_demo_init 函数,以及设置 disp_drv.hor_res 水平分辨率和 disp_drv.ver_res 垂直分辨率适配屏幕。

    #include "source/switch/switch.h"

    #define DISP_BUF_SIZE (480 * 480 * 2) //Buffer size for LVGL's screen drawing. A larger buffer reduces update frequency to the display and increases memory usage.

    int main(void)
    {
    ...
    /*According to the actual screen resolution used, the following is the setting for a screen resolution of 480x480 specification*/
    disp_drv.hor_res = 480; //Set horizontal resolution
    disp_drv.ver_res = 480; //Set vertical resolution
    ...
    /*Create a Demo*/
    // lv_demo_widgets();
    switch_demo_init();
    ...
    }

3.4 SquareLine Studio创建综合例程组件

  1. 打开 SquareLine Studio 1.5.0,在弹出的界面中选择 Create ,选择主要的LVGL版本为8.3,修改屏幕分辨率为480 x 480(请根据自己屏幕的分辨率进行修改),选择具体LVGL版本为8.3.11。

  2. 点击右下角 CREATE 创建工程,这里对模拟器的选择不重要,后续需要移植到对应开发板,而可选模拟器中并没有直接生成一整套可移植例程的功能。

  3. 界面编辑器初始创建的状态如下,有三个主要部分:面板窗口,可编辑视图,标题菜单。

    • 面板窗口的各元素功能介绍
      • Animations :创建和管理动画
      • Assets :可在项目中使用的图像和字体
      • Console :程序日志和错误消息
      • Font Maker :创建自定义字体
      • Hierarchy :选择并放置添加到屏幕的元素
      • History :程序保存您的操作。它最多可以记录 200 个操作
      • Inspector :在 Inspector 面板中,您可以查看所选小组件的参数,并可以为其分配事件
      • Error :错误面板主要显示缺少参数的错误
      • Screens :您可以找到屏幕列表,选择并管理它们
      • Widgets :您可以将此处列出的 Widget 放置在屏幕上
  4. 通过面板窗口的 Inspector 子面板设置屏幕背景色,选择 STYLE SETTINGS 样式设置,主样式中的 Background ,设置了屏幕对象的背景颜色 Bg Color HEX值为 21202E ,设置背景的渐变颜色 Bg Gradient color2D3B50 ,设置颜色梯度 Bg GradientVER 水平梯度。

  5. Assets 中添加图像。

  6. 拖动图像控件到可视化视图中,并在 Inspector 子面板选择所添加的图像并使其水平居中。

  7. 拖动文本框控件 Textarea 设置用户名,设置文本框控件为单行模式 One line mode ,设置字体颜色 Text ColorFFFFFF,设置字号 Text Fontmontserrat_24 ,设置背景角弧形半径 Bg Raduis60 ,设置背景颜色 Bg Color1A192B

  8. 拖动文本控件 Label 标识文本框,设置字体颜色以及大小。

  9. 以此类推,输入密码的文本框以及文本标识也是同样绘制或者复制粘贴,主要差别在于密码文本框设置为 Password mode

  10. 拖动按键控件 button ,创建登录按键,按键默认不带文本,需要将文本标签嵌入按键。

  11. 与此类似,创建退出按键,右键控件组,选择 Dupilcate ,会复制粘贴一个按键,拖动到合适位置并修改。

  12. 复制一个新界面,在此基础上修改。

  13. 清空新界面控件,创建弧形滑块组件,个性化修改样式,增加滑块控制文本事件效果。添加事件,设置事件名为 EventBright ,设置事件触发类型为 VALUE_CHANGED ,设置事件执行动作为 SET TEXT VALUE FROM ARC 改变文本值。

  14. 创建GPIO开关控制组件,个性化修改样式,增加滑块控制文本事件效果。添加事件,设置事件名为 EventGPIO ,设置事件触发类型为 CLICKED ,设置事件执行动作为 SET TEXT VALUE FROM CHECKED 改变文本值,On text时为H 表示高电平, Off text 时为 L 表示低电平。

  15. 创建GPIO开关控制组件,个性化修改样式,增加返回界面效果。添加事件,设置事件名为 EventBack ,设置事件触发类型为 CLICKED ,设置事件执行动作为 CHANGE SCREEN切换屏幕,切换到的屏幕设置为 ScreenLogin 前一个屏幕,此屏幕名是自定义的,需根据实际情况设置。

    • 注意:对于前界面的登录与退出按键事件添加逻辑也是类似,可以自己通过面板窗口添加,也可以在代码中自行调用。
  16. 至此综合例程组件初步设计完成,导出 UI 文件。

  17. ui 文件结构介绍:

目录文件名说明
componentsui_comp_backlightarc.c背光弧形组件实现
ui_comp_backlightarc.h背光弧形组件头文件
ui_comp.c组件逻辑实现
ui_comp.h组件头文件
ui_comp_hook.c事件处理钩子实现
ui_comp_hook.h事件处理钩子头文件
ui_comp_panel1.c面板1组件实现
ui_comp_panel1.h面板1组件头文件
fonts---存放字体文件
imagesui_img_1072195271.c幸狐LOGO图像数据
ui_img_183911018.c返回按键图像数据
screensui_ScreenCtrl.c控制屏幕实现
ui_ScreenLogin.c登录屏幕实现
ui.c主界面初始化及事件处理逻辑
ui_events.h用户界面事件和回调声明
ui.hUI相关函数和结构体声明
ui_helpers.c辅助函数实现
ui_helpers.h辅助函数头文件

3.5 综合例程

  1. 回到搭建好的 LVGL 项目文件夹中,创建并移动到例程文件存储目录。

    luckfox@luckfox:~/lvgl/lvgl_project$ mkdir -p source/multex
    luckfox@luckfox:~/lvgl/lvgl_project$ cd source/multex
  2. SquareLine Studio 创建的综合例程组件传入 multex 目录,传输完成后文件列表如下:

  3. 基础屏幕 UI 显示界面代码实现在 ui_ScreenCtrl.cui_ScreenLogin.c 中,可以在 ui_ScreenLogin.c 中添加跳转控制界面事件回调函数和退出登录界面回调函数。

    #include "../ui.h"

    void ui_ScreenLogin_screen_init(void)
    {
    ...
    lv_obj_add_event_cb(ui_ButtonLogin, ui_event_ButtonLogin, LV_EVENT_ALL, NULL);
    lv_obj_add_event_cb(ui_ButtonExit, ui_event_ButtonExit, LV_EVENT_ALL, NULL);
    }
  4. 编辑 ui.c 文件,ui.c 主要存放控件触发事件代码,实现了控制界面跳转事件函数,登录界面退出事件函数,GPIO电平切换事件函数,按键返回上一界面事件函数。

    #include "../switch/switch.h"

    void ui_event_ButtonLogin(lv_event_t * e)
    {
    lv_event_code_t event_code = lv_event_get_code(e);
    lv_obj_t *account = ui_TextAreaACount;
    const char *account_txt = lv_textarea_get_text(account);
    lv_obj_t *passwd = ui_TextAreaPasswd;
    const char *passwd_txt = lv_textarea_get_text(passwd);

    if(event_code == LV_EVENT_CLICKED) {
    if(!strcmp(account_txt,"root")&&!strcmp(passwd_txt,"luckfox")){
    _ui_screen_change(&ui_ScreenCtrl, LV_SCR_LOAD_ANIM_MOVE_TOP, 500, 0, &ui_ScreenCtrl_screen_init);
    }
    }
    }

    void ui_event_ButtonExit(lv_event_t * e)
    {
    lv_event_code_t event_code = lv_event_get_code(e);

    if(event_code == LV_EVENT_CLICKED) {
    _ui_screen_delete(&ui_ScreenLogin);
    lv_obj_clean(ui_ScreenLogin);
    lv_refr_now(lv_disp_get_default());
    exit(1);
    }
    }

    void ui_event_SwitchGPIO(lv_event_t * e)
    {
    lv_obj_t * target = lv_event_get_target(e);

    if(lv_obj_has_state(target, LV_STATE_CHECKED)) {
    _ui_checked_set_text_value(ui_LabelLow, target, "H", "L");
    set_gpio(41,1);
    lv_obj_set_x(ui_LabelGPIO, -27);
    lv_obj_set_x(ui_LabelLow, 70);
    }
    else {
    _ui_checked_set_text_value(ui_LabelLow, target, "H", "L");
    set_gpio(41,0);
    lv_obj_set_x(ui_LabelGPIO, 25);
    lv_obj_set_x(ui_LabelLow, -66);
    }
    }

    void ui_event_ButtonClick(lv_event_t * e)
    {
    lv_event_code_t event_code = lv_event_get_code(e);

    if(event_code == LV_EVENT_CLICKED) {
    _ui_screen_change(&ui_ScreenLogin, LV_SCR_LOAD_ANIM_MOVE_BOTTOM, 500, 0, &ui_ScreenLogin_screen_init);
    }
    }
  5. 编辑 ui_comp_backlightarc.c 文件,ui_comp_backlightarc.c 主要存放子组件的行为和属性实现代码,实现了弧形滑块控件控制背光事件函数。

    void ui_event_comp_BackLightArc_BacklightArc(lv_event_t * e)
    {
    lv_event_code_t event_code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_target(e);
    lv_obj_t ** comp_BackLightArc = lv_event_get_user_data(e);

    if(event_code == LV_EVENT_CLICKED) {
    _ui_arc_set_text_value(comp_BackLightArc[UI_COMP_BACKLIGHTARC_BACKLIGHTARC_PANELBOX2_PANELBOXDISPLAY_LIGHTVALUE],target, "", "");
    int ret = set_backlight(lv_arc_get_value(target));
    if(ret < 0)
    {
    perror("set_backlight");
    }
    }
    }
  6. 修改lv_conf.h配置,让字体适配更多字号。

    #define LV_FONT_MONTSERRAT_18 1
    #define LV_FONT_MONTSERRAT_20 1
    #define LV_FONT_MONTSERRAT_24 1
    #define LV_FONT_MONTSERRAT_36 1
    #define LV_FONT_MONTSERRAT_40 1
  7. 修改 main.c 调用 ui_init 函数,以及修改 disp_drv.hor_res 水平分辨率和 disp_drv.ver_res 垂直分辨率适配屏幕。

    #include "source/multex/ui.h"

    #define DISP_BUF_SIZE (480 * 480 * 2) //Buffer size for LVGL's screen drawing. A larger buffer reduces update frequency to the display and increases memory usage.

    int main(void)
    {
    ...
    /*According to the actual screen resolution used, the following is the setting for a screen resolution of 480x480 specification*/
    disp_drv.hor_res = 480; //Set horizontal resolution
    disp_drv.ver_res = 480; //Set vertical resolution
    ...
    /*Create a Demo*/
    // lv_demo_widgets();
    ui_init();
    ...
    }

4.移植例程到开发板

4.1 交叉编译并运行按键例程

  1. 修改 Makefile。

    CC := /home/buildroot/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
    BIN = button_demo
    CSRCS +=$(LVGL_DIR)/source/button/button.c
    • 注意:CC 变量内容请根据自己的 SDK 路径填写
  2. 执行 make 指令。

    make
  3. scp 传输例程。

    scp button_demo root@192.168.9.110:/root
    • 注意:请根据自己开发板的实际 IP 地址填写
  4. 在开发板终端运行例程。

    [root@luckfox root]# ./button_demo
    • 以下为例程运行效果图:

4.2 交叉编译并运行滑动块例程

  1. 修改Makefile文件。

    CC := /opt/workspace/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
    BIN = slider_demo
    CSRCS +=$(LVGL_DIR)/source/slider/slider.c
    • 注意:CC 变量内容请根据自己的 SDK 路径填写
  2. 执行 make 指令。

    make
  3. scp 传输例程。

    scp slider_demo root@192.168.9.110:/root
    • 注意:请根据自己开发板的实际 IP 地址填写
  4. 在开发板终端运行例程。

    [root@luckfox root]# ./slider_demo
    • 以下为例程运行效果图:

4.3 交叉编译并运行开关例程

  1. 修改Makefile文件。

    CC := /opt/workspace/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
    BIN = switch_demo
    CSRCS +=$(LVGL_DIR)/source/switch/switch.c
    • 注意:CC 变量内容请根据自己的 SDK 路径填写
  2. 执行 make 指令。

    make
  3. scp 传输例程。

    scp switch_demo root@192.168.9.110:/root
    • 注意:请根据自己开发板的实际 IP 地址填写
  4. 在开发板终端运行例程。

    [root@luckfox root]# ./switch_demo
    • 以下为例程运行效果图:

4.4 交叉编译并运行综合例程

  1. 修改 Makefile。

    CC := /home/buildroot/luckfox-pico/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
    BIN = multex_demo
    SRC_DIR = $(LVGL_DIR)/source/multex
    CSRCS += $(wildcard $(SRC_DIR)/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/components/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/fonts/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/images/*.c)
    CSRCS += $(wildcard $(SRC_DIR)/screens/*.c)
    • 注意:CC变量内容请根据自己的 SDK 路径填写
  2. 执行 make 指令。

    make
  3. scp 传输例程。

    scp multex_demo root@192.168.9.110:/root
    • 注意:请根据自己开发板的实际 IP 地址填写
  4. 在开发板终端运行例程。

    [root@luckfox root]# ./multex_demo
    • 以下为例程运行效果图: