基于ESP32S3的智能终端项目--2.LVGL 移植和配置LVGL&LVGL部件和事件
三、LVGL 移植和配置(让屏幕和触摸用起来)
LVGL 是一个开源的嵌入式图形库,可以帮我们在屏幕上画出漂亮的按钮、文字、滑块等界面元素。要让它在你的 ESP32-S3 上跑起来,需要完成几步配置:添加库、修改配置文件、告诉 LVGL 如何把图像画到屏幕上、如何读取触摸坐标。
1. 在 platformio.ini 中添加依赖(一键下载 LVGL 和驱动库)
打开项目里的 platformio.ini,在 lib_deps 后面加上这三行(如果原来有别的库,就换行添加):
ini
lib_deps =
lvgl/lvgl@^8.3.9 ; LVGL 图形库
bodmer/TFT_eSPI@2.5.0 ; TFT 屏幕显示驱动(兼容各种 SPI 屏幕)
fbiego/CST816S@^1.1.1 ; CST816S 触摸芯片驱动(如果你的触摸芯片是这个型号)
保存文件后,PlatformIO 会自动下载这三个库到项目的 .pio/libdeps 文件夹里。如果下载慢,参考之前的国内源方法。
2. 配置 LVGL 核心文件 lv_conf.h(告诉 LVGL 怎么运行)
LVGL 本身有很多可配置的选项,比如内存大小、是否使用操作系统、刷新速度等。这些配置放在一个叫 lv_conf.h 的文件里。
-
找到模板文件:打开项目里的
.pio/libdeps/esp32-s3-devkitc-1/lvgl文件夹,里面有个lv_conf_template.h,把它复制一份到你的项目根目录(和platformio.ini同一层),并重命名为lv_conf.h。
(如果根目录已经有了,直接打开修改) -
修改配置:用 VSCode 打开
lv_conf.h,找到下面几项并修改:-
第1步:启用 LVGL
文件开头有一行#if 0,把它改成#if 1。这样 LVGL 的配置才会生效。 -
第2步:让 LVGL 使用 Arduino 的内存管理函数
搜索LV_MEM_CUSTOM,把它设为 1,并取消下面两行的注释(或者直接加上):c #define LV_MEM_CUSTOM 1 #define LV_MEM_CUSTOM_INCLUDE <Arduino.h> // 使用 Arduino 的 malloc/free -
第3步:告诉 LVGL 如何获取系统时间(用于动画、长按等)
搜索LV_TICK_CUSTOM,设为 1,并添加:c #define LV_TICK_CUSTOM 1 #define LV_TICK_CUSTOM_INCLUDE "Arduino.h" #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) // 用 Arduino 的 millis() 作为时钟源 -
其他配置保持默认即可,比如屏幕分辨率、颜色深度等后面会通过代码设置。
-
小提示:如果你的 ESP32-S3 有 8MB PSRAM,可以再开启
LV_MEM_SIZE调大内存,但初学者先用默认就好。
3. 配置屏幕驱动 TFT_eSPI(告诉驱动你的屏幕型号和接线)
TFT_eSPI 是一个通用的 SPI 屏幕库,支持很多种屏幕驱动芯片(比如 ST7789、ILI9341)。我们需要修改它的配置文件,告诉它用哪种芯片、屏幕尺寸、以及和 ESP32-S3 的引脚连接。
-
找到配置文件:打开
.pio/libdeps/esp32-s3-devkitc-1/TFT_eSPI文件夹,里面有一个User_Setup.h,用 VSCode 打开它。 -
修改内容(建议先把原来的内容备份一下,然后按下面步骤修改):
-
第1步:选择屏幕驱动芯片
注释掉其他所有#define ..._DRIVER的行,只保留你的屏幕对应的那一行。比如你的屏幕是 ST7789 驱动的(常见于 240x320 的圆屏或方屏),就只保留:c #define ST7789_DRIVER -
第2步:设置屏幕分辨率和颜色顺序
添加或确认以下几行(根据你的屏幕手册):c #define TFT_WIDTH 240 // 屏幕宽度(像素) #define TFT_HEIGHT 320 // 屏幕高度(像素) #define TFT_RGB_ORDER TFT_BGR // 颜色顺序,如果颜色显示不对(比如红色变蓝色),就改成 TFT_RGB #define TFT_INVERSION_OFF // 反色开关,如果屏幕颜色反了(白色变黑),就改成 TFT_INVERSION_ON -
第3步:设置 SPI 引脚(非常重要!要和你的硬件接线一致)
找到#define TFT_MOSI等引脚定义的地方,改成你的实际接线。以常见的 ESP32-S3 开发板为例(假设 MOSI→7, SCLK→15, CS→4, DC→6, RST→5, BL→16):c #define TFT_MOSI 7 #define TFT_SCLK 15 #define TFT_CS 4 #define TFT_DC 6 #define TFT_RST 5 #define TFT_BL 16 // 背光引脚 #define TFT_BACKLIGHT_ON HIGH // 背光点亮电平(通常是 HIGH) -
第4步:设置 SPI 通信速度
在文件末尾附近,找到#define SPI_FREQUENCY,可以设成 40000000(40MHz):c #define SPI_FREQUENCY 40000000
-
保存 User_Setup.h。TFT_eSPI 会自动使用这些配置。
4. 配置触摸驱动 CST816S(告诉驱动触摸芯片的 I2C 引脚和地址)
如果你的触摸芯片是 CST816S(常见于一些圆屏),需要修改它的头文件。
-
找到头文件:打开
.pio/libdeps/esp32-s3-devkitc-1/CST816S文件夹,找到CST816S.h,用 VSCode 打开。 -
修改 I2C 地址:
搜索CST816S_ADDRESS,确保它被定义为0x38(大部分 CST816S 是这个地址):c #define CST816S_ADDRESS 0x38
保存文件。后面我们会在代码里设置触摸的引脚(SDA、SCL、RST、IRQ)。
5. 编写主程序:注册显示和触摸设备(告诉 LVGL 怎么用它们)
现在我们把 LVGL、屏幕、触摸三者关联起来。LVGL 官方提供了一个 Arduino 例程,我们可以把它复制过来修改。
-
复制例程:打开
.pio/libdeps/esp32-s3-devkitc-1/lvgl/examples/arduino/LVGL_Arduino文件夹,把里面的LVGL_Arduino.ino文件内容全部复制,替换掉你自己src/main.cpp的内容。 -
修改屏幕尺寸:
在文件开头,找到这两行:c static const uint16_t screenWidth = 480; static const uint16_t screenHeight = 320;改成你的屏幕尺寸(比如 240x320):
c static const uint16_t screenWidth = 320; // 注意:这里是宽度,如果屏幕是竖屏,可能宽320高240,根据你的实际调整 static const uint16_t screenHeight = 240;注意:如果你的屏幕是竖屏(比如手机样式),通常宽度小于高度,这里填
width=240, height=320;如果是横屏,则反过来。请根据你的屏幕方向确定。 -
添加触摸对象:
在文件开头,#include <lvgl.h>下面,添加触摸库头文件和触摸对象:cpp #include <CST816S.h> CST816S touch(3, 18, 8, 46); // 参数顺序:SDA, SCL, RST, IRQ(根据你的接线修改) -
修改触摸回调函数:
找到my_touchpad_read函数,将其内容替换为(根据 CST816S 的数据格式):cpp void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) { if (touch.available()) { // 如果有触摸 data->state = LV_INDEV_STATE_PR; // 按下状态 // CST816S 返回的坐标可能需要调整方向,这里假设触摸的 x 对应屏幕的 y,且镜像 data->point.x = 320 - touch.data.y; // 根据你的实际情况调整映射关系 data->point.y = touch.data.x; } else { data->state = LV_INDEV_STATE_REL; // 释放状态 } }注意:坐标映射需要根据屏幕和触摸的实际方向调整。上面的例子只是一种可能,如果触摸位置不对,你可能需要交换 x、y,或者用
touch.data.x直接赋值。 -
修改
setup()中的触摸初始化:
找到tft.setTouch(calData);那行(大概在例程的setup后半部分),把它注释掉,换成:cpp // uint16_t calData[5] = { 275, 3620, 264, 3532, 1 }; // tft.setTouch(calData); // 这是电阻屏的校准,不需要 touch.begin(); // 初始化 CST816S -
最后,添加一个简单的界面来测试:
在setup()末尾(lv_timer_handler();之前)添加:cpp // 创建一个标签显示 "Hello" lv_obj_t *label = lv_label_create(lv_scr_act()); lv_label_set_text(label, "Hello ESP32-S3 + LVGL!"); lv_obj_align(label, LV_ALIGN_CENTER, 0, -20); // 创建一个按钮,点击时在串口打印信息 lv_obj_t *btn = lv_btn_create(lv_scr_act()); lv_obj_set_size(btn, 120, 50); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 40); lv_obj_t *btn_label = lv_label_create(btn); lv_label_set_text(btn_label, "Click me"); lv_obj_center(btn_label); // 给按钮添加事件回调 lv_obj_add_event_cb(btn, [](lv_event_t *e) { if (lv_event_get_code(e) == LV_EVENT_CLICKED) { Serial.println("Button clicked!"); } }, LV_EVENT_CLICKED, NULL); -
保存文件,然后编译上传。如果一切顺利,屏幕会显示文字和一个按钮,触摸按钮时串口会打印 "Button clicked!"。
6. 常见问题排查
-
屏幕不亮:检查背光引脚和
TFT_BL配置是否正确,背光电平是HIGH还是LOW。 -
屏幕显示花屏或颜色不对:尝试修改
TFT_RGB_ORDER为TFT_RGB,或者调整TFT_INVERSION_OFF/ON。 -
触摸没反应:检查 I2C 引脚和地址,用 I2C 扫描程序确认设备地址是否为 0x38。坐标映射可能需要交换 x、y 或取反。
-
编译报错:可能是某个库版本不兼容,可以尝试降低版本(比如 TFT_eSPI 用 2.5.0,LVGL 用 8.3.9)。
四、LVGL 部件和事件(开始画界面吧)
LVGL 提供了很多现成的“部件”(Widgets),就像手机 App 里的按钮、滑块、开关等。你只需要创建它们,设置属性,然后处理事件(比如点击、滑动)。
1. 常用部件一览(先认识几个常用的)
| 部件名称 | 作用 | 创建函数 |
|---|---|---|
| Label(标签) | 显示文字 | lv_label_create(parent) |
| Button(按钮) | 可点击的区域 | lv_btn_create(parent) |
| Slider(滑块) | 滑动调节数值 | lv_slider_create(parent) |
| Switch(开关) | 开/关切换 | lv_switch_create(parent) |
| Checkbox(复选框) | 多选选项 | lv_checkbox_create(parent) |
| Textarea(文本框) | 输入多行文字 | lv_textarea_create(parent) |
| Image(图像) | 显示图片 | lv_img_create(parent) |
| Chart(图表) | 显示曲线/柱状图 | lv_chart_create(parent) |
| List(列表) | 垂直列表 | lv_list_create(parent) |
每个部件创建时都需要指定一个“父对象”,通常是屏幕本身(lv_scr_act()),也可以是其他容器(比如面板)。
2. 创建、布局与样式(三步搞定一个部件)
2.1 创建对象
cpp
lv_obj_t *btn = lv_btn_create(lv_scr_act()); // 在屏幕上创建一个按钮
2.2 设置大小和位置
cpp
lv_obj_set_size(btn, 120, 50); // 宽度120,高度50
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); // 居中对齐,偏移(0,0)
对齐方式有很多种,比如 LV_ALIGN_TOP_LEFT(左上角)、LV_ALIGN_BOTTOM_MID(底部中间)等。后面的两个数字是 x 和 y 方向的偏移像素。
2.3 在按钮上添加文字
cpp
lv_obj_t *label = lv_label_create(btn); // 在按钮上创建标签
lv_label_set_text(label, "Click"); // 设置文字
lv_obj_center(label); // 让标签在按钮内居中
2.4 添加样式(可选)
如果你想改变按钮的颜色、圆角、阴影等,可以定义样式并应用:
cpp
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_hex(0xFF0000)); // 红色背景
lv_style_set_radius(&style, 10); // 圆角10像素
lv_obj_add_style(btn, &style, 0); // 应用样式到按钮
3. 事件回调机制(让部件动起来)
当用户点击按钮、滑动滑块时,LVGL 会产生一个“事件”。你需要为部件注册一个回调函数,在回调里判断事件类型并做出响应。
3.1 注册事件回调
cpp
lv_obj_add_event_cb(btn, my_event_cb, LV_EVENT_CLICKED, NULL);
-
btn:要监听的部件。 -
my_event_cb:回调函数名。 -
LV_EVENT_CLICKED:监听的事件类型(这里只监听点击,你也可以监听多种事件)。 -
NULL:用户数据(可传任意指针)。
3.2 编写回调函数
cpp
void my_event_cb(lv_event_t *e) {
lv_event_code_t code = lv_event_get_code(e); // 获取事件代码
if (code == LV_EVENT_CLICKED) {
Serial.println("Button clicked!");
}
}
常用事件类型
-
LV_EVENT_CLICKED:点击完成(按下并释放) -
LV_EVENT_PRESSED:刚按下 -
LV_EVENT_RELEASED:释放 -
LV_EVENT_VALUE_CHANGED:值改变(比如滑块滑动) -
LV_EVENT_LONG_PRESSED:长按
4. 完整示例:一个带事件的按钮
把下面代码放到 loop() 之前(通常放在 setup() 里):
cpp
// 创建按钮
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 150, 60);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
// 按钮上的文字
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Press me");
lv_obj_center(label);
// 添加点击事件
lv_obj_add_event_cb(btn, [](lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_CLICKED) {
Serial.println("Button pressed!");
// 可以在这里改变按钮文字等
lv_obj_t *btn = (lv_obj_t*)lv_event_get_target(e);
lv_obj_t *label = lv_obj_get_child(btn, 0); // 获取按钮的第一个子对象(就是label)
lv_label_set_text(label, "Clicked!");
}
}, LV_EVENT_CLICKED, NULL);

这里用了 C++ 的 Lambda 表达式([](lv_event_t *e){...}),可以让代码更紧凑。如果你不熟悉 Lambda,也可以像前面那样单独写一个函数。
5. 试试其他部件:滑块和开关
滑块示例
cpp
// 创建滑块
lv_obj_t *slider = lv_slider_create(lv_scr_act());
lv_obj_set_size(slider, 200, 10);
lv_obj_align(slider, LV_ALIGN_CENTER, 0, -40);
// 创建显示数值的标签
lv_obj_t *slider_label = lv_label_create(lv_scr_act());
lv_label_set_text(slider_label, "0");
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_TOP_MID, 0, -10);
// 添加值改变事件
lv_obj_add_event_cb(slider, [](lv_event_t *e) {
lv_obj_t *slider = lv_event_get_target(e);
char buf[8];
lv_snprintf(buf, sizeof(buf), "%d", lv_slider_get_value(slider));
lv_obj_t *label = (lv_obj_t*)lv_event_get_user_data(e);
lv_label_set_text(label, buf);
}, LV_EVENT_VALUE_CHANGED, slider_label);

开关示例
cpp
lv_obj_t *sw = lv_switch_create(lv_scr_act());
lv_obj_align(sw, LV_ALIGN_CENTER, 0, 50);
lv_obj_add_event_cb(sw, [](lv_event_t *e) {
if (lv_event_get_code(e) == LV_EVENT_VALUE_CHANGED) {
bool state = lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED);
Serial.println(state ? "ON" : "OFF");
}
}, LV_EVENT_VALUE_CHANGED, NULL);

按键

6. 学习更多
-
LVGL 官方文档:https://docs.lvgl.io/(有详细的中文翻译)
-
正点原子的视频教程(就是你截图的那个)也非常好,跟着做能快速上手。
现在,你已经掌握了 LVGL 的基本移植和部件使用方法,可以开始设计自己的智能终端界面了!如果在配置过程中遇到问题,多检查接线和配置文件,一步步排查。祝你开发顺利!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)