简介这是一份面向嵌入式开发者的ESP32-S3图形界面实战资源聚焦用LVGL图形库驱动1.69寸电容触摸屏解决显示与触控联调中的驱动适配问题。屏幕显示部分采用ST7789控制器通过SPI接口传输图像数据触摸部分由CST816芯片负责支持多点触控需经I2C或SPI读取触点坐标。资源重点在于把两者整合进LVGL编写自定义触摸驱动模块、注册触摸事件回调、将CST816数据转换为LVGL可识别的输入状态并完成初始化与参数配置。压缩包共971个文件约17.39MB以C源码与头文件为主体辅以Python脚本、Markdown说明、PNG图片、字体文件及构建配置覆盖驱动适配、示例工程与文档说明。已有6177人学习下载适合具备一定ESP32与SPI/I2C基础的开发者参考可据此快速搭建可交互的图形界面理解显示刷新与触摸响应的完整链路。1. 从一块 1.69 寸屏开始为什么你的 esp32s3 跑 lvgl 总是白屏很多人第一次把 1.69 寸电容触摸屏接到 esp32s3 上烧录完程序屏幕要么全白要么背光亮但没图像要么触摸完全没反应。这不是 lvgl 本身的问题而是 esp32s3 的引脚映射、SPI 总线配置、触摸芯片初始化顺序这三件事里至少有一件没对齐。我见过太多人卡在这一步反复烧录十几次最后发现只是 CS 引脚号写错了。这套环境的核心是 esp32s3 lvgl 1.69 寸电容触摸屏常见驱动 IC 为 ST7789V 或 GC9A01触摸 IC 多为 CST816S 或 FT6236。它适合做小尺寸 HMI 面板、便携设备 UI、桌面信息屏这类场景。装不好不会烧板子但会让你在“白屏—改代码—再白屏”的循环里耗掉一整个周末。下面按我实际装机的顺序从环境版本、工程配置、驱动对接、触摸校准到排错一步步走完。2. 环境准备esp-idf 版本、lvgl 版本和屏幕参数先对齐2.1 选 esp-idf v5.1 还是 v5.3esp32s3 对 esp-idf 的版本比较敏感。我一般用 v5.1.2 或 v5.3.1这两个版本对 esp32s3 的 SPI 主机驱动和 LCD 外设支持最稳。v5.0 以前的一些 lvgl 移植例程在 v5.3 上编译会报esp_lcd_panel_io_spi_config_t字段缺失所以别混用。安装 esp-idf 的常见做法是走官方安装脚本# 克隆 esp-idf 到本地指定 v5.3.1 分支 git clone -b v5.3.1 --recursive https://github.com/espressif/esp-idf.git cd esp-idf ./install.sh esp32s3 . ./export.shinstall.sh esp32s3只装 esp32s3 相关的工具链比全量安装省一半时间。export.sh每次开终端都要执行一次或者写进.bashrc。装完后用idf.py --version确认输出是ESP-IDF v5.3.1。2.2 lvgl 版本选择v8.3 还是 v9lvgl v8.3 的 API 和网上大部分 esp32s3 例程兼容v9 改了显示驱动注册方式很多旧教程直接抄会编译不过。如果你不是非要追新我建议先用 v8.3.11。把 lvgl 作为组件放进工程# 在工程目录下创建 components 文件夹 mkdir -p components cd components git clone -b release/v8.3 https://github.com/lvgl/lvgl.git然后在CMakeLists.txt里加set(EXTRA_COMPONENT_DIRS components)。这样 lvgl 源码随工程走换电脑不用重新配。2.3 1.69 寸屏的关键参数表不同厂家的 1.69 寸屏参数有差异接线前先确认这几项参数常见值说明分辨率240×280ST7789V 常见GC9A01 是 240×240接口SPI 4线SCLK、MOSI、CS、DC部分带 RST触摸 ICCST816S / FT6236I2C 接口地址 0x15 或 0x38背光独立引脚或常亮部分模块 BLK 接 3.3V 即亮逻辑电平3.3V别接 5V会烧触摸 IC注意买屏时一定问清楚驱动 IC 型号。ST7789V 和 GC9A01 的初始化命令不一样写错就是白屏。3. 工程配置SPI 总线、引脚映射和 lvgl 移植层3.1 用 esp_lcd 初始化 SPI 屏esp-idf v5.x 推荐用esp_lcd组件驱动屏幕而不是自己写 SPI 时序。先配置 SPI 总线// SPI 总线配置esp32s3 用 SPI2_HOST spi_bus_config_t buscfg { .sclk_io_num 12, // 时钟引脚 .mosi_io_num 11, // 数据引脚 .miso_io_num -1, // 屏幕不需要 MISO .quadwp_io_num -1, .quadhd_io_num -1, .max_transfer_sz 240 * 280 * 2 8, // 全屏缓冲区大小 }; ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, buscfg, SPI_DMA_CH_AUTO));max_transfer_sz要大于一帧图像字节数否则刷屏会报SPI transfer size exceeds。SPI_DMA_CH_AUTO让驱动自动选 DMA 通道esp32s3 有两个 DMA 通道一般够用。3.2 注册 LCD 面板和 IO 句柄接着挂载 LCD 设备esp_lcd_panel_io_handle_t io_handle NULL; esp_lcd_panel_io_spi_config_t io_config { .dc_gpio_num 10, // DC 引脚 .cs_gpio_num 9, // CS 引脚 .pclk_hz 40 * 1000 * 1000, // SPI 时钟 40MHz .lcd_cmd_bits 8, .lcd_param_bits 8, .spi_mode 0, .trans_queue_depth 10, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, io_config, io_handle));pclk_hz别一上来就写 80MHz1.69 寸屏很多排线较长40MHz 更稳。如果花屏降到 20MHz 试试。trans_queue_depth是异步传输队列深度设 10 够用设太大占内存。3.3 lvgl 移植层disp_drv 和 ticklvgl 需要一个显示缓冲区和刷新回调// 分配绘制缓冲区建议 1/10 屏幕大小 static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[240 * 30]; lv_disp_draw_buf_init(draw_buf, buf, NULL, 240 * 30); static lv_disp_drv_t disp_drv; lv_disp_drv_init(disp_drv); disp_drv.hor_res 240; disp_drv.ver_res 280; disp_drv.flush_cb my_flush_cb; // 你的刷屏函数 disp_drv.draw_buf draw_buf; lv_disp_drv_register(disp_drv);flush_cb里调用esp_lcd_panel_draw_bitmap把 lvgl 渲染好的区域推给屏幕。别忘了在app_main里起一个 1ms 的定时器调lv_tick_inc(1)否则 lvgl 动画和触摸响应都会卡。4. 触摸对接CST816S 的 I2C 初始化和坐标映射4.1 I2C 总线配置1.69 寸电容屏的触摸 IC 走 I2Cesp32s3 用 I2C_NUM_0i2c_config_t i2c_conf { .mode I2C_MODE_MASTER, .sda_io_num 8, .scl_io_num 7, .sda_pullup_en GPIO_PULLUP_ENABLE, .scl_pullup_en GPIO_PULLUP_ENABLE, .master.clk_speed 100000, // 100kHzCST816S 最高 400kHz }; i2c_param_config(I2C_NUM_0, i2c_conf); i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);CST816S 的 I2C 地址是0x15FT6236 是0x38。用i2c_scanner例程扫一下就能确认。上拉电阻很多模块已经焊好如果读不到数据先查 SDA/SCL 有没有 4.7k 上拉。4.2 注册 lvgl 输入设备触摸读点后要注册成 lvgl 的 indevstatic lv_indev_drv_t indev_drv; lv_indev_drv_init(indev_drv); indev_drv.type LV_INDEV_TYPE_POINTER; indev_drv.read_cb my_touch_read_cb; lv_indev_drv_register(indev_drv);my_touch_read_cb里读 CST816S 的寄存器0x02开始 6 个字节前两字节是 X中间两字节是 Y最后是触摸状态。注意 CST816S 的坐标范围可能和屏幕分辨率不一致需要做一次线性映射// 假设触摸 IC 返回 0~240屏幕也是 240 宽 lv_coord_t x raw_x * 240 / 240; lv_coord_t y raw_y * 280 / 280;如果 X 轴反了用240 - xY 轴反了用280 - y。这个在read_cb里改一行就行。4.3 触摸校准的偷懒做法CST816S 一般不需要软件校准但如果你发现触摸点偏移固定值可以在read_cb里加偏移量。我一般先烧一个画点程序手指点屏幕四角看落点差多少然后直接改映射公式。比跑校准算法快。5. 避坑排查白屏、花屏、触摸无反应的 5 个真实翻车记录5.1 白屏但背光亮现象屏幕全白背光正常串口无报错。原因ST7789V 的初始化命令没发或者dc_gpio_num配错。esp_lcd 的esp_lcd_new_panel_st7789需要手动调esp_lcd_panel_init。解决确认esp_lcd_panel_init(panel_handle)被调用且 DC 引脚和实际接线一致。用万用表量 DC 引脚在刷屏时有没有电平变化。5.2 花屏、颜色错乱现象图像出来但颜色是反的或者有雪花噪点。原因SPI 时钟太快或者lcd_cmd_bits/lcd_param_bits设错。ST7789V 是 8 位命令 8 位参数GC9A01 也是。解决把pclk_hz从 40MHz 降到 20MHz。如果颜色反了在初始化后发0x21命令开反转或者改color_space配置。5.3 触摸完全无反应现象屏幕显示正常但触摸没任何响应。原因I2C 地址错、上拉电阻没焊、或者触摸 IC 没复位。CST816S 的 RST 引脚如果悬空可能不工作。解决用i2c_scanner扫地址确认0x15能扫到。如果扫不到把 RST 接 GPIO 并在初始化时拉低 10ms 再拉高。5.4 lvgl 任务卡死或看门狗复位现象跑几分钟后重启串口报Task watchdog got triggered。原因flush_cb里用了阻塞式 SPI 传输且没让出 CPU。或者lv_tick_inc在中断里调用了。解决flush_cb里用esp_lcd_panel_draw_bitmap的异步模式传NULL回调然后在lv_disp_flush_ready里通知 lvgl。lv_tick_inc放在esp_timer回调里别放中断。5.5 编译报错lv_conf.h找不到现象编译时提示lv_conf.h: No such file or directory。原因lvgl 组件没把lv_conf.h路径加进 include。解决在工程根目录放一份lv_conf.h然后在CMakeLists.txt里加include_directories(${CMAKE_SOURCE_DIR})。或者把 lvgl 的lv_conf_template.h复制成lv_conf.h放到components/lvgl旁边。6. 装完怎么验证跑一个触摸画板才算真通烧录完先别急着写复杂 UI用 lvgl 官方lv_demo_widgets或自己写一个画板验证。画板能同时验证显示刷新和触摸坐标是最省事的验收方式。// 创建一个画布手指画线 static lv_obj_t *canvas lv_canvas_create(lv_scr_act()); static lv_color_t cbuf[LV_CANVAS_BUF_SIZE_TRUE_COLOR(240, 280)]; lv_canvas_set_buffer(canvas, cbuf, 240, 280, LV_IMG_CF_TRUE_COLOR); lv_canvas_fill_bg(canvas, lv_color_white(), LV_OPA_COVER); // 在触摸回调里画点 void my_touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data) { // ... 读 CST816S ... if (pressed) { lv_canvas_set_px(canvas, x, y, lv_color_black()); style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />