适用开发板:ESP32-S3 Korvo 2 V3
工程:Korvo_Firmware/02.beginner.spi_lcd_touch
target:esp32s3
硬件:ST7789 SPI 240×280 + CST816S I2C + TCA9554 背光
学习时长:约 25 分钟 · 难度:★★☆☆☆
相关:CST816触摸、LVGL V9
学习目标
完成本节后,你将能够:
- 理解 SPI LCD 的初始化流程(总线 → 面板 → 背光)
- 掌握 I2C 触摸屏驱动的集成方法
- 了解 LVGL 移植的三要素:flush 回调、tick 定时器、任务循环
- 区分「手写移植」与「BSP 方式」的优劣
注意:P4C5 使用 MIPI 大屏,不是本例程,见 P4C5 mipi_lcd_touch。
1. 这个例子在干嘛
这是 Korvo 所有图形例程的 基础——手动完成「SPI LCD 初始化 + I2C 触摸 + LVGL 移植」的最小闭环。理解本例后,后续的 MP3 播放器、摄像头预览、人脸识别等带 UI 的例程就很容易看懂了。
它做了以下事情:
app_main
├── 1. SPI 总线初始化 (spi_bus_initialize)
├── 2. ST7789 面板驱动创建 (esp_lcd_panel_*)
├── 3. TCA9554 I/O 扩展器开背光
├── 4. I2C 初始化 + CST816S 触摸驱动
├── 5. LVGL 初始化
│ ├── lv_init()
│ ├── 注册 display(flush 回调 → SPI 写屏)
│ ├── 注册 indev(read 回调 → I2C 读触摸)
│ └── esp_timer 提供 tick
└── 6. 启动 Demo → lv_demo_music()(或 Meter 仪表)
2. 硬件接线一览
| 信号 | GPIO | 说明 |
|---|---|---|
| LCD CLK (SCK) | 1 | SPI 时钟 |
| LCD MOSI (SDO) | 0 | SPI 数据 |
| LCD DC | 2 | 数据/命令切换 |
| LCD CS | 46 | 片选 |
| 触摸 SCL | 18 | I2C 时钟 |
| 触摸 SDA | 17 | I2C 数据 |
| 背光控制 | TCA9554 | 通过 I/O 扩展器控制,不是直接 GPIO |
TCA9554 小知识:Korvo 板上多个功能(背光、PA 使能、LED 等)共用一颗 TCA9554 I/O 扩展器。操作背光需要先初始化 I2C,再通过 TCA9554 寄存器设置对应 bit。详见 TCA9554 教程。
3. 编译烧录
cd Korvo_Firmware\02.beginner.spi_lcd_touch
idf.py set-target esp32s3
idf.py build flash monitor
4. 运行现象
| 现象 | 说明 |
|---|---|
| 默认画面 | 跑 lv_demo_music(),有动画和触摸交互 |
| 触摸操作 | 可点击播放/暂停、滑动切歌 |
| Meter 仪表盘 | 工程内 lvgl_demo_ui.c 有仪表 Demo,默认未调用 |
想切换到 Meter Demo? 修改 app_main.c,将 lv_demo_music() 替换为 lvgl_demo_ui_init() 即可。
5. 软件架构详解
┌──────────────────────────────────────────┐
│ lv_demo_music() │
├──────────────────────────────────────────┤
│ LVGL 核心 │
├──────────────┬───────────────────────────┤
│ Display │ Input Device │
│ flush_cb │ read_cb │
│ (SPI 写屏) │ (I2C 读触摸) │
├──────────────┼───────────────────────────┤
│ ST7789 │ CST816S │
│ SPI Bus │ I2C Bus │
│ GPIO 0/1/2 │ GPIO 17/18 │
└──────────────┴───────────────────────────┘
↑
esp_timer → lv_tick_inc() (每 ms)
FreeRTOS Task → lv_timer_handler() (主循环)
LVGL 移植三要素(面试也常考):
- Display flush:告诉 LVGL「画好的像素怎么送到屏幕」
- Tick:告诉 LVGL「时间在流逝」(动画、手势判断靠它)
- Handler 循环:定期调用
lv_timer_handler()让 LVGL 处理事件和刷新
与产品工程的关系:后续 Korvo 例程多使用
ksdiy_example_display或ksdiy_korvo_bsp封装以上所有初始化,一行代码搞定。学习本例的意义是 理解底层。
6. 关键代码片段解析
6.1 SPI LCD 初始化流程
spi_bus_initialize() // 配置 SPI 总线(CLK、MOSI)
esp_lcd_new_panel_io_spi() // 在总线上创建面板 I/O(加 DC、CS)
esp_lcd_new_panel_st7789() // 用面板 I/O 创建 ST7789 驱动
esp_lcd_panel_init() // 发送初始化命令序列
6.2 触摸初始化
i2c_master_bus_new() // 创建 I2C 总线
esp_lcd_touch_new_i2c_cst816s() // 创建触摸驱动实例
6.3 LVGL flush 回调核心逻辑
void flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
// 1. 计算区域宽高
// 2. esp_lcd_panel_draw_bitmap(panel, x1, y1, x2+1, y2+1, px_map)
// 3. lv_display_flush_ready(disp) ← 必须调用!否则 LVGL 卡死
}
7. 常见问题与排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 白屏/无背光 | TCA9554 背光未开;SPI 线序错 | 检查 I2C 初始化是否在 SPI 之前;核对 GPIO |
| 有背光但无图 | SPI CLK/MOSI/DC/CS 接错 | 对照原理图逐个确认 |
| 触摸无反应 | I2C 地址不对;GPIO 17/18 被占 | 用 i2c_scanner 例程确认 CST816S 地址 |
| 触摸坐标偏移 | 屏幕旋转后坐标未同步 | LVGL indev 中配置与 display rotation 一致 |
| 画面抖动/撕裂 | 刷新频率高但 SPI 带宽不足 | 提高 SPI CLK 或减小刷新区域 |
8. 动手改造建议
- 切换 Demo:把
lv_demo_music()换成lv_demo_widgets()看控件效果 - 自建 UI:创建一个按钮 + 标签,点击按钮改变标签文字
- 观察 flush:在 flush 回调中加
printf打印区域坐标,理解 LVGL 的脏区域刷新机制 - 禁用触摸:注释掉 indev 注册代码,观察 Demo 仍能自动播放动画(说明触摸和显示是独立的)
9. 知识延伸
- SPI 模式:ST7789 通常使用 SPI Mode 0,部分屏需要 Mode 3,这在
esp_lcd_panel_io_spi_config_t中配置 - 颜色深度:LVGL 支持 16 位(RGB565)和 32 位(ARGB8888),SPI 小屏推荐 16 位以节省带宽
- DMA:SPI 传输建议开启 DMA,flush 时 CPU 无需等待数据发送完毕
10. 下一步
| 方向 | 推荐例程 | 学到什么 |
|---|---|---|
| BSP 方式跑 LVGL | lvgl_esp_adapter | 产品级集成方式 |
| LVGL V9 深入 | 03.development.lvgl_v9 | 更多控件和主题 |
| MP3 播放器 | MP3 | LVGL + 音频 |
酷世DIY · Kevincoooool