跳转到内容

触摸屏集成

触摸屏集成

本节介绍如何在 LVGL 智能家居面板中集成触摸屏功能。学习完成后,您将能够:

  • 配置电容触摸控制器(FT6X36 / CST820 / GT911)
  • 实现触摸事件的读取和处理
  • 理解 LVGL 触摸输入设备驱动的工作方式
  • 处理多点触控和手势识别

在开始本节之前,请确保:

  • 已完成 LVGL 安装和配置
  • 已完成 UI 设计基础学习
  • 触摸屏已正确接线到 ESP32

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;
}
};
#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");
}
// 控件点击事件
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);
// 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);

对于电阻触摸(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;
}
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;
}
};

症状: 屏幕上触摸没有任何反应

原因:

  • I2C 连接错误
  • 触摸控制器初始化失败
  • 触摸与显示坐标不匹配

解决方案:

  1. 使用 I2C 扫描程序检查设备地址
  2. 验证 Wire.begin() 的 SDA/SCL 引脚
  3. 打印触摸原始坐标检查读数

症状: 点击某处但响应位置与实际不符

原因:

  • 触摸屏旋转方向与显示不同
  • 触摸未校准

解决方案:

// 坐标映射
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);

本节介绍了 LVGL 触摸屏的集成方法:

  1. 触摸控制器:FT6X36(电容)和 XPT2046(电阻)的驱动配置
  2. LVGL 输入集成:通过 touch_read_cb 回调将触摸数据接入 LVGL
  3. 事件处理:点击、开关、滑块等控件的触摸事件绑定
  4. 手势识别:左右上下滑动的手势检测
  5. 校准与防抖:电阻触摸校准和软件防抖处理