LVGL 图形界面开发
LVGL (Light and Versatile Graphics Library) 是一个开源嵌入式图形库,用于在资源受限的微控制器上创建图形用户界面(GUI)。它提供了丰富的控件(如按钮、标签、滑块和图表),使开发者可以高效地构建交互式图形界面。该库使用 C 语言编写,具有高可移植性,并针对低内存占用和高性能进行了优化。凭借高效性能和活跃的社区支持,LVGL 已成为 ESP32 平台上图形界面开发的热门选择之一。
LVGL 通过其硬件抽象层(Hardware Abstraction Layer, HAL),将图形处理逻辑与具体的硬件操作分离。这意味着 LVGL 库本身不直接控制任何特定硬件(如显示驱动芯片或触摸控制器)。

LVGL 与硬件的连接通过注册驱动函数完成。这些驱动函数构成了 LVGL 与具体硬件通信的桥梁。LVGL 在需要时(如刷新屏幕或读取输入)调用这些预先注册的函数,实现对硬件的间接控制。这种设计赋予了 LVGL 良好的可移植性,使其能够适配多样化的硬件平台。
主要包括以下三个部分:
-
显示驱动接口 (Display Driver Interface)
此接口用于将 LVGL 渲染的图像数据发送到显示设备。需要提供一个显示驱动函数,当 LVGL 将部分界面渲染到内部缓冲区后,便会调用此函数。该函数负责将此像素数据块通过 SPI 或其他总线协议,传输给显示控制器。
-
输入设备驱动接口 (Input Device Driver Interface)
此接口用于向 LVGL 报告来自触摸屏、物理按键、编码器等输入设备的状态。开发者需要提供一个输入设备驱动函数,LVGL 会周期性地调用该函数。此函数需要读取硬件状态,并将其转换为 LVGL 定义的格式,例如触摸坐标或按键事件。
-
系统节拍接口 (System Tick Interface)
LVGL 的动画、事件处理等内部任务依赖于一个稳定的时间基准。开发者需要提供一个函数,该函数返回自系统启动以来经过的毫秒数。LVGL 通过调用此函数来跟踪时间流逝,以管理所有与时间相关的任务。
本文示例基于 微雪 ESP32-S3 1.69 英寸触摸液晶开发板 (ESP32-S3-Touch-LCD-1.69)
微雪 ESP32-S3-Touch-LCD-1.69 板载 1.69 英寸电容触摸 LCD 屏、六轴传感器(三轴加速度计和三轴陀螺仪)、RTC 等外设。屏幕采用 ST7789V2 显示驱动芯片和 CST816T 电容触摸芯片方案。
配置 LVGL 环境
在 ESP32 上使用 LVGL,通常涉及两个主要步骤:首先安装 LVGL 核心库及所需的硬件驱动库,然后配置 LVGL。
安装库
本教程的示例基于 微雪 ESP32-S3-Touch-LCD-1.69 开发板 ,该开发板的显示驱动芯片为 ST7789V2,触摸芯片为 CST816T。示例代码使用 GFX Library for Arduino
库驱动显示,并使用 Arduino_DriveBus
库驱动触摸芯片。
可从 官方链接 下载 ESP32-S3-Touch-LCD-1.69 开发板的示例程序包。包内的 Arduino\libraries
目录已包含本教程所需的全部库文件。
库或文件名称 | 说明 | 版本 | 安装方式 |
---|---|---|---|
Arduino_DriveBus | CST816 触摸芯片驱动库 | v1.0.1 | 手动安装 |
GFX Library for Arduino | ST7789 显示驱动图形库 | v1.4.9 | 通过库管理器或手动安装 |
lvgl | LVGL 图形库 | v8.4.0 | 通过库管理器或手动安装 |
Mylibrary/pin_config.h | 开发板引脚宏定义 | —— | 手动安装 |
lv_conf.h | LVGL 配置文件 | —— | 手动安装 |
LVGL 及其驱动库的版本之间存在较强的依赖关系。例如,为 LVGL v8 编写的驱动可能不兼容 LVGL v9。为确保教程示例能够稳定复现,推荐使用上表列出的特定版本。混合使用不同版本的库可能导致编译失败或运行时异常。
安装步骤:
-
解压已下载的 示例程序包。
-
将其
Arduino\libraries
目录下的所有文件夹(Arduino_DriveBus、GFX_Library_for_Arduino 等)复制到 Arduino 的库文件夹中。信息Arduino 库文件夹的路径通常是:
c:\Users\<用户名>\Documents\Arduino\libraries
。也可以在 Arduino IDE 中通过 文件 > 首选项,查看“项目文件夹位置”来定位。库文件夹就是此路径下的
libraries
文件夹。 -
其他安装方式请参考:Arduino 库管理教程。
修改 LVGL 配置文件
安装后还需要配置 LVGL。LVGL 通过一个名为 lv_conf.h
的文件进行配置。LVGL 的功能开关和关键参数均通过这个配置文件集中管理。
一般需要按照以下步骤操作:
- 前往已安装 Arduino 库的目录
- 进入已安装的
lvgl
库目录,将lv_conf_template.h
复制一份并重命名为lv_conf.h
。然后将此文件移动到 Arduino 的库文件夹根目录(例如.../Arduino/libraries
),使其与lvgl
文件夹同级。 - 打开
lv_conf.h
并将第一个#if 0
更改为#if 1
以启用文件内容 - 在
LV_COLOR_DEPTH
中设置显示器的颜色深度。
对于本文示例,无需手动执行以上配置步骤。
为简化操作,官方示例程序包中已提供一个针对 ESP32-S3-Touch-LCD-1.69 开发板优化好的 lv_conf.h
文件。只需将该文件从示例包中复制到 Arduino 库文件夹的根目录下即可。
对于特定开发板,直接使用硬件厂商提供的预设文件是高效且不易出错的适配方法。
示例 1:基础显示 (Hello World)
本示例展示了在屏幕上显示一行文本 "Hello World!" 的最小 LVGL 应用,涵盖了 LVGL 的基本初始化流程。
代码
#include <lvgl.h>
#include "Arduino_GFX_Library.h"
#include "lv_conf.h"
#include "pin_config.h"
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
static const uint16_t screenWidth = 240;
static const uint16_t screenHeight = 280;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];
/* LCD - 显示屏硬件接口定义 */
Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, LCD_SCK, LCD_MOSI);
Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST /* RST */,
0 /* rotation */, true /* IPS */, LCD_WIDTH, LCD_HEIGHT, 0, 20, 0, 0);
/* LVGL 显示刷新回调 */
void my_disp_flush(lv_disp_drv_t *disp, 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);
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
lv_disp_flush_ready(disp);
}
/* LVGL Tick 增加回调 */
void example_increase_lvgl_tick(void *arg) {
/* 告诉 LVGL 经过了多少毫秒 */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
void setup() {
// 1. 硬件初始化 (Hardware Initialization)
// 建议将所有与物理硬件直接相关的初始化放在一起。
// 初始化屏幕显示
gfx->begin();
pinMode(LCD_BL, OUTPUT);
digitalWrite(LCD_BL, HIGH);
// 2. LVGL 核心与心跳初始化 (LVGL Core and Tick Initialization)
// 这是 LVGL 运行的基础,应尽早完成。
lv_init();
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
// 3. LVGL 显示驱动初始化 (LVGL Display Driver Initialization)
// 将 LVGL 与硬件驱动连接
// --- 初始化显示驱动 ---
lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
// 4. UI 创建与样式应用 (UI Creation and Style Application)
// 这是应用层,在所有底层都准备好后进行。
// --- 样式定义优化 ---
// 将所有相关的样式属性(字体、颜色等)都定义在同一个 style 对象中,更具模块化。
static lv_style_t style_large_white_font;
lv_style_init(&style_large_white_font);
lv_style_set_text_font(&style_large_white_font, &lv_font_montserrat_24);
lv_style_set_text_color(&style_large_white_font, lv_color_hex(0xffffff));
// 设置屏幕背景色
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x003a57), LV_PART_MAIN);
// 创建 Label
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello World!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
// 将包含所有属性的样式一次性应用到 label 上
lv_obj_add_style(label, &style_large_white_font, 0);
}
void loop() {
lv_timer_handler(); /* let the GUI do its work */
delay(5);
}
代码解析
-
库引入与全局定义
#include <lvgl.h>
: 引入 LVGL 核心库。#include "Arduino_GFX_Library.h"
: 引入显示屏驱动库。#include "pin_config.h"
: 引入开发板(ESP32-S3-Touch-LCD-1.69)的引脚定义文件。draw_buf
和buf
: 定义 LVGL 的绘图缓冲区。LVGL 先将图形内容渲染到此内存区域,然后一次性“刷”到屏幕上,以提高效率。缓冲区大小被设为屏幕像素的十分之一,这是一个在内存占用和性能之间的常见折衷。
-
硬件接口实例化
Arduino_DataBus *bus = ...
: 创建一个 SPI 总线实例,用于与 LCD 显示屏通信。参数为 SPI 相关的引脚。Arduino_GFX *gfx = ...
: 创建一个 ST7789 显示驱动的实例,并将其与上面创建的 SPI 总线关联。它封装了底层屏幕操作的指令。
-
LVGL 回调函数 (Callbacks)
my_disp_flush()
: 这是连接 LVGL 与硬件显示驱动的核心桥梁函数。当 LVGL 完成一小块区域的渲染后,会调用此函数,并将渲染好的像素数据 (color_p
) 传递过来。函数内部通过gfx->draw16bitRGBBitmap()
将这块数据发送到屏幕的指定位置。最后,lv_disp_flush_ready()
通知 LVGL 数据已发送,可以继续渲染下一部分。example_increase_lvgl_tick()
: 这是 LVGL 的系统节拍(心跳)函数。它通过lv_tick_inc()
告诉 LVGL 时间已经流逝了多少毫秒。这个时间基准对于动画、光标闪烁等时间相关的任务至关重要。
-
setup()
函数setup()
函数按照逻辑顺序完成了所有初始化工作:- 硬件初始化: 调用
gfx->begin()
初始化显示控制器,并点亮屏幕背光。 - LVGL 核心与心跳初始化: 调用
lv_init()
初始化 LVGL 库本身。然后创建一个高精度的esp_timer
,使其每隔EXAMPLE_LVGL_TICK_PERIOD_MS
(2 毫秒)就调用一次example_increase_lvgl_tick()
,为 LVGL 提供稳定心跳。 - LVGL 显示驱动初始化: 这是将
my_disp_flush
回调函数注册到 LVGL 的过程。它告诉 LVGL:“当需要刷新屏幕时,请调用my_disp_flush
函数”。同时还配置了屏幕分辨率和绘图缓冲区。 - UI 创建: 在所有底层都准备好后,开始构建用户界面。代码首先创建并配置了一个样式(24 号字体,白色),然后创建了一个标签(
lv_label
),设置其文本为 "Hello World!",将其居中对齐,并应用了预设的样式。
- 硬件初始化: 调用
-
loop()
函数lv_timer_handler()
: 这是 LVGL 的主处理函数,必须在loop
中被反复调用。它负责处理所有待办任务,如重绘屏幕、执行动画、响应事件等。delay(5)
: 加入短暂延时,可避免loop
循环占用全部 CPU 资源,为其他后台任务(如 Wi-Fi 通信)预留处理时间。
示例 2:交互示例(可点击的按钮)
本示例在 "Hello World" 的基础上,增加了触摸输入功能,并创建了一个可点击的按钮。每次点击按钮,按钮上的文本都会更新。
代码
#include <lvgl.h>
#include "Arduino_GFX_Library.h"
#include "Arduino_DriveBus_Library.h"
#include "lv_conf.h"
#include "pin_config.h"
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
static const uint16_t screenWidth = 240;
static const uint16_t screenHeight = 280;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];
/* LCD - 显示屏硬件接口定义 */
Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, LCD_SCK, LCD_MOSI);
Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST /* RST */,
0 /* rotation */, true /* IPS */, LCD_WIDTH, LCD_HEIGHT, 0, 20, 0, 0);
/* Touch - 触摸屏硬件接口定义 */
std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus =
std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire);
void Arduino_IIC_Touch_Interrupt(void); // 函数前向声明
std::unique_ptr<Arduino_IIC> CST816T(new Arduino_CST816x(IIC_Bus, CST816T_DEVICE_ADDRESS,
TP_RST, TP_INT, Arduino_IIC_Touch_Interrupt));
// 触摸中断服务函数
void Arduino_IIC_Touch_Interrupt(void) {
CST816T->IIC_Interrupt_Flag = true;
}
/* LVGL 显示刷新回调 */
void my_disp_flush(lv_disp_drv_t *disp, 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);
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
lv_disp_flush_ready(disp);
}
/* LVGL 触摸读取回调 */
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
// 从触摸芯片读取坐标
int32_t touchX = CST816T->IIC_Read_Device_Value(CST816T->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X);
int32_t touchY = CST816T->IIC_Read_Device_Value(CST816T->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y);
// 检查中断标志位来判断是否有新的触摸事件
if (CST816T->IIC_Interrupt_Flag == true) {
CST816T->IIC_Interrupt_Flag = false; // 清除标志位
data->state = LV_INDEV_STATE_PR; // 状态:按下
/* 检查坐标有效性后设定坐标 */
if (touchX >= 0 && touchY >= 0) {
data->point.x = touchX;
data->point.y = touchY;
}
} else {
data->state = LV_INDEV_STATE_REL; // 状态:释放
}
}
/* LVGL Tick 增加回调 */
void example_increase_lvgl_tick(void *arg) {
/* 告诉 LVGL 经过了多少毫秒 */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
/* --- UI 界面代码 --- */
// 按钮事件回调函数
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++;
/* 获取按钮的第一个子对象(即标签),并修改其文本 */
lv_obj_t *label = lv_obj_get_child(btn, 0);
lv_label_set_text_fmt(label, "Clicked: %d", cnt);
}
}
/*创建一个带标签的按钮并响应点击事件 (UI 示例)*/
void lv_example_get_started_1(void) {
lv_obj_t *btn = lv_btn_create(lv_scr_act()); /* 在当前屏幕上创建按钮 */
lv_obj_set_pos(btn, 70, 100); /* 设置位置 */
lv_obj_set_size(btn, 100, 50); /* 设置尺寸 */
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); /* 为按钮分配事件回调 */
lv_obj_t *label = lv_label_create(btn); /* 在按钮上创建标签 */
lv_label_set_text(label, "Click Me"); /* 设置标签文本 */
lv_obj_center(label); /* 标签居中 */
}
void setup() {
// 1. 硬件初始化 (Hardware Initialization)
// 首先完成所有与物理硬件(屏幕、触摸、背光等)直接通信的初始化。
// 初始化屏幕显示
gfx->begin();
pinMode(LCD_BL, OUTPUT);
digitalWrite(LCD_BL, HIGH); // 打开背光
// 初始化触摸控制器
while (CST816T->begin() == false) {
delay(2000); // 如果失败,等待一段时间后重试
}
CST816T->IIC_Write_Device_State(CST816T->Arduino_IIC_Touch::Device::TOUCH_DEVICE_INTERRUPT_MODE,
CST816T->Arduino_IIC_Touch::Device_Mode::TOUCH_DEVICE_INTERRUPT_PERIODIC);
// 2. LVGL 核心与心跳初始化 (LVGL Core and Tick Initialization)
lv_init();
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
// 3. LVGL 驱动注册 (LVGL Driver Registration)
// 将 LVGL 的逻辑操作与硬件的回调函数连接
// --- 初始化显示驱动 ---
lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
// --- 初始化输入设备(触摸)驱动 ---
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER; // 类型为指针/触摸
indev_drv.read_cb = my_touchpad_read; // 注册读取回调
lv_indev_drv_register(&indev_drv);
// 4. UI 创建与应用层初始化 (UI Creation & Application Initialization)
// 在所有底层都准备就绪后,开始创建图形界面。
// 设置屏幕背景色
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_hex(0x003a57), LV_PART_MAIN);
// 调用 UI 创建函数
lv_example_get_started_1();
}
void loop() {
lv_timer_handler(); /* 让 LVGL GUI 处理其任务(如动画、事件等) */
delay(5);
}
代码解析
此示例在示例 1 的基础上增加了触摸交互功能,其核心区别在于触摸驱动的引入和事件处理。
-
触摸硬件接口实例化
std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus = ...
: 创建一个 I2C 总线实例,用于与触摸控制器 CST816T 通信。std::unique_ptr<Arduino_IIC> CST816T = ...
: 创建一个 CST816T 触摸芯片的驱动实例,并将其与 I2C 总线关联。Arduino_IIC_Touch_Interrupt
: 这是一个触摸中断回调函数。当触摸芯片检测到触摸动作时,会通过中断信号触发此函数,函数内部将IIC_Interrupt_Flag
标志位置为true
。
-
LVGL 触摸读取回调
my_touchpad_read()
: 此函数是连接 LVGL 与硬件触摸驱动的核心桥梁,LVGL 会周期性地调用该函数以查询输入设备的状态。- 函数逻辑:它首先检查
IIC_Interrupt_Flag
标志位。如果为true
(表示有新的触摸事件),它就从触摸芯片读取当前的 X/Y 坐标,并将状态data->state
设置为LV_INDEV_STATE_PR
(按下);否则,将状态设置为LV_INDEV_STATE_REL
(释放)。LVGL 根据这个函数返回的状态和坐标来判断用户的触摸操作。
-
UI 界面代码
btn_event_cb()
: 这是一个事件回调函数,用于响应按钮的交互事件。- 当按钮被点击 (
LV_EVENT_CLICKED
) 时,此函数被触发。 - 函数内部,一个静态计数器
cnt
会自增。 lv_obj_get_child(btn, 0)
获取按钮的子对象(即按钮上的标签),然后lv_label_set_text_fmt
更新标签的文本,显示新的计数值。
- 当按钮被点击 (
lv_example_get_started_1()
: 这是一个封装好的 UI 创建函数。lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL)
: 这是关键的一步,它将btn
对象与btn_event_cb
回调函数绑定起来。从此,btn
上发生的所有事件 (LV_EVENT_ALL
) 都会触发btn_event_cb
函数。
-
setup()
函数- 1. 硬件初始化: 新增了对触摸控制器
CST816T
的初始化。 - 2. LVGL 驱动注册: 对比示例 1,在注册显示驱动之后,新增了输入设备驱动的初始化和注册。
lv_indev_drv_init()
: 初始化输入设备驱动结构体。indev_drv.type = LV_INDEV_TYPE_POINTER
: 设置设备类型为指针设备(如触摸屏、鼠标)。indev_drv.read_cb = my_touchpad_read
: 将my_touchpad_read
回调函数注册到 LVGL。这告诉 LVGL:“当需要获取触摸状态时,请调用my_touchpad_read
函数”。
- 3. UI 创建: 调用
lv_example_get_started_1()
来创建交互式按钮界面。
- 1. 硬件初始化: 新增了对触摸控制器
示例 3:最小功能模板
本示例为 ESP32-S3-Touch-LCD-1.69 开发板提供了一个集成了显示和触摸功能的最小化 LVGL 项目模板。它集成了显示与触摸所需的所有底层代码,构成一个稳固的开发起点,可用于快速验证 LVGL 官方示例或实现自定义 UI 设计。
代码
#include <lvgl.h>
#include "Arduino_GFX_Library.h"
#include "Arduino_DriveBus_Library.h"
#include "lv_conf.h"
#include "pin_config.h"
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
static const uint16_t screenWidth = 240;
static const uint16_t screenHeight = 280;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * screenHeight / 10];
/* LCD - 显示屏硬件接口定义 */
Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, LCD_SCK, LCD_MOSI);
Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST /* RST */,
0 /* rotation */, true /* IPS */, LCD_WIDTH, LCD_HEIGHT, 0, 20, 0, 0);
/* Touch - 触摸屏硬件接口定义 */
std::shared_ptr<Arduino_IIC_DriveBus> IIC_Bus =
std::make_shared<Arduino_HWIIC>(IIC_SDA, IIC_SCL, &Wire);
void Arduino_IIC_Touch_Interrupt(void); // 函数前向声明
std::unique_ptr<Arduino_IIC> CST816T(new Arduino_CST816x(IIC_Bus, CST816T_DEVICE_ADDRESS,
TP_RST, TP_INT, Arduino_IIC_Touch_Interrupt));
// 触摸中断服务函数
void Arduino_IIC_Touch_Interrupt(void) {
CST816T->IIC_Interrupt_Flag = true;
}
/* LVGL 显示刷新回调 */
void my_disp_flush(lv_disp_drv_t *disp, 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);
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
lv_disp_flush_ready(disp);
}
/* LVGL 触摸读取回调 */
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
// 从触摸芯片读取坐标
int32_t touchX = CST816T->IIC_Read_Device_Value(CST816T->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_X);
int32_t touchY = CST816T->IIC_Read_Device_Value(CST816T->Arduino_IIC_Touch::Value_Information::TOUCH_COORDINATE_Y);
// 检查中断标志位来判断是否有新的触摸事件
if (CST816T->IIC_Interrupt_Flag == true) {
CST816T->IIC_Interrupt_Flag = false; // 清除标志位
data->state = LV_INDEV_STATE_PR; // 状态:按下
/* 检查坐标有效性后设定坐标 */
if (touchX >= 0 && touchY >= 0) {
data->point.x = touchX;
data->point.y = touchY;
}
} else {
data->state = LV_INDEV_STATE_REL; // 状态:释放
}
}
/* LVGL Tick 增加回调 */
void example_increase_lvgl_tick(void *arg) {
/* 告诉 LVGL 经过了多少毫秒 */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
/* --- UI 界面代码 --- */
// https://docs.lvgl.io/8.4/examples.html
// 将示例代码函数放到这里
void setup() {
// 1. 硬件初始化 (Hardware Initialization)
// 首先完成所有与物理硬件(屏幕、触摸、背光等)直接通信的初始化。
// 初始化屏幕显示
gfx->begin();
pinMode(LCD_BL, OUTPUT);
digitalWrite(LCD_BL, HIGH); // 打开背光
// 初始化触摸控制器
while (CST816T->begin() == false) {
delay(2000); // 如果失败,等待一段时间后重试
}
CST816T->IIC_Write_Device_State(CST816T->Arduino_IIC_Touch::Device::TOUCH_DEVICE_INTERRUPT_MODE,
CST816T->Arduino_IIC_Touch::Device_Mode::TOUCH_DEVICE_INTERRUPT_PERIODIC);
// 2. LVGL 核心与心跳初始化 (LVGL Core and Tick Initialization)
lv_init();
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000);
// 3. LVGL 驱动注册 (LVGL Driver Registration)
// 将 LVGL 的逻辑操作与硬件的回调函数连接
// --- 初始化显示驱动 ---
lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * screenHeight / 10);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
// --- 初始化输入设备(触摸)驱动 ---
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER; // 类型为指针/触摸
indev_drv.read_cb = my_touchpad_read; // 注册读取回调
lv_indev_drv_register(&indev_drv);
// 4. UI 创建与应用层初始化 (UI Creation & Application Initialization)
// 在所有底层都准备就绪后,开始创建图形界面。
// 调用 UI 创建函数
// lv_example_xxx();
}
void loop() {
lv_timer_handler(); /* 让 LVGL GUI 处理其任务(如动画、事件等) */
delay(5);
}
代码解析
本示例为 ESP32-S3-Touch-LCD-1.69 开发板提供了一个功能完整的 LVGL 项目模板,其中预置了所有必需的硬件初始化和驱动注册逻辑,构成了 LVGL 运行的基础环境。
- 作为模板使用
- 该代码的主要价值在于其重用性。对于使用相同硬件(ESP32-S3-Touch-LCD-1.69)的任何新项目,可以直接复制此模板。
- 开发流程:
信息
本示例基于 LVGL v8.4.0, 可以直接使用 LVGL8.4 官方示例。
- 在
/* --- UI 界面代码 --- */
区域内编写或粘贴新的 UI 创建函数(例如,来自 LVGL 官方文档的示例)。 - 在
setup()
函数的末尾,调用刚刚添加的 UI 创建函数(例如,lv_example_roller_1();
)。
- 在
- 希望通过这个示例帮助你快速运行 LVGL 官方示例,熟悉 lvgl 的开发。