☰
ESP32-S3驱动1.69寸屏跑LVGL:从黑屏到触摸的完整避坑指南
2026/10/19 8:09:41 网站建设 项目流程

简介:这是一份面向嵌入式开发者的ESP32-S3图形界面实战资源,聚焦用LVGL图形库驱动1.69寸电容触摸屏,适合已具备一定单片机与SPI、I2C基础、希望快速跑通显示与触控交互的中级开发者。资源包共971个文件,以C源码与头文件为主体,辅以Python脚本、Markdown说明、PNG/GIF图片素材及字体文件,压缩包约17.39MB,目录结构完整,便于按模块查阅与移植。内容围绕ST7789显示控制器与CST816触摸芯片展开,涵盖SPI显示初始化、I2C触摸通信、LVGL自定义触摸驱动注册及事件回调处理等关键环节,并附带音乐播放器、控件演示等示例素材,可直接参考整合进自己的工程。目前已有6177人学习下载,适合想缩短LVGL移植调试周期、对照示例排查显示与触控问题的开发者参考。

1. 从一块 1.69 寸屏开始:为什么 ESP32-S3 跑 LVGL 总在第一步翻车

很多人第一次把 1.69 寸电容触摸屏接到 ESP32-S3 上,代码烧进去屏幕要么全黑、要么花屏、要么触摸完全没反应,折腾一晚上连 LVGL 的启动画面都没见着。这套组合在小型智能面板、便携设备里太常见了,ESP32-S3 自带足够 RAM 和 PSRAM 扩展能力,LVGL 又是嵌入式 GUI 里资料最厚的一档,按理说应该很顺,但实际卡人的地方往往不在 LVGL 本身,而在屏幕驱动 IC 的型号识别、SPI 引脚映射、触摸芯片初始化顺序这三件事上。这篇笔记按我实际装环境的顺序走一遍,从版本选择、依赖确认、驱动配置到烧录验证,每一步都给出可抄的命令和参数,适合刚拿到板子和屏、还没点亮第一帧的开发者。

2. 环境准备:ESP-IDF 版本、LVGL 版本和屏幕驱动 IC 的对应关系

2.1 先确认屏幕用的是哪颗驱动 IC

1.69 寸电容触摸屏常见的有两种组合:显示驱动 IC 多为 ST7789V 或 GC9A01,触摸 IC 多为 CST816S 或 FT6236。这两个信息决定了后面选哪个 LVGL 显示端口和触摸端口,选错了就是黑屏或触摸无响应。拿到屏先看排线丝印或卖家给的规格书,确认下面三项:

项目常见型号确认方式
显示驱动 ICST7789V / GC9A01排线丝印或规格书
触摸 ICCST816S / FT6236规格书或 I2C 扫描地址
分辨率240x280 / 240x240规格书
接口SPI + I2C排线引脚定义

我一般会先用 I2C 扫描确认触摸 IC 地址,CST816S 通常是 0x15,FT6236 通常是 0x38。地址对不上,后面 LVGL 的输入设备注册就是白费功夫。

2.2 ESP-IDF 和 LVGL 版本怎么选

ESP-IDF 建议用 v5.1.x 或 v5.2.x,这两个版本对 ESP32-S3 的 SPI DMA 和 PSRAM 支持比较稳。LVGL 用 v8.3.x,v9 虽然新,但和 ESP-IDF 的端口层还在磨合,新手容易在 lv_port_disp 和 lv_port_indev 的接口上卡住。安装命令如下:

# 安装 ESP-IDF v5.1.2 mkdir -p ~/esp && cd ~/esp git clone -b v5.1.2 --recursive https://github.com/espressif/esp-idf.git cd esp-idf ./install.sh esp32s3 . ./export.sh

install.sh esp32s3只装 S3 相关工具链,省空间也省时间。export.sh每次开新终端都要执行一次,或者写进.bashrc。装完用idf.py --version确认输出是 v5.1.2。

LVGL 不用单独 clone,直接在项目里用组件管理器拉:

# 在项目目录下创建 components 并拉取 LVGL v8.3.11 mkdir -p components && cd components git clone -b v8.3.11 https://github.com/lvgl/lvgl.git

LVGL v8.3.11 对 ESP-IDF v5.1 的兼容性最好,lv_conf.h里把LV_COLOR_DEPTH设为 16,LV_MEM_CUSTOM设为 1 并指向heap_caps_malloc,这样能吃到 PSRAM。

2.3 项目目录结构先摆对

在写任何驱动代码之前,目录结构要固定下来,不然后面改路径改到怀疑人生:

project/ ├── CMakeLists.txt ├── sdkconfig.defaults ├── main/ │ ├── CMakeLists.txt │ ├── main.c │ ├── lv_port_disp.c │ ├── lv_port_indev.c │ └── st7789.c └── components/ └── lvgl/

sdkconfig.defaults里先写死几个关键项,避免每次 menuconfig 手点:

CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_OCT=y CONFIG_SPIRAM_SPEED_80M=y CONFIG_LV_COLOR_DEPTH_16=y

CONFIG_SPIRAM_MODE_OCT对应 S3 的八线 PSRAM,如果你的模组是四线,改成CONFIG_SPIRAM_MODE_QUAD=y。这个选错,LVGL 分配大 buffer 时会直接崩。

3. 配置 SPI 和 I2C:引脚映射、时钟频率和 DMA 通道

3.1 SPI 总线初始化

ST7789V 走 SPI,ESP32-S3 的 SPI2 或 SPI3 都行,我一般用 SPI2。下面是一段可抄的初始化代码:

// st7789.c 片段:SPI 总线初始化 #include "driver/spi_master.h" #define PIN_MOSI 11 #define PIN_SCLK 12 #define PIN_CS 10 #define PIN_DC 13 #define PIN_RST 14 #define PIN_BL 15 spi_bus_config_t buscfg = { .mosi_io_num = PIN_MOSI, .miso_io_num = -1, // 屏幕只写不读,MISO 不接 .sclk_io_num = PIN_SCLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 240 * 280 * 2 + 8, // 一帧 RGB565 大小 }; spi_device_interface_config_t devcfg = { .clock_speed_hz = 40 * 1000 * 1000, // 40MHz,先保守 .mode = 0, // ST7789 用 SPI mode 0 .spics_io_num = PIN_CS, .queue_size = 7, }; spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); spi_bus_add_device(SPI2_HOST, &devcfg, &spi);

max_transfer_sz必须大于一帧数据量,否则 LVGL 刷屏时会报SPI buffer too small。clock_speed_hz先给 40MHz,跑通后再往 80MHz 试,很多屏在 80MHz 下会出雪花,属于玄学范畴,降回 40MHz 就好。SPI_DMA_CH_AUTO让驱动自动选 DMA 通道,S3 上一般没问题。

3.2 I2C 总线初始化给触摸 IC

触摸 IC 走 I2C,ESP32-S3 的 I2C0 或 I2C1 都可以:

// lv_port_indev.c 片段:I2C 初始化 #include "driver/i2c.h" #define PIN_SDA 8 #define PIN_SCL 9 #define TOUCH_ADDR 0x15 // CST816S 地址 i2c_config_t i2c_cfg = { .mode = I2C_MODE_MASTER, .sda_io_num = PIN_SDA, .scl_io_num = PIN_SCL, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .master.clk_speed = 400000, // 400kHz 快速模式 }; i2c_param_config(I2C_NUM_0, &i2c_cfg); i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);

TOUCH_ADDR按实际 IC 改,FT6236 是 0x38。clk_speed给 400kHz,如果读触摸坐标偶尔丢点,降到 100kHz 试试,排线长的时候高速率容易受干扰。

3.3 LVGL 显示端口对接

LVGL 的显示端口要提供flush_cb,把 LVGL 画好的 buffer 通过 SPI 推给屏:

// lv_port_disp.c 片段:flush 回调 static void disp_flush(lv_disp_drv_t *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; st7789_set_window(area->x1, area->y1, area->x2, area->y2); st7789_push_colors((uint8_t *)color_p, w * h * 2); lv_disp_flush_ready(drv); // 必须调用,否则 LVGL 不再刷新 }

lv_disp_flush_ready漏掉是新手最常见的黑屏原因之一,LVGL 会一直等这个信号,画面就停在第一帧不动。st7789_set_window负责发 CASET 和 RASET 命令,st7789_push_colors负责发 RAMWR 和像素数据。

3.4 触摸输入端口对接

触摸端口要提供read_cb,把 I2C 读到的坐标转成 LVGL 的输入事件:

// lv_port_indev.c 片段:触摸读取回调 static void touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) { uint16_t x, y; if (cst816s_read(&x, &y) == ESP_OK) { >// main.c 片段:验证用界面 lv_obj_t *scr = lv_scr_act(); lv_obj_t *label = lv_label_create(scr); lv_label_set_text(label, "ESP32-S3 + LVGL OK"); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); lv_obj_t *btn = lv_btn_create(scr); lv_obj_set_size(btn, 100, 50); lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -20); lv_obj_t *btn_label = lv_label_create(btn); lv_label_set_text(btn_label, "TOUCH"); lv_obj_center(btn_label);

屏幕出现居中的文字和底部按钮,说明显示链路通了。然后用手指点按钮,按钮变色,说明触摸链路也通了。如果按钮不变色,回到touch_read里加一句ESP_LOGI("TOUCH", "x=%d y=%d", x, y),看串口有没有坐标输出。有坐标但按钮不响应,检查lv_indev_drv_register有没有调用,以及style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询