3、触摸屏驱动与校准:XPT2046/FT6336驱动原理、触摸坐标读取、校准算法实现、手势识别基础

触摸屏,说白了就是中控屏的「眼睛」和「耳朵」。用户点哪里、滑哪里,全靠它来感知。我在做第一版智能家居面板时,就吃过触摸不准的亏——点「开灯」结果关掉了空调,那叫一个尴尬。所以这一章,咱们把触摸屏的驱动和校准彻底讲透。

3.1 两种主流触摸屏:电阻屏 vs 电容屏

先搞清楚你手头用的是哪种。市面上智能家居中控屏,常见两种方案:

类型 代表芯片 原理 特点
电阻式 XPT2046 压力感应,两层导电膜接触 便宜、耐脏、支持手套/手写笔
电容式 FT6336 人体电容感应,多点触控 灵敏、支持手势、透光好

我个人习惯:如果做低成本、户外或工业场景,选XPT2046;如果是家用中控屏,必须上FT6336——用户体验差太多了。

3.2 XPT2046 驱动原理与坐标读取

XPT2046 是一个12位的ADC芯片,通过SPI接口通信。它怎么知道触摸位置?说白了就是测电压分压比。

触摸屏的上下两层导电膜,分别接X轴和Y轴。当手指按下时,两层接触,芯片在X轴加电压,从Y轴读电压,就能算出X坐标;反过来读Y坐标。

核心要点: XPT2046 每次只能读一个轴,所以需要分时读取 X 和 Y。

来看一段我实际项目里用过的驱动代码(ESP-IDF框架):

// XPT2046 SPI 读取函数
uint16_t xpt2046_read_xy(uint8_t cmd) {
    uint8_t buf[3] = {0};
    uint16_t result = 0;
    
    // 片选拉低
    gpio_set_level(TOUCH_CS_PIN, 0);
    
    // 发送命令(例如 0xD0 读X,0x90 读Y)
    spi_transaction_t trans = {
        .length = 24,
        .tx_buffer = buf,
        .rx_buffer = buf,
    };
    buf[0] = cmd;
    spi_device_transmit(spi_handle, &trans);
    
    // 片选拉高
    gpio_set_level(TOUCH_CS_PIN, 1);
    
    // 解析12位数据
    result = ((buf[1] & 0x3F) << 6) | (buf[2] >> 2);
    return result;
}
我的经验: 读XPT2046时,第一次读取的数据往往不准,因为ADC需要稳定时间。我习惯连续读两次,取第二次的值。或者直接加一个1ms的延时。

3.3 FT6336 驱动原理与多点触控

FT6336 就高级多了。它内部自带MCU,直接通过I2C接口给你返回触摸点坐标。你只需要读它的寄存器就行。

它的寄存器映射表长这样:

寄存器地址 功能 说明
0x02 触摸点数 0=无触摸,1=单点,2=两点
0x03 触摸1 X高字节 与0x04组合成12位X坐标
0x05 触摸1 Y高字节 与0x06组合成12位Y坐标
0x09 触摸2 X高字节 同上,第二个触摸点
0x0B 触摸2 Y高字节 同上

读取流程很简单:

  1. 先读0x02寄存器,看有几个触摸点
  2. 如果有触摸,依次读对应点的X、Y坐标
  3. 坐标是12位的,需要把高字节和低字节拼起来
// FT6336 读取触摸点
typedef struct {
    uint16_t x;
    uint16_t y;
    uint8_t  id;
} touch_point_t;

uint8_t ft6336_read_points(touch_point_t *points) {
    uint8_t num = i2c_read_byte(FT6336_ADDR, 0x02);
    if (num == 0 || num > 2) return 0;
    
    for (int i = 0; i < num; i++) {
        uint8_t reg_base = (i == 0) ? 0x03 : 0x09;
        uint8_t buf[4];
        i2c_read_bytes(FT6336_ADDR, reg_base, buf, 4);
        
        points[i].x = ((buf[0] & 0x0F) << 8) | buf[1];
        points[i].y = ((buf[2] & 0x0F) << 8) | buf[3];
        points[i].id = i;
    }
    return num;
}
注意: FT6336 的坐标原点在芯片左下角,而你的屏幕显示原点可能在左上角。记得做Y轴翻转:y = SCREEN_HEIGHT - y

3.4 触摸校准算法实现

为什么需要校准?因为触摸屏的物理坐标和屏幕的显示坐标不是一一对应的。可能有旋转、缩放、偏移。我曾经遇到一个项目,触摸屏贴歪了2毫米,结果边缘按钮死活点不准。

最常用的校准算法是「三点校准法」。原理很简单:

  • 在屏幕三个角显示校准点
  • 用户点击后,记录触摸坐标
  • 通过解线性方程组,算出转换矩阵

转换公式:

X_screen = A * X_touch + B * Y_touch + C
Y_screen = D * X_touch + E * Y_touch + F

三个点,六个未知数,刚好解出来。代码实现:

// 三点校准参数
typedef struct {
    float A, B, C;
    float D, E, F;
} calib_params_t;

calib_params_t calib;

void calib_compute(touch_point_t *touch_pts, screen_point_t *screen_pts) {
    // 解线性方程组,这里用克莱姆法则
    // 具体推导略,网上很多现成代码
    // 我习惯用浮点数计算,精度够用
    float div = ...; // 分母
    calib.A = ...;
    calib.B = ...;
    calib.C = ...;
    calib.D = ...;
    calib.E = ...;
    calib.F = ...;
}

// 校准转换函数
void calib_transform(uint16_t tx, uint16_t ty, uint16_t *sx, uint16_t *sy) {
    *sx = (uint16_t)(calib.A * tx + calib.B * ty + calib.C);
    *sy = (uint16_t)(calib.D * tx + calib.E * ty + calib.F);
}
避坑指南: 我曾经把校准参数直接存到代码里,结果换了一块屏就全废了。正确的做法是:第一次上电时进入校准模式,把参数保存到NVS(非易失存储)中。以后每次启动直接读取。

3.5 手势识别基础

有了触摸坐标,手势识别就是水到渠成的事。手势的本质,就是触摸点的运动轨迹。

我把它分为三个层次:

  1. 单点手势: 点击、长按、滑动
  2. 多点手势: 双指缩放、旋转
  3. 复杂手势: 画圈、字母手势

最简单的滑动识别:

// 滑动方向判断
typedef enum {
    SWIPE_NONE,
    SWIPE_UP,
    SWIPE_DOWN,
    SWIPE_LEFT,
    SWIPE_RIGHT
} swipe_dir_t;

swipe_dir_t detect_swipe(touch_point_t start, touch_point_t end) {
    int16_t dx = end.x - start.x;
    int16_t dy = end.y - start.y;
    
    // 滑动距离太短,忽略
    if (abs(dx) < 20 && abs(dy) < 20) return SWIPE_NONE;
    
    // 判断主方向
    if (abs(dx) > abs(dy)) {
        return (dx > 0) ? SWIPE_RIGHT : SWIPE_LEFT;
    } else {
        return (dy > 0) ? SWIPE_DOWN : SWIPE_UP;
    }
}

双指缩放更简单:记录两个手指的初始距离和当前距离,比值就是缩放因子。

我的建议: 手势识别不要做得太复杂。中控屏不是手机,用户不会在上面画字母。做好「点击」「滑动」「长按」三个手势,就覆盖了90%的交互场景。

3.6 实战中的坑与经验

最后分享几个我踩过的坑:

  • 触摸抖动: 手指按下瞬间,坐标会跳变。加一个「去抖」——连续读三次,取中间值。
  • 边缘不灵敏: 电容屏的边缘灵敏度天生差。可以在驱动层做「边缘补偿」,把边缘区域的坐标映射到更靠内的位置。
  • 休眠唤醒: FT6336 有休眠模式,唤醒后需要重新初始化。我遇到过唤醒后读不到触摸,后来发现是I2C时钟没恢复。
  • 校准参数丢失: 如果NVS被擦除,校准参数就没了。我加了一个「校准标志位」,如果标志位不对,强制进入校准模式。

嗯,这一章内容不少。触摸驱动看似简单,但真正做好、做稳,需要花不少心思。下一章我们开始讲GUI框架的选择与移植,到时候你会看到触摸和显示是怎么配合的。


公众号:蓝海资料掘金营,微信deep3321