ESP32S3驱动CST328触摸与ST7789显示,完整移植LVGL实战指南

发布时间:2026/10/19 17:36:13
ESP32S3驱动CST328触摸与ST7789显示,完整移植LVGL实战指南
简介面向 ESP32-S3 嵌入式开发者的 LVGL 图形界面例程包基于 ESP-IDF 框架驱动 CST328 触摸与 ST7789 显示屏适合需要在 TFT 彩屏上快速实现交互界面的物联网项目开发者。压缩包共 1342 个文件大小 26.45MB主要包含 c/h 源码、Python 辅助脚本、Markdown 说明文档以及 PNG 图片资源覆盖 LVGL 各类原生 demo 与驱动配置示例。已有 1465 人学习下载内容组织清晰便于按模块检索。开发者可快速掌握 ESP32S3 与 LVGL 的整合方法获得屏幕初始化、触摸校准、控件布局等核心代码并直接复用其中音乐播放器、控件展示等示例显著缩短人机界面开发周期。1. ESP32S3驱动CST328与ST7789一块带触摸的小屏如何跑起LVGL手里一块屏幕标注触控IC是CST328、显示IC是ST7789结果商家给的驱动只适配STM32这种情况在开发圈里太常见了。我把这套硬件接到ESP32S3上基于ESP-IDF框架把LVGL完整跑通触摸、显示、UI三端衔接全部能编译运行。这篇笔记会拆开讲硬件连接、两颗驱动IC的初始化与读取逻辑、LVGL的移植点再记录几个实测必踩的坑。适合手里有同款屏、想在ESP32S3上快速验证LVGL的开发者也适合第一次把电容触摸屏接进ESP-IDF工程的人——踩坑记录能帮你少折腾一个晚上。2. 系统架构与硬件连接一块屏背后的协议链路CST328与ST7789各管一摊活它们和主控之间走的是不同总线。搞清这一步后面代码看多少都有底。2.1 引脚分配与硬件接线ST7789是SPI接口的TFT驱动ICCST328是I2C接口的电容触摸IC。一块成品屏幕模组通常把这两颗IC集成在同一块FPC上对外引出两组引脚。接线前先确认屏厂给的原理图不同批次引脚顺序可能不一样。屏幕信号对接ICESP32S3 GPIO说明SCK / SCLST7789 / CST328GPIO12共用两路总线的时钟MOSI / SDAST7789 / CST328GPIO11共用数据线CSST7789GPIO10片选低有效DCST7789GPIO9数据/命令切换RSTST7789GPIO8复位BL背光GPIO7或直连3.3VPWM可调亮度I2C_INTCST328GPIO6触摸中断可选注意CST328的I2C地址有两种常见值0x15或0x01具体由屏厂在模组内部通过地址引脚或寄存器配置决定。调试时可以先用I2C扫描工具探测地址再写死到驱动里。这块屏出厂固件把触摸坐标原点放在左下角和ST7789的默认扫描方向相反后面LVGL里要做坐标变换这部分在第四章展开。2.2 协议链路为什么触摸走I2C、显示走SPI一条SPI总线跑满可以到几十Mbps适合推送大量像素数据I2C虽然慢但触摸报文一次只有几个字节实时性要求高的是上报频率而不是带宽。于是显示数据走SPI、触摸状态走I2C成为这类低成本模组的常见组合。我一般把这两路独立看显示链路是从LVGL的缓冲区到ST7789的GRAM触摸链路是从CST328的寄存器到LVGL的indev驱动。两条链路在LVGL内部汇合互不干扰。理解这个分层之后调试的时候就能快速判断问题是出在屏幕刷新、触摸读取还是坐标映射层不会一股脑怀疑硬件。2.3 ESP-IDF工程目录与构建流程这套例程用ESP-IDF的组件化结构组织LVGL以组件形式引入。目录长这样├── main │ ├── CMakeLists.txt │ └── app_main.c ├── components │ ├── cst328 │ │ ├── include/cst328.h │ │ ├── cst328.c │ │ └── CMakeLists.txt │ ├── st7789 │ │ ├── include/st7789.h │ │ ├── st7789.c │ │ └── CMakeLists.txt │ └── lvgl │ └── 官方 LVGL 组件 ├── sdkconfig.defaults └── CMakeLists.txtESP-IDF构建系统会自动把main和components下的每个子目录当作待编译组件前提是每个目录都有CMakeLists.txt并声明了源文件和头文件路径。构建命令没什么特殊的idf.py set-target esp32s3 idf.py menuconfig # 配置 SPI / I2C 引脚与频率 idf.py build idf.py flash -p /dev/ttyUSB0 monitorsdkconfig.defaults里预设了LVGL需要开启的宏比如CONFIG_LV_COLOR_DEPTH_16y和CONFIG_LV_MEM_CUSTOMy。前者对应ST7789的RGB565后者让LVGL使用ESP-IDF的堆管理器避免内存碎片问题。LVGL组件通过IDF组件管理器拉取版本锁定在8.3.x这个版本与ESP-IDF的esp_lcd接口配合最稳定。3. 核心驱动拆解从初始化序列到坐标上报这一章把两条驱动的关键函数逐段讲透。代码不算长但每一行都对应着头疼过的时序问题。3.1 ST7789一条SPI初始化序列与显存刷新ST7789的初始化说白了就是按顺序写寄存器顺序错了或者时序不对屏就是白的或者花的。例程里把初始化命令做成一个表逐条发送// st7789_init_cmds: 命令, 参数个数, 参数... static const uint8_t init_cmds[] { 0x01, 0, // SWRESET 软复位 0x11, 0, // SLPOUT 退出睡眠模式 0x36, 1, 0x00, // MADCTL 扫描方向, 0x00 按屏厂丝印默认 0x3A, 1, 0x05, // COLMOD 像素格式, 0x05 16位 RGB565 0x20, 0, // INVOFF 关闭颜色反转 0x13, 0, // NORON 正常显示模式 0x29, 0, // DISPON 打开显示 }; void st7789_init(void) { st7789_send_cmd(0x01, NULL, 0); vTaskDelay(pdMS_TO_TICKS(120)); // 软复位后必须等待, 否则后续命令丢失 for (int i 0; i sizeof(init_cmds); i (init_cmds[i1] 2)) { st7789_send_cmd(init_cmds[i], init_cmds[i2], init_cmds[i1]); vTaskDelay(pdMS_TO_TICKS(5)); } }st7789_send_cmd内部会先拉低DC引脚表示命令再通过SPI发送发送像素数据时拉高DC。每个命令之间的延时是ST7789数据手册要求的尤其是软复位后那120毫秒省略的话屏幕上大概率只有一片白。如果想要画面翻转直接改MADCTL参数就行比每次都去动XY坐标划算。显存刷新的核心是开窗函数。LVGL每次只刷新一小块区域驱动就要把GRAM的写指针移到那块区域void st7789_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { uint8_t data[4]; data[0] x0 8; data[1] x0 0xFF; data[2] x1 8; data[3] x1 0xFF; st7789_send_cmd(0x2A, data, 4); // CASET 列地址 data[0] y0 8; data[1] y0 0xFF; data[2] y1 8; data[3] y1 0xFF; st7789_send_cmd(0x2B, data, 4); // PASET 行地址 st7789_send_cmd(0x2C, NULL, 0); // RAMWR 开始写显存 }开窗之后连续写入RGB565像素即可。注意分辨率ST7789支持240x320也有的屏是240x280CASET和PASET的参数必须和实际分辨率对齐否则画面会偏移甚至滚动。这个函数会被LVGL的flush回调频繁调用所以性能很关键所有等待都放在SPI硬件层处理CPU不要在这条路径上做像素级操作。3.2 CST328读触摸状态、滤掉噪声、上报坐标CST328的数据读取非常简单往I2C总线上写一个寄存器地址然后连续读6个字节。第一个字节是触摸状态和点数后四个字节是两个触摸点的坐标#define CST328_I2C_ADDR 0x15 #define CST328_REG_TOUCH 0x01 bool cst328_read_point(int16_t *x, int16_t *y, uint8_t *points) { uint8_t buf[5] {0}; // 先写寄存器地址, 再读 5 字节: 状态 点1 X 点1 Y 点2 X 点2 Y i2c_write_read(CST328_I2C_ADDR, CST328_REG_TOUCH, buf, 5); *points buf[0] 0x0F; // 低四位是有效触摸点数 if (*points 0) return false; *x ((buf[1] 0x0F) 8) | buf[2]; // X: 高4位 低8位 *y ((buf[3] 0x0F) 8) | buf[4]; // Y: 高4位 低8位 return true; }坐标是12位的范围在0到4095之间而屏幕分辨率只有240x320。这个比例映射放在LVGL的indev回调里做最简单。还有一点CST328上报的坐标含有噪声手指离开屏幕的瞬间经常出现一个跳变的点。例程里的策略是连续读到两次有效坐标且位置变化小于某个阈值才更新屏幕上的位置相当于一个软件防抖。3.3 LVGL桥接层把两个驱动接进事件循环LVGL本身不知道硬件长什么样它只认两个回调一个是显示刷新一个是触摸输入。把ST7789和CST328接进LVGL就是实现这两个回调void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { st7789_set_window(area-x1, area-y1, area-x2, area-y2); uint32_t size (area-x2 - area-x1 1) * (area-y2 - area-y1 1); st7789_send_pixels((uint16_t *)color_p, size); lv_disp_flush_ready(drv); // 通知 LVGL 可以继续渲染 } void lvgl_touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data) { int16_t x, y; uint8_t points; if (cst328_read_point(x, y, points)) { // CST328 原点在左下角, 屏幕显示原点在左上角, 做 Y 翻转 style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />