LVGL全程LittleVGL,是一个轻量化的,开源的,用于嵌入式GUI设计的图形库。并且配合LVGL模拟器,可以在电脑对界面进行编辑显示,测试通过后再移植进嵌入式设备中,实现高效的项目开发。

LVGL中文教程手册:极客笔记之LVGL教程

配置信息
芯片:ESP32-PICO-D4
PlatformIO版本:6.1.5
显示屏型号:ST7789V 240x240

一. platformIO生成项目文件

首先生成一个新项目,可以取消最后一行的勾选,自定义项目存储位置。

项目架构

Project
├─ .pio
│  ├─ build                    # 编译生成的文件
│  ├─ libdeps              # 依赖的开源库文件
│  │  ├─ integrity.dat     # 库配置文件,这个文件自动生成,不能手动修改
├─ .vscode                 # vscode配置文件
├─ lib                              # 私有库文件,里面的文件会被编译为静态库链接到项目中
├─ include                     # 头文件放置文件夹
├─ src                         # 源文件放置文件夹
├─ test                        # 测试文件放置文件夹
├─ .gitignore              
└─ platformio.ini               # 项目参数配置文件,libdeps中的库文件就在这里进行配置

修改platformio.ini文件
添加烧录串口号和串口监视速率,这里的串口速率只是配置platformio monitor接收数据的速率,并没有配置芯片的串口发送速率。

monitor_speed = 115200
upload_port = COM11

添加开源库
在下方的界面中搜索相应的库文件,并选择想要的版本添加到文件中。

注意:各个项目的库文件虽然都可以在installed中找到,但是不同项目的库文件不是共享的。也就是说,即使两个项目用了同一个版本的同一个库,installed中会有两个库文件,分别存储在两个项目的libdeps中。


选择添加之后,platformio.ini文件中就会自动生成依赖库代码。

二. 修改配置文件

1.lvgl
复制lv_conf_template.h到同一路径下,并改名为lvgl_conf.h

内容解注释

启用自定义时钟
注意:没有一步的话会导致屏幕停留在第一帧的界面

2.TFT_eSPI
修改User_Setup_Select.h文件,选择屏幕的型号。
这里只能解注释一个 #include ,所以需要把 #include<User_Setup.h> 给注释掉。

修改Setup24_ST7789.h文件,这里需要根据自己屏幕和芯片的连接引脚来确定引脚号。

三. 创建lvgl和屏幕的连接

可以参考lvgl/examples/porting/lv_port_disp_template.c文件,但是里面没有添加屏幕初始化的内容,所以需要结合TFT的demo来编写代码,以下是我用的源码。

注意:需要用cpp文件来编写,因为TFT_eSPI是基于cpp的。

/**
 * @file display.cpp
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "display.h"
#include <stdbool.h>
#include <TFT_eSPI.h>

/*********************
 *      DEFINES
 *********************/
#ifndef MY_DISP_HOR_RES
    #define MY_DISP_HOR_RES    (240)
#endif

#ifndef MY_DISP_VER_RES
    #define MY_DISP_VER_RES    (240)
#endif

/**********************
 *  STATIC VARIABLES
 **********************/
TFT_eSPI tft = TFT_eSPI();

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    tft.startWrite();
    tft.setAddrWindow(area->x1, area->y1, w, h);
    tft.pushColors(&color_p->full, w * h, true);
    tft.endWrite();

    lv_disp_flush_ready(disp_drv);
}

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void Display::init(void)
{
    /* Set Backlight mode */
    ledcSetup(LCD_BL_PWM_CHANNEL, 5000, 8);
    ledcAttachPin(LCD_BL_PIN, LCD_BL_PWM_CHANNEL);

    /* Init lvgl */
    lv_init();

    /* Init display device */
    tft.begin(); 
    tft.setRotation(4); /* mirror */

    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];  /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/
    static lv_disp_drv_t disp_drv;      /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);        /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

void Display::routine(void)
{
    lv_task_handler();
}

void Display::setBackLight(float duty)
{
    duty = constrain(duty, 0, 1);
    duty = 1 - duty;
    ledcWrite(LCD_BL_PWM_CHANNEL, (int)(duty * 255));
}
#ifndef DISPLAY_H
#define DISPLAY_H

#include <lvgl.h>

#define LCD_BL_PIN 5
#define LCD_BL_PWM_CHANNEL 0


class Display
{
private:


public:
    void init();
    void routine();
    void setBackLight(float);
};

#endif

四. 设计UI

这里使用的是官方提供的demo,lvgl/examples 中有很多官方提供的demo可以进行参考。

void lv_demo(void)
{
    lv_obj_t * obj = lv_obj_create(lv_scr_act());
    lv_obj_set_style_bg_color(obj, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, 0);

    lv_obj_align(obj, LV_ALIGN_LEFT_MID, 10, 0);

    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, obj);
    lv_anim_set_values(&a, 10, 50);
    lv_anim_set_time(&a, 1000);
    lv_anim_set_playback_delay(&a, 100);
    lv_anim_set_playback_time(&a, 300);
    lv_anim_set_repeat_delay(&a, 500);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);

    lv_anim_set_exec_cb(&a, anim_size_cb);
    lv_anim_start(&a);
    lv_anim_set_exec_cb(&a, anim_x_cb);
    lv_anim_set_values(&a, 10, 240);
    lv_anim_start(&a);
}

五. 修改main.cpp

/**
 * @file main.cpp
 * @author jozenlee
 * @brief
 * @version 0.1
 * @date 2022-12-15
 *
 * @copyright Copyright (c) 2022
 *
 */

#include <Arduino.h>
#include "lv_demo.h"

#include "display.h"
Display screen;

// lv_group_t * group;
void setup() {
    /* Init Serial */
    Serial.begin(115200);

    /* Init lvgl display port */
    screen.init();
    screen.setBackLight(0.2);

    /* Inflate GUI objects */
    lv_demo();
}

void loop() {
    // run this as often as possible
    screen.routine();

    /* Serial test */
    Serial.println("hello");
}

效果