04-触摸屏驱动.md 28 KB


title: "Linux 多点电容触摸屏驱动" tags:

  • embedded-linux
  • touchscreen
  • input-subsystem
  • multitouch
  • i2c
  • ft5426
  • gt9147 created: 2026-09-17 updated: 2026-09-17 pdf_ref: "正点原子 I.MX6U嵌入式Linux驱动开发指南 V2.0.1 - 第六十四章" ---

Linux 多点电容触摸屏驱动

1. 触摸屏概述

1.1 电阻式 vs 电容式触摸屏

特性 电阻式触摸屏 电容式触摸屏
工作原理 压力导通两层电阻膜 手指感应电场变化
触摸介质 任何硬物(手指/笔) 仅导体(手指/电容笔)
多点触控 不支持(单点) 支持(多点)
透光率 较低(~75%) 较高(>90%)
使用寿命 约10万次 约5000万次
分辨率 中等
成本 中高
抗干扰 较好 易受电磁干扰

电阻式触摸屏通过按压使上下两层电阻膜接触产生电压信号,适合工业环境。电容式触摸屏通过检测手指触摸时引起的电容变化来定位坐标,是目前智能手机和平板电脑的主流方案。

1.2 I2C 触摸屏工作原理

触摸屏由专用触摸 IC(如 [[03-FT5426触摸驱动|FT5426]]、GT9147)控制,通过 I2C 接口与主控通信:

┌──────────────┐     I2C总线     ┌──────────────┐
│   主控 SoC   │◄──────────────►│   触摸 IC    │
│  (IMX6ULL)   │                │  (FT5426)    │
│              │    INT(中断)    │              │
│              │◄───────────────│              │
│              │    RST(复位)    │              │
│              │───────────────►│              │
└──────────────┘                └──────────────┘

关键信号:

  • SCL/SDA:I2C 时钟和数据线,标准模式 100KHz,快速模式 400KHz
  • INT:中断引脚,触摸 IC 检测到触摸事件后拉低通知主控读取数据
  • RST:复位引脚,低电平有效,用于初始化触摸 IC

1.3 多点触控协议(MT Protocol)

Linux 内核支持两种多点触控协议,定义在 Documentation/input/multi-touch-protocol.txt

Type A 协议

  • 适用于触摸点不能被单独跟踪的设备
  • 通过 SYN_MT_REPORT 分隔每个触摸点的数据
  • 内核需要对所有触摸点进行批量处理
  • 已较少使用,现代设备几乎不再采用

Type B 协议(主流):

  • 适用于硬件可以跟踪每个触摸点的设备
  • 使用 slot 机制标识每个触摸点
  • 通过 ABS_MT_TRACKING_ID 添加/删除触摸点
  • FT5426、GT9147 等主流触摸 IC 均采用此协议

1.4 ABS_MT 事件定义

多点触控相关的事件定义在 include/uapi/linux/input.h

#define ABS_MT_SLOT          0x2f  /* 触摸槽位,Type B 使用 */
#define ABS_MT_TOUCH_MAJOR   0x30  /* 触摸椭圆长轴 */
#define ABS_MT_TOUCH_MINOR   0x31  /* 触摸椭圆短轴 */
#define ABS_MT_WIDTH_MAJOR   0x32  /* 接近椭圆长轴 */
#define ABS_MT_WIDTH_MINOR   0x33  /* 接近椭圆短轴 */
#define ABS_MT_ORIENTATION   0x34  /* 椭圆方向 */
#define ABS_MT_POSITION_X    0x35  /* 触摸点 X 坐标 */
#define ABS_MT_POSITION_Y    0x36  /* 触摸点 Y 坐标 */
#define ABS_MT_TOOL_TYPE     0x37  /* 触摸工具类型 */
#define ABS_MT_BLOB_ID       0x38  /* blob 分组 ID */
#define ABS_MT_TRACKING_ID   0x39  /* 触摸点唯一 ID */
#define ABS_MT_PRESSURE      0x3a  /* 触摸压力 */
#define ABS_MT_DISTANCE      0x3b  /* 悬浮距离 */
#define ABS_MT_TOOL_X        0x3c  /* 工具 X 坐标 */
#define ABS_MT_TOOL_Y        0x3d  /* 工具 Y 坐标 */

最常用的是 ABS_MT_SLOTABS_MT_POSITION_XABS_MT_POSITION_YABS_MT_TRACKING_ID


2. Linux 输入子系统

2.1 输入设备框架

Linux 输入子系统(Input Subsystem)负责管理所有输入设备(键盘、鼠标、触摸屏等),架构分为三层:

┌─────────────────────────────────┐
│          用户空间                │
│   /dev/input/eventX             │
├─────────────────────────────────┤
│         输入核心层               │
│   drivers/input/input.c         │
├────────────┬────────────────────┤
│  事件处理层 │    驱动层          │
│ evdev/kbd  │   touchscreen/     │
│ 等处理器   │   ft5x06.c         │
└────────────┴────────────────────┘

触摸屏驱动处于驱动层,负责将硬件事件转换为输入子系统标准事件上报。

2.2 坐标上报流程

触摸屏坐标上报遵循标准流程:

触摸事件发生
    │
    ▼
中断处理函数被调用
    │
    ▼
通过 I2C 读取触摸坐标
    │
    ▼
调用 input_report_abs() 上报坐标
    │
    ▼
调用 input_sync() 同步数据
    │
    ▼
用户空间从 /dev/input/eventX 读取

2.3 关键事件类型

EV_SYN  // 同步事件,标记一组完整数据
EV_KEY   // 按键事件(BTN_TOUCH 表示触摸按下/抬起)
EV_ABS   // 绝对坐标事件(触摸坐标)

2.4 SYN_REPORT 同步机制

每次触摸事件上报完毕后,必须调用 input_sync() 发送 SYN_REPORT 事件,告诉内核"这是一组完整的数据,请处理"。内核收到 SYN_REPORT 后才会将数据传递给用户空间。


3. 触摸屏设备树配置

3.1 节点格式

触摸屏 IC 通常挂载在 I2C 总线下,设备树节点需要包含:

  • I2C 地址(reg 属性)
  • compatible 兼容字符串
  • 中断引脚配置
  • 复位引脚配置
  • pinctrl 引脚复用

3.2 中断配置

IMX6ULL 的触摸屏中断配置涉及:

  • interrupt-parent:中断所属 GPIO 控制器
  • interrupts:中断号和触发方式(0 = 下降沿触发)
  • interrupt-gpios:中断 GPIO 引脚描述

3.3 pinctrl 配置

/* 中断引脚配置 */
pinctrl_tsc: tscgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080  /* TSC_INT */
    >;
};

/* 复位引脚配置(位于 iomuxc_snvs 节点下) */
pinctrl_tsc_reset: tsc_reset {
    fsl,pins = <
        MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0  /* TSC_RST */
    >;
};

3.4 FT5426 完整设备树示例

&i2c2 {
    clock_frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c2>;
    status = "okay";

    /* FT5426 触摸屏 IC */
    ft5426: ft5426@38 {
        compatible = "edt,edt-ft5426";
        reg = <0x38>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_tsc &pinctrl_tsc_reset>;
        interrupt-parent = <&gpio1>;
        interrupts = <9 0>;
        reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
        interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
    };
};
属性 说明
compatible 用于驱动匹配,需与驱动 of_match_table 一致
reg I2C 从机地址,FT5426 为 0x38
interrupt-parent 中断引脚所属 GPIO 控制器
interrupts <GPIO引脚号 触发方式>,0 为下降沿
reset-gpios 复位引脚 GPIO 描述
interrupt-gpios 中断引脚 GPIO 描述

3.5 GT9147/GT911 设备树示例

gt9147: gt9147@14 {
    compatible = "goodix,gt9147", "goodix,gt9xx";
    reg = <0x14>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_tsc &pinctrl_tsc_reset>;
    interrupt-parent = <&gpio1>;
    interrupts = <9 0>;
    reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
    interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
    status = "okay";
};

4. 触摸屏驱动 API

4.1 input_allocate_device / devm_input_allocate_device

struct input_dev *input_allocate_device(void);
struct input_dev *devm_input_allocate_device(struct device *dev);

申请 input 设备结构体。devm_ 前缀版本将资源绑定到设备,设备移除时自动释放。

4.2 input_set_abs_params

void input_set_abs_params(struct input_dev *dev,
                          unsigned int code,
                          int min, int max,
                          int fuzz, int flat);

设置绝对坐标事件的参数范围。触摸屏需要设置:

  • ABS_MT_POSITION_X:X 坐标范围(0 ~ 屏幕宽度)
  • ABS_MT_POSITION_Y:Y 坐标范围(0 ~ 屏幕高度)

4.3 input_mt_init_slots

int input_mt_init_slots(struct input_dev *dev,
                        unsigned int num_slots,
                        unsigned int flags);

初始化 Type B 多点触控的 slot 数量。num_slots 为最大支持触摸点数,flags 常用值:

  • INPUT_MT_DIRECT:直接输入设备(触摸屏)
  • INPUT_MT_DROP_UNUSED:丢弃未使用的触摸点

4.4 input_mt_slot

void input_mt_slot(struct input_dev *dev, int slot);

Type B 协议专用,设置当前上报的触摸点 slot 编号。触发 ABS_MT_SLOT 事件。

4.5 input_mt_report_slot_state

void input_mt_report_slot_state(struct input_dev *dev,
                                unsigned int tool_type,
                                bool active);

Type B 协专用,上报 ABS_MT_TRACKING_IDABS_MT_TOOL_TYPE 事件:

  • active = true:新增触摸点,内核自动分配 tracking ID
  • active = false:触摸点抬起,内核自动分配 -1
  • tool_type:通常为 MT_TOOL_FINGER

4.6 input_report_abs

void input_report_abs(struct input_dev *dev,
                      unsigned int code, int value);

上报绝对坐标值,如 ABS_MT_POSITION_XABS_MT_POSITION_Y

4.7 input_sync

void input_sync(struct input_dev *dev);

上报 SYN_REPORT 事件,标记一组完整触摸数据结束。

4.8 input_mt_report_pointer_emulation

void input_mt_report_pointer_emulation(struct input_dev *dev,
                                       bool use_count);

兼容单点触摸,上报 BTN_TOOL_TAP 事件供不支持 MT 协议的应用程序使用。

4.9 devm_request_threaded_irq

int devm_request_threaded_irq(struct device *dev, int irq,
                              irq_handler_t handler,
                              irq_handler_t thread_fn,
                              unsigned long irqflags,
                              const char *devname,
                              void *dev_id);

申请线程化中断,中断处理分为上半部(硬中断)和下半部(内核线程)。触摸屏常用:

  • IRQF_TRIGGER_FALLING:下降沿触发
  • IRQF_ONESHOT:中断线程运行期间保持中断屏蔽

5. 完整源码分析

5.1 驱动整体结构

本驱动基于 FT5426 触摸 IC,挂载在 I2C2 总线下,使用 Type B 多点触控协议,最大支持 5 点触摸。

#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>

#define MAX_SUPPORT_POINTS      5       /* 5点触摸 */
#define TOUCH_EVENT_DOWN       0x00    /* 按下 */
#define TOUCH_EVENT_UP         0x01    /* 抬起 */
#define TOUCH_EVENT_ON         0x02    /* 接触 */
#define TOUCH_EVENT_RESERVED   0x03    /* 保留 */

/* FT5X06 寄存器定义 */
#define FT5X06_TD_STATUS_REG   0x02    /* 状态寄存器 */
#define FT5x06_DEVICE_MODE_REG 0x00    /* 模式寄存器 */
#define FT5426_IDG_MODE_REG    0xA4    /* 中断模式 */
#define FT5X06_READLEN         29      /* 读取寄存器个数 */

5.2 设备结构体

struct ft5x06_dev {
    struct device_node *nd;       /* 设备节点 */
    int irq_pin, reset_pin;       /* 中断和复位IO */
    int irqnum;                   /* 中断号 */
    void *private_data;           /* 私有数据 */
    struct input_dev *input;      /* input结构体 */
    struct i2c_client *client;    /* I2C客户端 */
};

static struct ft5x06_dev ft5x06;  /* 全局设备实例 */

5.3 I2C 读写函数

/* 连续读取多个寄存器 */
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg,
                            void *val, int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = dev->client;

    msg[0].addr  = client->addr;     /* FT5426地址 */
    msg[0].flags = 0;                /* 标记为写 */
    msg[0].buf   = &reg;             /* 寄存器首地址 */
    msg[0].len   = 1;

    msg[1].addr  = client->addr;
    msg[1].flags = I2C_M_RD;         /* 标记为读 */
    msg[1].buf   = val;
    msg[1].len   = len;

    ret = i2c_transfer(client->adapter, msg, 2);
    if (ret == 2)
        return 0;
    return -EREMOTEIO;
}

/* 连续写入多个寄存器 */
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg,
                             u8 *buf, u8 len)
{
    u8 b[256];
    struct i2c_msg msg;
    struct i2c_client *client = dev->client;

    b[0] = reg;
    memcpy(&b[1], buf, len);

    msg.addr  = client->addr;
    msg.flags = 0;
    msg.buf   = b;
    msg.len   = len + 1;

    return i2c_transfer(client->adapter, &msg, 1);
}

/* 写单个寄存器 */
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
    u8 buf = data;
    ft5x06_write_regs(dev, reg, &buf, 1);
}

5.4 触摸芯片复位

static int ft5x06_ts_reset(struct i2c_client *client,
                           struct ft5x06_dev *dev)
{
    int ret = 0;

    if (gpio_is_valid(dev->reset_pin)) {
        /* 申请复位IO,默认输出低电平(复位有效) */
        ret = devm_gpio_request_one(&client->dev,
                                    dev->reset_pin,
                                    GPIOF_OUT_INIT_LOW,
                                    "edt-ft5x06 reset");
        if (ret)
            return ret;

        msleep(5);                /* 保持复位5ms */
        gpio_set_value(dev->reset_pin, 1);  /* 释放复位 */
        msleep(300);              /* 等待芯片启动 */
    }
    return 0;
}

5.5 触摸中断处理函数(核心)

static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
    struct ft5x06_dev *multidata = dev_id;
    u8 rdbuf[29];
    int i, type, x, y, id;
    int offset, tplen;
    int ret;
    bool down;

    offset = 1;     /* 偏移量:0x02+1=0x03开始是触摸值 */
    tplen = 6;      /* 每个触摸点占6个寄存器 */

    memset(rdbuf, 0, sizeof(rdbuf));

    /* 从 0x02 寄存器开始连续读取 29 个字节 */
    ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG,
                           rdbuf, FT5X06_READLEN);
    if (ret)
        goto fail;

    /* 上报每一个触摸点坐标 */
    for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
        u8 *buf = &rdbuf[i * tplen + offset];

        /*
         * TOUCH1_XH (地址 0x03) 各位描述:
         *   bit7:6  Event flag 0:按下 1:释放 2:接触 3:无事件
         *   bit5:4  保留
         *   bit3:0  X轴触摸点的11~8位
         */
        type = buf[0] >> 6;       /* 获取触摸类型 */
        if (type == TOUCH_EVENT_RESERVED)
            continue;

        /* 坐标解析(触摸屏与FT5X06方向相反) */
        x = ((buf[2] << 8) | buf[3]) & 0x0fff;
        y = ((buf[0] << 8) | buf[1]) & 0x0fff;

        /*
         * TOUCH1_YH (地址 0x05) 各位描述:
         *   bit7:4  Touch ID,触摸点ID
         *   bit3:0  Y轴触摸点的11~8位
         */
        id = (buf[2] >> 4) & 0x0f;
        down = type != TOUCH_EVENT_UP;

        /* Type B 协议上报 */
        input_mt_slot(multidata->input, id);
        input_mt_report_slot_state(multidata->input,
                                   MT_TOOL_FINGER, down);
        if (!down)
            continue;

        input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
        input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
    }

    input_mt_report_pointer_emulation(multidata->input, true);
    input_sync(multidata->input);

fail:
    return IRQ_HANDLED;
}

坐标解析详解

FT5426 每个触摸点占 6 个字节(寄存器 0x03~0x08),布局如下:

字节0: [type(2bit)][reserved(2bit)][X_h(4bit)]
字节1: [X_l(8bit)]
字节2: [id(4bit)][Y_h(4bit)]
字节3: [Y_l(8bit)]
字节4: [weight]
字节5: [misc]
  • X 坐标 = ((buf[2] << 8) | buf[3]) & 0x0fff:组合高低字节,取低12位
  • Y 坐标 = ((buf[0] << 8) | buf[1]) & 0x0fff:组合高低字节,取低12位
  • 触摸 ID = (buf[2] >> 4) & 0x0f:从 YH 字节高4位提取

5.6 中断初始化

static int ft5x06_ts_irq(struct i2c_client *client,
                         struct ft5x06_dev *dev)
{
    int ret = 0;

    /* 申请中断 GPIO */
    if (gpio_is_valid(dev->irq_pin)) {
        ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
                                    GPIOF_IN, "edt-ft5x06 irq");
        if (ret) {
            dev_err(&client->dev,
                    "Failed to request GPIO %d, error %d\n",
                    dev->irq_pin, ret);
            return ret;
        }
    }

    /* 申请线程化中断 */
    ret = devm_request_threaded_irq(&client->dev, client->irq,
                                    NULL, ft5x06_handler,
                                    IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
                                    client->name, &ft5x06);
    if (ret) {
        dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
        return ret;
    }
    return 0;
}

5.7 probe 函数(驱动入口)

static int ft5x06_ts_probe(struct i2c_client *client,
                           const struct i2c_device_id *id)
{
    int ret = 0;
    ft5x06.client = client;

    /* 1. 获取设备树中的中断和复位引脚 */
    ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node,
                                        "interrupt-gpios", 0);
    ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node,
                                          "reset-gpios", 0);

    /* 2. 复位 FT5x06 */
    ret = ft5x06_ts_reset(client, &ft5x06);
    if (ret < 0)
        goto fail;

    /* 3. 初始化中断 */
    ret = ft5x06_ts_irq(client, &ft5x06);
    if (ret < 0)
        goto fail;

    /* 4. 初始化 FT5X06 寄存器 */
    ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0);
    ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1);

    /* 5. 申请并初始化 input_dev */
    ft5x06.input = devm_input_allocate_device(&client->dev);
    if (!ft5x06.input) {
        ret = -ENOMEM;
        goto fail;
    }
    ft5x06.input->name = client->name;
    ft5x06.input->id.bustype = BUS_I2C;
    ft5x06.input->dev.parent = &client->dev;

    /* 设置事件类型 */
    __set_bit(EV_KEY, ft5x06.input->evbit);
    __set_bit(EV_ABS, ft5x06.input->evbit);
    __set_bit(BTN_TOUCH, ft5x06.input->keybit);

    /* 设置坐标范围 */
    input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
    input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
    input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X, 0, 1024, 0, 0);
    input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y, 0, 600, 0, 0);

    /* 初始化 MT slots */
    ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
    if (ret)
        goto fail;

    /* 注册 input 设备 */
    ret = input_register_device(ft5x06.input);
    if (ret)
        goto fail;

    return 0;

fail:
    return ret;
}

5.8 I2C 驱动框架与模块注册

/* 传统匹配表 */
static const struct i2c_device_id ft5x06_ts_id[] = {
    { "edt-ft5206", 0 },
    { "edt-ft5426", 0 },
    { /* sentinel */ }
};

/* 设备树匹配表 */
static const struct of_device_id ft5x06_of_match[] = {
    { .compatible = "edt,edt-ft5206" },
    { .compatible = "edt,edt-ft5426" },
    { /* sentinel */ }
};

/* I2C 驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name  = "edt_ft5x06",
        .of_match_table = of_match_ptr(ft5x06_of_match),
    },
    .id_table = ft5x06_ts_id,
    .probe    = ft5x06_ts_probe,
    .remove   = ft5x06_ts_remove,
};

/* 驱动入口 */
static int __init ft5x06_init(void)
{
    return i2c_add_driver(&ft5x06_ts_driver);
}

/* 驱动出口 */
static void __exit ft5x06_exit(void)
{
    i2c_del_driver(&ft5x06_ts_driver);
}

module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

5.9 Type B 时序详解

按下时的上报时序

ABS_MT_SLOT      value=0      ← 选择触摸点0
ABS_MT_TRACKING_ID value=5    ← 分配跟踪ID
ABS_MT_POSITION_X  value=1004 ← X坐标
ABS_MT_POSITION_Y  value=23   ← Y坐标
BTN_TOUCH         value=1     ← 按下
ABS_X             value=1004  ← 兼容单点(指针模拟)
ABS_Y             value=23    ← 兼容单点(指针模拟)
SYN_REPORT        value=0     ← 同步

抬起时的上报时序

ABS_MT_TRACKING_ID  value=-1  ← 释放触摸点
BTN_TOUCH           value=0   ← 抬起
SYN_REPORT          value=0   ← 同步

6. 触摸校准

6.1 为什么需要校准

  • 电阻式触摸屏:必须校准,存在坐标漂移
  • 电容式触摸屏:出厂已校准,但加工精度问题可能仍需微调
  • 触摸屏与 LCD 未完全对齐时也需要校准

6.2 五点校准算法

五点校准选取屏幕四角和中心共 5 个校准点:

  (0,0)─────────────(W,0)
    │                   │
    │      (W/2,H/2)    │
    │                   │
  (0,H)─────────────(W,H)

算法步骤:

  1. 显示 5 个校准点,依次触摸
  2. 记录触摸屏原始坐标 (rx_i, ry_i) 和期望的 LCD 坐标 (lx_i, ly_i)
  3. 使用最小二乘法拟合变换矩阵:

    lx = A * rx + B * ry + C
    ly = D * rx + E * ry + F
    
  4. 求解 6 个系数 A~F

  5. 保存校准数据到文件

6.3 校准数据存储

tslib 将校准数据保存在 /etc/pointercal 文件中:

<A> <B> <C> <D> <E> <F> <screen_width> <screen_height>

运行 ts_calibrate 命令可进行交互式校准,校准完成后自动生成此文件。


7. 实验验证

7.1 编译与加载驱动

# 编译驱动模块
make -j32

# 复制模块到开发板
cp ft5x06.ko /nfs/rootfs/lib/modules/4.1.15/

# 开发板上加载
depmod                              # 首次加载需执行
modprobe ft5x06.ko                  # 加载驱动模块

加载成功后会生成 /dev/input/eventX 设备文件。

7.2 hexdump 原始数据查看

hexdump /dev/input/event2

触摸屏幕并观察输出,每行数据格式为:

tv_sec  tv_usec  type  code  value
02bb    9459    0003  002f  00000000    ← ABS_MT_SLOT 0
02bb    9459    0003  0039  00000005    ← ABS_MT_TRACKING_ID 5
02bb    9459    0003  0035  000003ec    ← ABS_MT_POSITION_X 1004
02bb    9459    0003  0036  00000017    ← ABS_MT_POSITION_Y 23
02bb    9459    0001  014a  00000001    ← BTN_TOUCH 按下
02bb    9459    0000  0000  00000000    ← SYN_REPORT

7.3 tslib 测试

# 安装 tslib 后配置环境变量
export TSLIB_TSDEVICE=/dev/input/event2
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0

# 校准(电容屏可选)
ts_calibrate

# 多点触摸测试
ts_test_mt

ts_test_mt 提供三种模式:

  • Drag:拖拽模式,一个手指一个十字光标
  • Draw:绘制模式,在屏幕上画线验证多点触摸
  • Quit:退出

7.4 evtest 命令

# 查看输入设备列表
cat /proc/bus/input/devices

# 实时查看事件
evtest /dev/input/event2

7.5 将驱动编译到内核

# 1. 复制驱动文件
cp ft5x06.c (内核源码)/drivers/input/touchscreen/

# 2. 修改 Makefile
echo "obj-y += ft5x06.o" >> drivers/input/touchscreen/Makefile

# 3. 重新编译内核
make -j32

8. 跨平台对比

8.1 IMX6ULL vs STM32 vs RK3568 触摸驱动差异

特性 IMX6ULL STM32MP1 RK3568
I2C 控制器 I2C1~I2C4 I2C1~I2C6 I2C1~I2C5
设备树格式 标准 Linux DT 标准 Linux DT 标准 Linux DT
中断控制器 GIC + GPIO GIC + GPIO GIC + GPIO
中断线程化 devm_request_threaded_irq 同左 同左
触摸 IC FT5426/GT9147 FT5336/GT911 GT911/FT5426
I2C 频率 100K/400K 100K/400K 100K/400K/1M
主线内核支持 需补丁 较好 较好

8.2 驱动代码通用性

触摸屏驱动的核心逻辑(input 子系统 + MT 协议)在各平台间完全通用,差异仅在于:

  • 设备树中的 GPIO/中断号不同
  • I2C 控制器驱动不同
  • 部分 SoC 的 pinctrl 配置语法略有差异

驱动框架代码可以跨平台复用,只需修改设备树即可适配新平台。


9. 面试精选

Q1: Type A 和 Type B 多点触控协议的核心区别是什么?为什么现在主流使用 Type B?

:Type A 协议适用于硬件无法单独跟踪触摸点的设备,每个触摸点上报后用 SYN_MT_REPORT 分隔,内核需要根据坐标聚类来确定哪些事件属于同一个触摸点,处理开销大且精度低。Type B 协议通过 slot 机制和 ABS_MT_TRACKING_ID 为每个触摸点分配唯一标识,硬件可以跟踪并维护触摸点的生命周期,内核直接按 slot 处理,效率高且准确。现代触摸 IC(如 FT5426、GT911)都支持硬件级别的触摸点跟踪,因此 Type B 成为主流。

Q2: 为什么触摸屏驱动使用 devm_request_threaded_irq 而不是 request_irq

:有三个原因:(1) 触摸屏中断频率高且持续时间长(手指放在屏幕上会不断产生中断),中断线程化后可以设置优先级,避免阻塞高优先级任务;(2) devm_ 前缀版本申请的资源由设备模型自动管理,驱动卸载时无需手动 free_irq,代码更简洁;(3) 触摸数据处理(I2C 读取 + 坐标解析)相对耗时,放在内核线程中可以与进程公平调度,避免长时间占用硬件中断上下文。

Q3: input_mt_init_slotsflags 参数 INPUT_MT_DIRECTINPUT_MT_POINTER 有什么区别?

INPUT_MT_DIRECT 表示直接输入设备(如触摸屏),触摸点坐标直接对应屏幕坐标,内核会跳过一些坐标转换逻辑。INPUT_MT_POINTER 表示间接指针设备(如触摸板),触摸点坐标需要映射到屏幕坐标,内核会进行额外的坐标变换和指针模拟。触摸屏应使用 INPUT_MT_DIRECT,触摸板应使用 INPUT_MT_POINTER

Q4: FT5426 触摸中断处理函数中,坐标解析为什么要将两个字节组合并取低 12 位?

:FT5426 每个触摸点的 X 和 Y 坐标各占 12 位(最大值 4095),但寄存器以 8 位为单位存储。以 X 坐标为例,高 4 位存储在 TOUCH_XH 寄存器的低 4 位(bit3:0),低 8 位存储在 TOUCH_XL 寄存器。读取时将两个字节组合:(high << 8) | low 得到 16 位值,再 & 0x0fff 取低 12 位得到有效坐标。触摸屏与 IC 方向相反时还需要做坐标映射。

Q5: 如何将触摸屏驱动从模块编译改为编译进内核?需要注意什么?

:步骤:(1) 将驱动文件复制到 drivers/input/touchscreen/;(2) 修改该目录下的 Makefile,添加 obj-y += ft5x06.o(而非 obj-m);(3) 重新编译内核。需要注意:(a) 驱动编译进内核后,设备树中的 compatible 字符串必须与驱动的 of_match_table 匹配;(b) 确保设备树中触摸屏节点的 IO 配置正确且未被其他外设占用;(c) 编译进内核后,触摸屏设备的 event 编号可能与模块方式不同,需重新确认。