触摸屏集成
触摸屏集成
本节介绍如何在 LVGL 智能家居面板中集成触摸屏功能。学习完成后,您将能够:
- 配置电容触摸控制器(FT6X36 / CST820 / GT911)
- 实现触摸事件的读取和处理
- 理解 LVGL 触摸输入设备驱动的工作方式
- 处理多点触控和手势识别
在开始本节之前,请确保:
- 已完成 LVGL 安装和配置
- 已完成 UI 设计基础学习
- 触摸屏已正确接线到 ESP32
Touch Driver Integration
Section titled “Touch Driver Integration”FT6X36 Capacitive Touch
Section titled “FT6X36 Capacitive Touch”FT6X36 是最常用的电容触摸控制器,支持 2 点触控:
#include <Wire.h>
// FT6X36 I2C 地址#define FT6X36_ADDR 0x38#define TOUCH_SDA 21#define TOUCH_SCL 22
// 触摸数据结构typedef struct { uint16_t x; uint16_t y; uint8_t id; // 触摸点 ID uint8_t status; // 触摸状态} TouchPoint;
class FT6X36Touch {private: uint8_t readRegister(uint8_t reg) { Wire.beginTransmission(FT6X36_ADDR); Wire.write(reg); Wire.endTransmission(); Wire.requestFrom(FT6X36_ADDR, (uint8_t)1); return Wire.read(); }
public: bool begin(int sda, int scl) { Wire.begin(sda, scl); delay(100);
// 验证设备 uint8_t chipId = readRegister(0xA3); // 芯片 ID 寄存器 if (chipId != 0x36 && chipId != 0x51) { Serial.printf("FT6X36 未找到 (chip ID: 0x%02X)\n", chipId); return false; } Serial.println("FT6X36 触摸控制器已初始化"); return true; }
// 读取触摸点 int readTouch(TouchPoint* points, int maxPoints) { uint8_t status = readRegister(0x02); // 触摸状态寄存器
if (status == 0) return 0; // 无触摸
int touchCount = status & 0x0F; if (touchCount > maxPoints) touchCount = maxPoints;
for (int i = 0; i < touchCount; i++) { uint8_t base = 0x03 + (i * 6); // 每个触摸点占 6 个寄存器 points[i].x = ((readRegister(base) & 0x0F) << 8) | readRegister(base + 1); points[i].y = ((readRegister(base + 2) & 0x0F) << 8) | readRegister(base + 3); points[i].id = readRegister(base + 5) >> 4; points[i].status = readRegister(0x02) >> 2; // 触摸事件标志 }
return touchCount; }};LVGL Touch Input Integration
Section titled “LVGL Touch Input Integration”Touch Read Callback
Section titled “Touch Read Callback”#include <lvgl.h>
FT6X36Touch touchController;
// 触摸缓冲区(2点触控)TouchPoint touchPoints[2];
// LVGL 触摸读取回调void touch_read_cb(lv_indev_drv_t* drv, lv_indev_data_t* data) { int touchCount = touchController.readTouch(touchPoints, 2);
if (touchCount > 0) { data->state = LV_INDEV_STATE_PR; data->point.x = touchPoints[0].x; data->point.y = touchPoints[0].y;
// 屏幕坐标映射(如果触摸和显示方向不同) // data->point.x = map(touchPoints[0].x, 0, 320, 0, TFT_WIDTH); // data->point.y = map(touchPoints[0].y, 0, 240, 0, TFT_HEIGHT); } else { data->state = LV_INDEV_STATE_REL; }}
// 在 setup() 中注册void setupTouch() { touchController.begin(TOUCH_SDA, TOUCH_SCL);
// 注册触摸输入设备 static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = touch_read_cb; lv_indev_drv_register(&indev_drv);
Serial.println("触摸输入已注册到 LVGL");}Touch Event Handling
Section titled “Touch Event Handling”LVGL Touch Events
Section titled “LVGL Touch Events”// 控件点击事件void button_click_cb(lv_event_t* e) { lv_obj_t* target = lv_event_get_target(e); Serial.println("按钮被点击");
// 切换 LED / 发送 MQTT}
// 开关值变化事件void switch_value_cb(lv_event_t* e) { lv_obj_t* sw = lv_event_get_target(e); bool isOn = lv_obj_has_state(sw, LV_STATE_CHECKED);
if (isOn) { Serial.println("开关: ON"); lv_obj_set_style_bg_color(sw, lv_palette_main(LV_PALETTE_GREEN), LV_STATE_CHECKED); } else { Serial.println("开关: OFF"); lv_obj_set_style_bg_color(sw, lv_color_hex(0x555555), 0); }}
// 滑块触摸滑动事件void slider_changed_cb(lv_event_t* e) { lv_obj_t* slider = lv_event_get_target(e); int value = lv_slider_get_value(slider);
// 更新标签显示 lv_obj_t* label = (lv_obj_t*)lv_event_get_user_data(e); lv_label_set_text_fmt(label, "%d%%", value);
// 发布 MQTT 亮度控制}
// 在 UI 创建时绑定事件lv_obj_add_event_cb(lightBtn, button_click_cb, LV_EVENT_CLICKED, NULL);lv_obj_add_event_cb(switchCtrl, switch_value_cb, LV_EVENT_VALUE_CHANGED, NULL);lv_obj_add_event_cb(slider, slider_changed_cb, LV_EVENT_VALUE_CHANGED, label);Gesture Detection
Section titled “Gesture Detection”// LVGL 内置手势检测void gesture_cb(lv_event_t* e) { lv_indev_wait_release(lv_indev_get_act()); // 等待触摸释放
lv_gesture_t gesture = lv_indev_get_gesture_dir(lv_indev_get_act());
switch (gesture) { case LV_GESTURE_DIR_LEFT: Serial.println("左滑 → 下一页"); switch_to_next_page(); break; case LV_GESTURE_DIR_RIGHT: Serial.println("右滑 → 上一页"); switch_to_prev_page(); break; case LV_GESTURE_DIR_TOP: Serial.println("上滑 → 控制中心"); show_control_center(); break; case LV_GESTURE_DIR_BOTTOM: Serial.println("下滑 → 通知面板"); show_notification_panel(); break; default: break; }}
// 在根屏幕添加手势事件lv_obj_add_event_cb(lv_scr_act(), gesture_cb, LV_EVENT_GESTURE, NULL);Touch Calibration (Resistive Touch)
Section titled “Touch Calibration (Resistive Touch)”XPT2046 Calibration
Section titled “XPT2046 Calibration”对于电阻触摸(XPT2046),通常需要校准:
// 校准数据结构struct CalibrationData { int16_t xMin, xMax; int16_t yMin, yMax;};
CalibrationData cal = {200, 3800, 200, 3800}; // 默认值
// 读取触摸(带校准)bool getTouchCalibrated(uint16_t* x, uint16_t* y) { uint16_t rawX, rawY; bool touched = tft.getTouch(&rawX, &rawY);
if (touched) { // 映射到屏幕坐标 *x = map(rawX, cal.xMin, cal.xMax, 0, TFT_WIDTH - 1); *y = map(rawY, cal.yMin, cal.yMax, 0, TFT_HEIGHT - 1); return true; } return false;}Touch Debouncing
Section titled “Touch Debouncing”Software Debouncing
Section titled “Software Debouncing”class TouchDebouncer {private: unsigned long lastTouchTime; const unsigned long debounceDelay = 50; // 50ms 防抖动
public: bool filter() { unsigned long now = millis(); if (now - lastTouchTime < debounceDelay) { return false; // 过滤抖动 } lastTouchTime = now; return true; }};问题 1: 触摸无响应
Section titled “问题 1: 触摸无响应”症状: 屏幕上触摸没有任何反应
原因:
- I2C 连接错误
- 触摸控制器初始化失败
- 触摸与显示坐标不匹配
解决方案:
- 使用 I2C 扫描程序检查设备地址
- 验证
Wire.begin()的 SDA/SCL 引脚 - 打印触摸原始坐标检查读数
问题 2: 触摸坐标偏移
Section titled “问题 2: 触摸坐标偏移”症状: 点击某处但响应位置与实际不符
原因:
- 触摸屏旋转方向与显示不同
- 触摸未校准
解决方案:
// 坐标映射data->point.x = map(rawX, 0, 4095, 0, TFT_WIDTH);data->point.y = map(rawY, 0, 4095, 0, TFT_HEIGHT);// 或交换 x/y 轴// data->point.x = TFT_HEIGHT - map(rawY, 0, 4095, 0, TFT_HEIGHT);Summary
Section titled “Summary”本节介绍了 LVGL 触摸屏的集成方法:
- 触摸控制器:FT6X36(电容)和 XPT2046(电阻)的驱动配置
- LVGL 输入集成:通过
touch_read_cb回调将触摸数据接入 LVGL - 事件处理:点击、开关、滑块等控件的触摸事件绑定
- 手势识别:左右上下滑动的手势检测
- 校准与防抖:电阻触摸校准和软件防抖处理