多点电容触摸简介
我手上使用的屏幕是 TFT LCD + 触摸屏组合起来的,底下是 LCD 面板,上面是触摸面板,将两个封装到一起就成了带有触摸屏的 LCD 屏幕。电容触摸屏也需要一个驱动 IC,驱动 IC 一般会提供一个 I2C 接口给主控制器,主控制器可以通过 I2C 接口来读取驱动 IC 的数据。我手上使用的触摸控制 IC 是 GT1151。
多点触摸(MT)协议
我们已经知道以下几点:
①、电容触摸屏是 I2C 接口,需要触摸 IC。
②、触摸 IC 提供了中断信号引脚 (INT),可以通过中断来获取触摸信息。
③、电容触摸屏得到的是触摸位置绝对信息以及触摸屏是否有按下。
结合以上几点我们可以得出电容触摸屏驱动起始就是以以下几种 Linux 驱动框架组合起来的:
①、I2C 设备驱动,因为电容触摸 IC 基本都是 I2C 接口,因此大框架就是 I2C 设备驱动。
②、通过中断引脚向 Linux 内核上报触摸信息,因此需要用到 Linux 的中断驱动框架。坐标的上报在中断服务函数中完成。
③、触摸屏的坐标信息、屏幕按下和抬起信息属于 Linux 的 input 子系统,因此向 Linux 内核上报触摸屏坐标信息就得用 input 子系统。
接下来我们来学习 input 子系统下的多点电容触摸协议。
触摸点的信息通过一系列的 ABS_MT 事件上报给 Linux 内核,只有 ABS_MT 事件是用于多点出没的,ABS_MT 时间定义在文件 include/dt-bindings/input/linux-event-codes.h 中,如下
1 |
在这么多的 ABS_MT 事件中,我们最常用的就是 ABS_MT_SLOT、ABS_MT_POSITION_X、ABS_MT_POSITION_Y 和 ABS_MT_TRACKING_ID。其中 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y,用来上报触摸点的(X,Y)坐标信息,ABS_MT_SLOT 用来上报触摸点的 ID。对于能区分触摸点的设备,使用 ABS_MT_TRACKING_ID 来区分触摸点。
对于能区分触摸点的设备,我们上报触摸点信息的时候需要通过 input_mt_slot() 函数区分是哪一个触摸点,其原型如下
1 | static inline void input_mt_slot(struct input_dev *dev, int slot) |
此函数有两个参数,第一个参数是 input_dev 设备,第二个参数 slot 用于指定当前上报的是哪个触摸点信息。input_mt_slot() 函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点的数据。
不管是什么类型的设备,最终都要调用 input_sync 函数来表示多点触摸信息传输完成,告诉接收者处理之前累积的所有信息,并准备好下一次接收。对于能区分触摸点的设备,其使用 slot 协议区分具体的触摸点,slot 需要用到 ABS_MT_TRACKING_ID 信息,这个 ID 需要硬件提供,这个设备需要给每个识别出的触摸点分配一个 slot,后面使用这个 slot 上报信息。设备驱动给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息,可以通过 slot 的 ABS_MT_TRACKING_ID 来新增、替换或删除触摸点。一个非负数的 ID 表示一个有效的触摸点,-1 这个 ID 表示未使用 slot。一个以前不存在的 ID 表示这是一个新加的触摸点,一个 ID 如果在也不存在就表示删除了。
对于能区分触摸点的设备驱动,发送触摸点信息的时序如下,
1 | ABS_MT_SLOT 0 |
首先上报 ABS_MT_SLOT 事件,也就是触摸点对应的 SLOT。每次上报一个触摸点坐标是前要先用 input_mt_slot 函数上报当前触摸点 SLOT,触摸点的 SLOT 就是触摸点 ID,需要由触摸 IC 提供。接下来根据要求,每个 SLOT 必须关联一个 ABS_MT_TRACKING_ID,通过修改 SLOT 关联的 ABS_MT_TRACKING_ID 来完成对触摸点的添加、替换和删除。具体用到的函数就是 input_mt_report_slot_state,如果是添加一个新的触摸点,那么此函数第三个参数要设置为 true,Linux 内核会自动分配一个 ABS_MT_TRACKING_ID 的值。然后就是上报触摸点的 X、Y 坐标。最后需要上报 SYN_REPORT 事件以保证同步。
当一个触摸点移除后,同样需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理,时序如下所示
1 | ABS_MT_TRACKING_ID -1 |
ili210x.c 文件上报触摸坐标信息函数如下:
1 | static bool ili210x_report_events(struct ili210x *priv, u8 *touchdata) |
我们后续编写代码也是按照这个框架来编写。
多点触摸所使用的 API 函数
1、input_mt_init_slots 函数
input_mt_init_slots 函数用于初始化 MT 的输入 slots,编写 MT 驱动的时候必须先调用此函数初始化 slots,此函数原型如下所示
1 | int input_mt_init_slots(struct input_dev *dev, unsigned int num_slots,unsigned int flags) |
dev 表示 MT 设备对应的 input_dev,因为 MT 设备属于 input_dev;num_slots 表示设备要使用的 SLOT 数量,也就是触摸点的数量;flags 表示其他一些flags 信息。返回值为 0 成功。
2、input_mt_slot
此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据,此函数原型如下:
1 | static inline void input_mt_slot(struct input_dev *dev, int slot) |
其中,dev 就是 MT 设备对应的 input_dev,slot 就是当前发送的触摸点信息。
3、input_mt_report_slot_state
此函数用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE 事件,ABS_MT_TRACKING_ID 事件给 slot 关联一个 ABS_MT_TRACKING_ID,ABS_MT_TOOL_TYPE 事件指定触摸类型。
1 | bool input_mt_report_slot_state(struct input_dev *dev, unsigned int tool_type, bool active) |
其中,dev 表示 MT 设备对应的 input_dev。tool_type 表示触摸类型,可以选择手指、笔或手掌等等。active 为 true 时就表示连续触摸,input 子系统内核会自动分配一个 ABS_MT_TRACKING_ID 给 slot。false 表示触摸点抬起。
4、input_report_abs
所有触摸设备都使用此函数来上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 实现坐标信息上报。其函数原型如下:
1 | static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value) |
5、input_mt_report_pointer_emulation 函数
若追踪到的触摸点数量大于当前上报的数量,驱动程序用 BTN_TOOL_TAP 事件来通知用户空间当前追踪到的触摸点总数,然后调用 input_mt_report_pointer_emulation 函数将 use_count 参数设置为 false,否则将 use_count 参数设置为 true,表示当前的触摸点数量。函数原型如下:
1 | void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count); |
其中,dev 就是 MT 设备对应的 input_dev,use_count true 表示有效的触摸点数量,false 表示追踪到的触摸点数量大于当前上报的数量。
多点电容触摸驱动框架
综上所述,多点电容触摸框架需要以下几个内容:
1、主框架为 I2C。
2、Linux 中多用中断来上报触摸点坐标信息,使用到中断框架。
3、input 子系统框架
4、在中断处理中使用 Linux 的 MT 协议上报坐标信息。
接下来我们逐个回顾框架如何编写
1、I2C 驱动框架
驱动总体采用 I2C 框架,其参考代码如下所示
1 | static const struct i2c_device_id xxx_ts_id[] = {}; |
当设备树中触摸 IC 的设备节点和驱动匹配以后,probe 函数就会执行,可以在此函数中初始化触摸 IC、中断、input 子系统等等。
2、初始化触摸 IC、中断和 input 子系统
初始化操作都是在 xxx_ts_probe 函数中完成,参考框架如下所示
1 | static int xxx_tx_probe(struct i2c_client &client, const struct i2c_device_id *id) |
首先初始化触摸芯片,包括芯片的相关 IO,比如复位、中断等 IO 引脚,然后就是芯片本身的初始化,配置触摸芯片相关的寄存器。接下来就是申请中断、input dev,接着设置 input_dev 的上报事件。
值得注意的是,这里申请中断使用的函数是 devm_request_threaded_irq,这里的意思是将此中断线程化,可以保证高优先级的任务能被优先处理。devm_ 前缀的意思是让 Linux 自动释放资源。
3、上报坐标信息
最后就是在中断服务程序中上报读取到的坐标信息,根据所使用的多点电容触摸设备类型选择发送时序,对于能区分多个触摸点的设备参考框架如下:
1 | static irqreturn_t xxx_handler(int irq, void *dev_id) |
硬件分析
我手上的触摸芯片为 GT1151,这是一颗支持多点触控(最高5点)的电容触摸屏控制器,它通过最常用的 I2C总线 与主控芯片进行通信。除此之外,还有两根非常重要的GPIO线:
复位引脚(RST): 用于对触摸芯片进行硬件复位。通常由主控控制,在驱动初始化时,先拉低再拉高,完成一次复位操作。
中断引脚(INT): 这是触摸芯片主动“通知”主控的关键。当有触摸事件(按下、移动、抬起)发生时,GT1151会通过这个引脚向主控发送一个中断信号。采用中断方式,而不是让主控不停地去查询(轮询),可以极大地节省CPU资源,降低系统功耗。
查询底板原理图,可以得知 39、38、36、35 pin 对应的就是四根线,对应关系如下
| 触摸屏引脚 | 对应板级编号 | MX引脚 |
|---|---|---|
| CT_RST | A48 | SNVS_TAMPERD9 |
| CT_INT | B15 | GPIO_9 |
| I2C2_SDA | B32 | UART5_RXD |
| I2C2_SCL | B31 | UART5_TXD |

程序编写
修改设备树
打开 imx6ull-hcw-emmc.dtsi,找到 iomuxc 节点,添加以下 pinctrl 组
1 | pinctrl_gt1151_int: gt1151intgrp{ |
接下来,在 i2c2 节点下添加设备,如下
1 | gt1151@14{ |
使用新编译好的设备树启动 Linux 内核。
编写代码
创建 .c 文件,写入以下代码
1 | /* |
由于内核中已经存在了 gtxxx 的驱动,所以我们暂时先将设备树和驱动程序中的设备名改为 “hcw,gt1”。装载模块,现在我们可以读取到触摸屏上传的数据了
