跳到主要内容

第 08 节 移植 LVGL

在前面一章节当中我们用 ESP_Display_Panel 库驱动屏幕进行显示,在这一节我们也同样用这个库来驱动屏幕显示和触摸面板,并且移植 LVGL 库来显示官方例程。

硬件连接

为了将对应的代码上传到 ESP32-S3,需要用 Type-C 转 Type-A 的线将 ESP32-S3-Touch-LCD-7 的 USB 口接到电脑的 USB 口:

ESP32-S3-Touch-LCD-7 I2C demo 2

示例代码

请先从以下地址下载例程包:ESP32-S3-Touch-LCD-7 示例程序 (如果先前已经下载,直接打开对应文件夹即可) 将例程压缩包解压之后,打开对应的 09_lvgl_Porting 文件夹:

ESP32-S3-Touch-LCD-7 LVGL demo 1

双击打开 09_lvgl_Porting.ino:

ESP32-S3-Touch-LCD-7 LVGL demo 2

前面我们已经连接好了下载程序的 USB 口,在打开程序后,右上角选择好串口和开发板型号 Waveshare ESP32-S3-Touch-LCD-7。

ESP32-S3-Touch-LCD-7 LVGL demo 3

设置对应的开发板参数:

ESP32-S3-Touch-LCD-7 LVGL demo 4

然后点击箭头按钮进行程序上传,等待上传完成即可。

ESP32-S3-Touch-LCD-7 LVGL demo 5

运行结果

重新给屏幕上电,可以看到屏幕开始显示 lvgl 的例程,对于带有触摸面板的版本,滑动屏幕还能进行触摸的测试。

ESP32-S3-Touch-LCD-7 LVGL demo 6

代码回顾

1. 显示和触摸面板的驱动

在本节的例程当中,我们将用到 esp_panel_board_custom_conf.h 文件来配置屏幕和触摸,这个文件就放在例程当前的目录的当中,系统将会自动检测到这个文件并用其进行配置。关于这个文件的配置相关信息,可以查看 官方文档 进行了解。

下面我们就来讲解下如何配置这个文件的:

ESP32-S3-Touch-LCD-7 LVGL demo 7

开启自定义 LCD 面板

    #define ESP_PANEL_BOARD_DEFAULT_USE_CUSTOM  (1)

首先要将 ESP_PANEL_BOARD_DEFAULT_USE_CUSTOM 宏定义由 0 改为 1,表示打开自定义配置,将会启用 #if ESP_PANEL_BOARD_DEFAULT_USE_CUSTOM 下的配置参数。

    #define ESP_PANEL_BOARD_NAME                "Waveshare:ESP32-S3-Touch-LCD-7"
#define ESP_PANEL_BOARD_WIDTH (800) // Panel width (horizontal, in pixels)
#define ESP_PANEL_BOARD_HEIGHT (480) // Panel height (vertical, in pixels)

自定义板子的名字和定义屏幕的分辨率。

设置 LCD 的各项参数

    #define ESP_PANEL_BOARD_USE_LCD             (1)

ESP_PANEL_BOARD_USE_LCD 的宏定义从 0 修改成 1,表示打开 LCD panel,将会启用 #if ESP_PANEL_BOARD_USE_LCD 下面的配置参数。

    #define ESP_PANEL_BOARD_LCD_CONTROLLER      ST7262
#define ESP_PANEL_BOARD_LCD_BUS_TYPE (ESP_PANEL_BUS_TYPE_RGB)

定义 LCD 控制器 IC 和通信方式,选择 RGB bus 的通信方式。

    #if ESP_PANEL_BOARD_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB
#define ESP_PANEL_BOARD_LCD_RGB_USE_CONTROL_PANEL (0) // 0/1. Typically set to 1

#define ESP_PANEL_BOARD_LCD_RGB_CLK_HZ (16 * 1000 * 1000)
...
#define ESP_PANEL_BOARD_LCD_RGB_IO_DATA15 (40)

#endif

ESP_PANEL_BOARD_LCD_RGB_USE_CONTROL_PANEL 设置为 0,表示使用的是没有三线 SPI 的 RGB 接口,接着就是按照 LCD 的参数进行各项的配置,跟之前的 RGB 条纹例程是一样的。

设置触摸面板的参数

    #define ESP_OPEN_TOUCH 1

设置触摸开启的宏定义,用于选择是否打开触摸功能,值为 0 对应 关闭触摸,值为 1 对应 打开触摸,可依购买型号选择。

    #define ESP_PANEL_BOARD_USE_TOUCH               (ESP_OPEN_TOUCH)

在开头我们已经将 ESP_OPEN_TOUCH 设置成了 1,因此这里将会开启触摸 #if ESP_PANEL_BOARD_USE_TOUCH 以下的配置。如果是购买的不带触摸的版本,需将 ESP_OPEN_TOUCH 设置为 0,则不启用以下的触摸面板参数。

    #define ESP_PANEL_BOARD_TOUCH_CONTROLLER        GT911
#define ESP_PANEL_BOARD_TOUCH_BUS_TYPE (ESP_PANEL_BUS_TYPE_I2C)

定义触摸芯片为 GT911 以及 通信接口为 I2C bus。

    #define ESP_PANEL_BOARD_TOUCH_I2C_IO_SCL            (9)
#define ESP_PANEL_BOARD_TOUCH_I2C_IO_SDA (8)
#define ESP_PANEL_BOARD_TOUCH_INT_IO (4)

这里分别设置触摸的 SCL, SDA, 和 中断引脚。

设置背光参数

    #define ESP_PANEL_BOARD_USE_BACKLIGHT           (1)

ESP_PANEL_BOARD_USE_BACKLIGHT 设置为 1,表示开启背光的控制,将会使能 #if ESP_PANEL_BOARD_USE_BACKLIGHT 以下的配置。

    #define ESP_PANEL_BOARD_BACKLIGHT_TYPE          (ESP_PANEL_BACKLIGHT_TYPE_SWITCH_EXPANDER)

#if (ESP_PANEL_BOARD_BACKLIGHT_TYPE == ESP_PANEL_BACKLIGHT_TYPE_SWITCH_GPIO) || \
(ESP_PANEL_BOARD_BACKLIGHT_TYPE == ESP_PANEL_BACKLIGHT_TYPE_SWITCH_EXPANDER) || \
(ESP_PANEL_BOARD_BACKLIGHT_TYPE == ESP_PANEL_BACKLIGHT_TYPE_PWM_LEDC)

#define ESP_PANEL_BOARD_BACKLIGHT_IO (2) // Output GPIO pin number
#define ESP_PANEL_BOARD_BACKLIGHT_ON_LEVEL (1) // Active level, 0: low, 1: high

#endif

这里是将背光类型设置为 IO 扩展芯片进行控制,并且设置控制 IO 引脚为 EXIO2。

    #define ESP_PANEL_BOARD_BACKLIGHT_IDLE_OFF      (0)

设置初始化后开启背光。

设置 IO 扩展芯片的参数

    #define ESP_PANEL_BOARD_USE_EXPANDER            (1)

ESP_PANEL_BOARD_USE_EXPANDER 宏定义设置为 1,表示开始 IO 扩展芯片,将会使能 #if ESP_PANEL_BOARD_USE_EXPANDER 下的各项参数。

    #define ESP_PANEL_BOARD_EXPANDER_CHIP           CH422G

IO 扩展芯片名字设置成 CH422G

    #define ESP_PANEL_BOARD_EXPANDER_I2C_CLK_HZ         (400 * 1000)
// Typically set to 400K
#define ESP_PANEL_BOARD_EXPANDER_I2C_SCL_PULLUP (1) // 0/1. Typically set to 1
#define ESP_PANEL_BOARD_EXPANDER_I2C_SDA_PULLUP (1) // 0/1. Typically set to 1
#define ESP_PANEL_BOARD_EXPANDER_I2C_IO_SCL (9)
#define ESP_PANEL_BOARD_EXPANDER_I2C_IO_SDA (8)

#define ESP_PANEL_BOARD_EXPANDER_I2C_ADDRESS (0x20)

设置 IO 扩展芯片 I2C 通信的相关参数:时钟频率、SDA 和 SCL 引脚等。

配置完以上这些参数之后,即可在程序当中初始化屏幕并进行显示。

2. 移植配置 LVGL

头文件部分

    #include <Arduino.h>
#include <esp_display_panel.hpp>

#include <lvgl.h>
#include "lvgl_v8_port.h"
#include <demos/lv_demos.h>

using namespace esp_panel::drivers;
using namespace esp_panel::board;
  • 这里将引入程序要用到的各种库,包括 ESP Display Panel 官方库的核心头文件(esp_display_panel.hpp)、LVGL 主库(lvgl.h),以及 LVGL v8 在 ESP32 上的移植层 (lvgl_v8_port.h)等。

  • using namespace :为了简化后面写法,不用 esp_panel::board::Board ,直接写 Board

    lvgl_v8_port.h 库是什么?

    这个库是 ESP32 / ESP-IDF / Arduino 环境中为 LVGL v8 做的移植层,负责把 LVGL 和 ESP32-S3 硬件连接起来,例如:

    • 显示屏驱动(RGB / SPI / QSPI)
    • 触摸屏驱动(I2C / SPI)
    • LVGL 的定时 tick(lv_tick_inc)
    • LVGL 渲染任务(lv_timer_handler)
    • DMA / 双缓冲区配置(frame buffer)
    • malloc 内存区域适配

    如果没有这个文件,你无法直接在 ESP32-S3 上运行 LVGL。

setup() 函数

    Serial.begin(115200);
Serial.println("Initializing board");

初始化串口,供调试输出使用。

    Board *board = new Board();
board->init();

初始化 Board ,包括 LCD,Touch,和背光等,我们前面已经配置好了 esp_panel_board_custom_conf.h 文件,调用 init() 函数就可以按照之前的配置进行初始化。

    #if LVGL_PORT_AVOID_TEARING_MODE
auto lcd = board->getLCD();
// When avoid tearing function is enabled, the frame buffer number should be set in the board driver
lcd->configFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM);

这段代码只在 RGB 屏幕下有效,RGB 屏带宽大,因此需要更多优化。在 lvgl_v8_port.h 中设置了**LVGL_PORT_DISP_BUFFER_NUM** 的值是 2,所以这里是设置使用 2 个 frame buffer,避免显示撕裂。

    #if ESP_PANEL_DRIVERS_BUS_ENABLE_RGB && CONFIG_IDF_TARGET_ESP32S3
auto lcd_bus = lcd->getBus();

if (lcd_bus->getBasicAttributes().type == ESP_PANEL_BUS_TYPE_RGB) {
static_cast<BusRGB *>(lcd_bus)->configRGB_BounceBufferSize(lcd->getFrameWidth() * 10);
}
#endif

这里也是跟之前的例程一样,从 lcd 取得 Bus 指针并转换成 BusRGB* ,然后调用配置函数设置“bounce buffer”大小。这是 ESP32-S3 特有的 RGB 加速方法。

    assert(board->begin());

真正启动屏幕和触摸,初始化 LCD 驱动芯片、Touch 芯片,配置背光 PWM 等。

    Serial.println("Initializing LVGL");
lvgl_port_init(board->getLCD(), board->getTouch());

这里将开始配置 LVGL,初始化 LVGL 的 display driver,配置 touch 输入设备,初始化 LVGL tick,创建 LVGL 循环任务持续运行 lv_timer_handler() 等,不需要手写 flush_cb、touch_read_cb,库会自动处理。

    Serial.println("Creating UI");
lvgl_port_lock(-1);

LVGL 不是线程安全的,lvgl_port_lock() 是给 LVGL 上锁的函数,用来保证“同一时间只有一个任务在操作 LVGL”。设置为-1 表示一直等,直到拿到锁。

    lv_obj_t *label_1 = lv_label_create(lv_scr_act()); //在整块屏幕上面创建一个标签 1
lv_label_set_text(label_1, "Hello World!"); //标签的文本设置成 Hello World!
lv_obj_set_style_text_font(label_1, &lv_font_montserrat_30, 0); //设置字体
lv_obj_align(label_1, LV_ALIGN_CENTER, 0, -20); //设置标签位置

这一步是在父对象上面创建一个 Hello World 文本。

    lv_obj_t *label_2 = lv_label_create(lv_scr_act());
lv_label_set_text_fmt(
label_2, "ESP32_Display_Panel (%d.%d.%d)",
ESP_PANEL_VERSION_MAJOR, ESP_PANEL_VERSION_MINOR, ESP_PANEL_VERSION_PATCH
);
lv_obj_set_style_text_font(label_2, &lv_font_montserrat_16, 0);
lv_obj_align_to(label_2, label_1, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
lv_obj_t *label_3 = lv_label_create(lv_scr_act());
lv_label_set_text_fmt(label_3, "LVGL (%d.%d.%d)", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, LVGL_VERSION_PATCH);
lv_obj_set_style_text_font(label_3, &lv_font_montserrat_16, 0);
lv_obj_align_to(label_3, label_2, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);

这里跟上面的创建步骤基本一致,用于显示 ESP32_Display_Panel 库版本号和 LVGL 版本号。

   lv_demo_widgets();

这个 demo 会自动创建按钮、slider、列表等界面。

    lvgl_port_unlock();

解锁,结束 UI 构建。