--- 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 电容式触摸屏 > ⚠️ **来源说明**:原书第六十四章仅在 64.1.1 小结中回顾电容触摸原理(并指向第二十八章裸机实验),本节对比表为扩展知识。 | 特性 | 电阻式触摸屏 | 电容式触摸屏 | | -------- | ------------------- | --------------------- | | 工作原理 | 压力导通两层电阻膜 | 手指感应电场变化 | | 触摸介质 | 任何硬物(手指/笔) | 仅导体(手指/电容笔) | | 多点触控 | 不支持(单点) | 支持(多点) | | 透光率 | 较低(~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`: ```c #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_SLOT`、`ABS_MT_POSITION_X`、`ABS_MT_POSITION_Y` 和 `ABS_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 关键事件类型 ```c 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`:``。第 2 个 cell `0` 表示 `IRQ_TYPE_NONE`(**不指定触发方式**),实际触发方式由驱动程序 `request` 时的 `IRQF_TRIGGER_FALLING` 决定,即下降沿触发 - `interrupt-gpios`:中断 GPIO 引脚描述 ### 3.3 pinctrl 配置 ```dts /* 中断引脚配置 */ 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 完整设备树示例 ```dts &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` | ``,0 为下降沿 | | `reset-gpios` | 复位引脚 GPIO 描述 | | `interrupt-gpios` | 中断引脚 GPIO 描述 | ### 3.5 GT9147/GT911 设备树示例 ```dts 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 ```c 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 ```c 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 ```c int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots, unsigned int flags); ``` 初始化 Type B 多点触控的 slot 数量。`num_slots` 为最大支持触摸点数,`flags` 可选值(定义于 `include/uapi/linux/input.h`,可用 `|` 组合): ```c #define INPUT_MT_POINTER 0x0001 /* 间接指针设备,如触摸板 */ #define INPUT_MT_DIRECT 0x0002 /* 直接输入设备,如触摸屏 */ #define INPUT_MT_DROP_UNUSED 0x0004 /* 丢弃本帧未出现的触摸点 */ #define INPUT_MT_TRACK 0x0008 /* 使用内核内部tracking */ #define INPUT_MT_SEMI_MT 0x0010 /* 半MT设备,手指数量手动处理 */ ``` 原书调用示例:`input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);`(`0` 表示不附加任何 flag)。 ### 4.4 input_mt_slot ```c void input_mt_slot(struct input_dev *dev, int slot); ``` Type B 协议专用,设置当前上报的触摸点 slot 编号。触发 `ABS_MT_SLOT` 事件。 ### 4.5 input_mt_report_slot_state ```c void input_mt_report_slot_state(struct input_dev *dev, unsigned int tool_type, bool active); ``` Type B 协专用,上报 `ABS_MT_TRACKING_ID` 和 `ABS_MT_TOOL_TYPE` 事件: - `active = true`:新增触摸点,内核自动分配 tracking ID - `active = false`:触摸点抬起,内核自动分配 -1 - `tool_type`:通常为 `MT_TOOL_FINGER` ### 4.6 input_report_abs ```c void input_report_abs(struct input_dev *dev, unsigned int code, int value); ``` 上报绝对坐标值,如 `ABS_MT_POSITION_X`、`ABS_MT_POSITION_Y`。 ### 4.7 input_sync ```c void input_sync(struct input_dev *dev); ``` 上报 `SYN_REPORT` 事件,标记一组完整触摸数据结束。 ### 4.8 input_mt_report_pointer_emulation ```c void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count); ``` 指针模拟,供不支持 MT 协议的单点应用程序使用。`use_count` 参数: - `true`:上报**当前有效触摸点数量**(内核自动读取,无需用户指定),同时模拟 `ABS_X`/`ABS_Y`/`BTN_TOUCH` 事件; - `false`:**硬件追踪到的触摸点数量多于当前上报数量**,此时驱动应先用 `BTN_TOOL_*TAP` 事件告知用户空间追踪到的触摸点总数,再传 `false`。 ### 4.9 devm_request_threaded_irq ```c 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 点触摸。 ```c #include #include #include #include #include #include #include #include #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 设备结构体 ```c 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 读写函数 ```c /* 连续读取多个寄存器 */ 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 = ® /* 寄存器首地址 */ 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 触摸芯片复位 ```c 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 触摸中断处理函数(核心) ```c 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 中断初始化 ```c 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 函数(驱动入口) ```c 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 驱动框架与模块注册 ```c /* 传统匹配表 */ 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. 触摸校准 > ⚠️ **来源说明**:原书第六十四章仅介绍使用 tslib 的 `ts_calibrate` 进行校准,本节中的五点校准算法、最小二乘拟合与 `/etc/pointercal` 格式为扩展知识。 ### 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` 文件中: ``` ``` 运行 `ts_calibrate` 命令可进行交互式校准,校准完成后自动生成此文件。 --- ## 7. 实验验证 ### 7.1 编译与加载驱动 ```bash # 编译驱动模块 make -j32 # 复制模块到开发板 cp ft5x06.ko /nfs/rootfs/lib/modules/4.1.15/ # 开发板上加载 depmod # 首次加载需执行 modprobe ft5x06.ko # 加载驱动模块 ``` 加载成功后会生成 `/dev/input/eventX` 设备文件。 ### 7.2 hexdump 原始数据查看 ```bash hexdump /dev/input/event2 ``` 单指触摸右上角再抬起,原书实测原始数据(整理后)为: ``` /* 编号 */ /* tv_sec */ /* tv_usec */ /* type */ /* code */ /* value */ 0000000 02bb 0000 9459 0007 0003 002f 0000 0000 ← ABS_MT_SLOT 0 0000010 02bb 0000 9459 0007 0003 0039 0005 0000 ← ABS_MT_TRACKING_ID 5 0000020 02bb 0000 9459 0007 0003 0035 03ec 0000 ← ABS_MT_POSITION_X 1004 0000030 02bb 0000 9459 0007 0003 0036 0017 0000 ← ABS_MT_POSITION_Y 23 0000040 02bb 0000 9459 0007 0001 014a 0001 0000 ← BTN_TOUCH 按下 0000050 02bb 0000 9459 0007 0003 0000 03ec 0000 ← ABS_X 1004(指针模拟) 0000060 02bb 0000 9459 0007 0003 0001 0017 0000 ← ABS_Y 23(指针模拟) 0000070 02bb 0000 9459 0007 0000 0000 0000 0000 ← SYN_REPORT 0000080 02bb 0000 e5f8 0008 0003 0039 ffff ffff ← ABS_MT_TRACKING_ID -1(抬起) 0000090 02bb 0000 e5f8 0008 0001 014a 0000 0000 ← BTN_TOUCH 抬起 00000a0 02bb 0000 e5f8 0008 0000 0000 0000 0000 ← SYN_REPORT ``` 其中 `ABS_X`/`ABS_Y`(code 0x0000/0x0001)由 `input_mt_report_pointer_emulation()` 为单点应用模拟上报;`ABS_MT_TRACKING_ID = -1` 表示触摸点离开。 ### 7.3 tslib 测试 原书 tslib 环境变量写入 `/etc/profile`,并将驱动编入内核后触摸设备设为 `/dev/input/event1`;若使用模块方式则 event 编号可能为 `event2`,需按实际情况修改 `TSLIB_TSDEVICE`。 ```bash # 安装 tslib 后配置环境变量(写入 /etc/profile) 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 命令 ```bash # 查看输入设备列表 cat /proc/bus/input/devices # 实时查看事件 evtest /dev/input/event2 ``` ### 7.5 将驱动编译到内核 原书对应第 64.4.3 节,步骤如下: ```bash # 1. 复制驱动文件到触摸屏驱动目录 cp ft5x06.c (内核源码)/drivers/input/touchscreen/ -f # 2. 修改 drivers/input/touchscreen/Makefile,末尾添加 # obj-y += ft5x06.o # 3. 重新编译内核,用新 zImage 启动 make -j32 ``` > 原书提醒:编译为内核后触摸屏的 event 编号会变化(模块时为 `event2`,编入内核后变为 `event1`),需重新确认。 ### 7.6 使用内核自带的 edt-ft5x06 驱动(原书 64.6 节) 内核自带 `drivers/input/touchscreen/edt-ft5x06.c`,可驱动 FT5206/FT5406/FT5426。使用步骤: 1. **移除自定义驱动**:删除 `drivers/input/touchscreen/Makefile` 中的 `obj-y += ft5x06.o` 2. **使能内核驱动**:`make menuconfig` → `Device Drivers → Input device support → Touchscreens` → `<*> EDT FocalTech FT5x06 I2C Touchscreen support` 3. **修改设备树**:内核原生 `edt_ft5x06_of_match` 只支持三个 compatible: ```c static const struct of_device_id edt_ft5x06_of_match[] = { { .compatible = "edt,edt-ft5206", }, { .compatible = "edt,edt-ft5306", }, { .compatible = "edt,edt-ft5406", }, { /* sentinel */ } }; ``` 因此需给 `ft5426` 节点补充一条(三选一或改驱动匹配表): ```dts ft5426: ft5426@38 { compatible = "edt,edt-ft5426","edt,edt-ft5406"; reg = <0x38>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_tsc>; interrupt-parent = <&gpio1>; interrupts = <9 0>; reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; }; ``` ### 7.7 GT9147 / GT911 驱动(原书 64.7、64.8 节) 正点原子 4.3 寸屏(800×480、480×272)触摸 IC 为 **GT9147**;新版 7 寸屏换用 **GT911**。GT9147、GT911、GT1151 三款 IC 几乎一致,可直接用同一驱动文件 `gt9147.c`。 - GT9147/GT911 设备树节点:`compatible = "goodix,gt9147", "goodix,gt9xx";`,`reg = <0x14>` - 4.3寸屏 lcdif 时序(原书 64.7.3):480×272 用 `clock-frequency=<9000000>`;800×480 用 `clock-frequency=<31000000>` - 原书说明:`gt9147.c` 实现的是**单点触摸**,因为 GT9147 硬件无法检测每个触摸点的按下/抬起,多点上报效果不理想 --- ## 8. 跨平台对比 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 ### 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. 面试精选 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 ### 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_slots` 的 `flags` 参数 `INPUT_MT_DIRECT` 和 `INPUT_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 编号可能与模块方式不同,需重新确认。 --- **内容来源**: 《I.MX6U嵌入式Linux驱动开发指南》第64章 Linux 多点电容触摸屏实验(可参考第28章 多点电容触摸屏实验)