LV17-01-输入类设备-03-触摸屏设备
本文主要是输入类设备控制——触摸屏设备的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.2的64位版本 | |
VMware® Workstation 17 Pro | 17.6.0 build-24238078 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) | |
linux内核 | linux-4.15(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 | |
其他网站 | kernel - Linux source code (v4.15) - Bootlin | linux内核源码在线查看 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
https://elixir.bootlin.com/linux/latest/source | 在线阅读linux kernel源码 | |
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) |
一、电阻屏和电容屏
触摸屏分为电阻屏、电容屏。电阻屏结构简单,在以前很流行;电容屏支持多点触摸,现在的手机基本都是使用电容屏。
注意: LCD、触摸屏不是一回事, LCD 是输出设备,触摸屏是输入设备。制作触摸屏时特意把它的尺寸做得跟 LCD 一模一样,并且把触摸屏覆盖在 LCD 上。
1. 电阻屏
1.1 欧姆定律
就是长度和阻值成正比关系。电阻长度为L,阻值为 R,在两端施加 3.3V 电压。在某点测得电阻为 V,求上图中长度 X。根据欧姆定律: 3.3/R = V/Rx,因为长度和阻值成正比关系,上述公式转换为: 3.3∕L = V/X,所以 X=LV/3.3。
1.2 电阻屏基本原理
电阻屏就是基于欧姆定律制作的,它有上下两层薄膜,这两层薄膜就是两个电阻,如下图所示:
平时上下两层薄膜无接触,当点击触摸屏时,上下两层薄膜接触:这时就可以测量触点电压。过程如下
(1)测量 X 坐标
在 xp、 xm 两端施加 3.3V 电压, yp 和 ym 不施加电压(yp 就相当于探针),测量 yp 电压值。该电压值就跟 X 坐标成正比关系,假设:
1 | X = 3.3*Vyp/Xmax |
(2)测量 Y 坐标
yp、 ym 两端施加 3.3V 电压, xp 和 xm 不施加电压(xp 就相当于探针),测量 xp 电压值。该电压值就跟 Y 坐标成正比关系,假设:
1 | Y = 3.3*Vxp/Ymax |
在实际使用时,电阻屏的 Xmax、 Ymax 无从得知,所以使用之前要先较准:依次点击触摸屏的四个角和中心点,推算出 X、 Y 坐标的公式:
1 | X = func(Vyp) |
1.3 电阻屏数据
Linux 驱动程序中,会上报触点的 X、 Y 数据,注意:这不是 LCD 的坐标值,需要 APP 再次处理才能转换为 LCD 坐标值。 对应的 input_event 结构体中,“ type、 code、 value”如下:
- 按下时
1 | EV_KEY BTN_TOUCH 1 /* 按下 */ |
- 松开时
1 | EV_KEY BTN_TOUCH 0 /* 松开 */ |
2. 电容屏
2.1 基本原理
电容屏中有一个控制芯片,它会周期性产生驱动信号,接收电极接收到信号,并可测量电荷大小。当电容屏被按下时,相当于引入了新的电容,从而影响了接收电极接收到的电荷大小。主控芯片根据电荷大小即可计算出触点位置。
怎么通过电荷计算出触点位置?这由控制芯片实现,这类芯片一般是 I2C 接口。我们只需要编写程序,通过 I2C 读取芯片寄存器即可得到这些数据。
2.2 电容屏数据
参考文档:Linux内核源码中的 multi-touch-protocol.rst - Documentation/input/multi-touch-protocol.rst - Linux source code v4.15 - Bootlin。
电容屏可以支持多点触摸(Multi touch),驱动程序上报的数据中怎么分辨触点? 这有两种方法: Type A、 Type B,这也对应两种类型的触摸屏:
Type A:该类型的触摸屏不能分辨是哪一个触点,它只是把所有触点的坐标一股脑地上报,由软件来分辨这些数据分别属于哪一个触点。Type A 已经过时, Linux 内核中都没有 Type A 的源码了。
Type B:该类型的触摸屏能分辨是哪一个触点,上报数据时会先上报触点 ID,再上报它的数据。
我们来看一个最简单的示例,从使用场景分析来看看它上报的数据。当有 2 个触点时(type, code, value):
1 | EV_ABS ABS_MT_SLOT 0 // 这表示“我要上报一个触点信息了”,用来分隔触点信息 |
当 ID 为 45 的触点正在移动时:
1 | EV_ABS ABS_MT_SLOT 0 // 这表示“我要上报一个触点信息了”,之前上报过 ID,就不用再上报 ID了 |
松开 ID 为 45 的触点时(在前面 slot 已经被设置为 0,这里这需要再重新设置slot, slot 就像一个全局变量一样:如果它没变化的话,就无需再次设置):
1 | // 刚刚设置了 ABS_MT_SLOT 为 0,它对应 ID 为 45,这里设置 ID 为-1 就表示 ID 为 45 的触点被松开了 |
最后,松开 ID 为 46 的触点:
1 | EV_ABS ABS_MT_SLOT 1 // 这表示“我要上报一个触点信息了”,在前面设置过 slot 1 的 ID为 46 |
2.3 电容屏的实验数据
假设开发板上电容屏对应的设备节点是/dev/input/event0,执行以下命令:
1 | hexdump /dev/input/event0 |
然后用一个手指点击触摸屏,得到类似下图的数据
在上面的数据中,为了兼容老程序,它也上报了 ABS_X、 ABS_Y 数据,电阻触摸屏就是使用这类型的数据。所以基于电阻屏的程序,也可以用在电容屏上。使用两个手指点击触摸屏时,得到类似如下的数据:
为了兼容老程序,它也上报了 ABS_X、 ABS_Y 数据,但是只上报第 1 个触点的数据。
二、绝对位移事件
触摸屏设备是一个绝对位移设备,可以上报绝对位移事件,绝对位移事件如下(input-event-codes.h - include/uapi/linux/input-event-codes.h - Linux source code v4.15 - Bootlin):
三、单点触摸和多点触摸
1. 两个概念
触摸屏分为多点触摸设备和单点触摸设备。 单点触摸设备只支持单点触摸, 一轮(这里把一个同步事件称为一轮) 完整的数据只包含一个触摸点信息; 单点触摸设备以 ABS_XXX 事件承载、上报触摸点的信息,如 ABS_X(value 值对应的是 X 轴坐标值)、 ABS_Y(value 值对应的是 Y 轴坐标值)等绝对位移事件,而有些设备可能还支持 Z 轴坐标(通过 ABS_Z 事件上报、 value 值对应的便是 Z 轴坐标值)、 按压力大小(通过 ABS_PRESSURE 事件上报、 value 值对应的便是按压力大小) 以及接触面积等属性。 大部分的单点触摸设备都会上报 ABS_X 和 ABS_Y 事件,而其它绝对位移事件则根据具体的设备以及驱动的实现而定。
多点触摸设备可支持多点触摸,如 ALPHA 开发板配套使用的 4.3 寸、 7 寸等触摸屏均支持多点触摸,对于多点触摸设备, 一轮完整的数据可能包含多个触摸点信息。 多点触摸设备则是以 ABS_MT_XXX(MT 是 Multi-touch, 意思为: 多点触摸)事件承载、上报触摸点的信息, 如 ABS_MT_POSITION_X(X 轴坐标) 、 ABS_MT_POSITION_Y(Y 轴坐标)等绝对位移事件。
触摸屏设备除了上报绝对位移事件之外,还可以上报按键类事件和同步类事件。同步事件很好理解,因为几乎每一个输入设备都会上报同步事件、告知应用层本轮数据是否完整;当手指点击触摸屏或手指从触摸屏离开时,此时就会上报按键类事件, 用于描述按下触摸屏和松开触摸屏; 具体的按键事件为BTN_TOUCH(code=0x14a,也就是 330) ,当然,手指在触摸屏上滑动不会上报 BTN_TOUCH 事件。
TN_TOUCH 事件不支持长按状态,故其 value 不会等于 2。 对于多点触摸设备来说,只有第一个点按下时上报 BTN_TOUCH 事件 value=1、当最后一个点离开触摸屏时上报 BTN_TOUCH 事件 value=0。
2.事件上报的顺序
2.1 单点触摸设备
通过上面的了解可知,单点触摸设备事件上报的流程大概如下:
1 | 点击触摸屏时 |
以上列举出只是一个大致流程,实际上对于不同的触摸屏设备,能够获取到的信息量大小是不相同的,如某设备只能读取到触摸点的 X 和 Y 坐标、而另一设备却能读取 X、 Y 坐标以及按压力大小、 触点面积等信息, 总之这些数据都会在 SYN_REPORT 同步事件之前上报给应用层。
当手指点击触摸屏时,首先上报 BTN_TOUCH 事件,此时 value=1,表示按下;接着上报 ABS_X、 ABS_Y事件将 X、 Y 轴坐标数据发送给应用层;数据上报完成接着上报一个同步事件 SYN_REPORT,表示此次触摸点信息已经完整。
当手指在触摸屏上滑动时,并不会上报 BTN_TOUCH 事件,因为滑动过程并未发生按下、松开这种动作。
当松开时,首先上报了 BTN_TOUCH 事件,此时 value=0,表示手指已经松开了触摸屏,接着上报一个同步事件 SYN_REPORT。
2.2 多点触摸设备
多点触摸设备上报的一轮完整数据中可能包含多个触摸点的信息,如 5 点触摸设备,如果 5 个手指同时在触摸屏上滑动,那么硬件就会更新 5 个触摸点的信息, 内核需要把这 5 个触摸点的信息上报给应用层。
在 Linux 内核中, 多点触摸设备使用多点触摸(MT)协议上报各个触摸点的数据, MT 协议分为两种类型: Type A 和 Type B, Type A 协议实际使用中用的比较少,几乎处于淘汰的边缘, 这里就暂时不去了解了,我们重点来看看 Type B 协议。
- MT 协议之 Type B 协议
Type B 协议适用于能够追踪并区分触摸点的设备,开发板配套使用的触摸屏都属于这类设备。 Type B协议的重点是通过 ABS_MT_SLOT 事件上报各个触摸点信息的更新。
能够追踪并区分触摸点的设备通常在硬件上能够区分不同的触摸点, 例如对于一个 5 点触摸设备来说,硬件能够为每一个识别到的触摸点与一个 slot 进行关联,这个 slot 就是一个编号, 触摸点 0、触摸点 1、触摸点 2 等。底层驱动向应用层上报 ABS_MT_SLOT 事件, 此事件会告诉接收者当前正在更新的是哪个触摸点的数据, ABS_MT_SLOT 事件中对应的 value 数据存放的便是一个 slot、以告知应用层当前正在更新 slot关联的触摸点对应的信息。
每个识别出来的触摸点分配一个 slot, 与该 slot 关联起来, 利用这个 slot 来传递对应触点的变化。 除了ABS_MT_SLOT 事件之外,Type B 协议还会使用到 ABS_MT_TRACTKING_ID 事 件 ,ABS_MT_TRACTKING_ID 事件则用于触摸点的创建、替换和销毁工作,ABS_MT_TRACTKING_ID 事件携带的数据 value 表示一个 ID,一个非负数的 ID(ID>=0) 表示一个有效的触摸点,如果 ID 等于-1 表示该触摸点已经不存在、被移除了;一个以前不存在的 ID 表示这是一个新的触摸点。
Type B 协议可以减少发送到用户空间的数据,只有发生了变更的数据才会上报,如某个触摸点发生了移动,但仅仅只改变了 X 轴坐标、而未改变 Y 轴坐标,那么内核只会将改变后的 X 坐标值通过ABS_MT_POSITION_X 事件发送给应用层。
Type B 协议下多点触摸设备上报数据的流程大致如下:
1 | ABS_MT_SLOT 0 |
这里看完应该还是不会很明白,可以在下一节的笔记中根据现象来分析。
Tips:
slot 和 ID 这两个概念好像有些混乱,这里再简单解释一下:
slot 是硬件上的一个概念、而 ID 则可认为是软件上的一个概念;对于一个多点触摸设备来说,它最大支持的触摸点数是确定的,如 5 点触摸设备,最多支持 5 个触摸点;每一个触摸点在硬件上它有一个区分的编号,如触摸点0、触摸点 1、触摸点 2 等,这个编号就是一个 slot(通常从 0 开始) ; 如何给识别到的触点分配一个 slot 呢(触点与 slot 关联)?通常是按照时间先后顺序来的,如第一根手指先触碰到触摸屏,那第一根手指就对应触摸点 0(slot=0),接着第二根手指触碰到触摸屏则对应触摸点 1(slot=1)以此类推。这个通常是硬件所支持的。
而 ID 可认为是软件上的一个概念,它也用于区分不同的触摸点,但是它跟 slot 不同, 不是同一层级的概念;举个例子,如一根手指触碰到触摸屏之后拿开,然后再次触碰触摸屏,这个过程中,假设只有这一根手指进行触碰操作,那么两次触碰对应都是触摸点 0(slot=0),这个无疑义。但从触摸点的生命周期来看, 它们是同一个触摸点吗?答案肯定不是,为啥?手指从触摸屏上离开后,该触摸点就消失了、被删除了, 该触摸点的生命周期也就到此结束了,所以它们自然是不同的触摸点, 所以它们的 ID 是不同的。
3. 触摸屏上报数据分析
首先在测试触摸屏之前,需要保证开发板上已经连接了 LCD 屏, ALPHA I.MX6U 开发板出厂系统配套支持多种不同分辨率的 LCD 屏,包括 4.3 寸 480*272、 4.3 寸 800*480、 7 寸 800*480、 7 寸 1024*600以及 10.1 寸 1280*800,在启动开发板之前需要将 LCD 屏通过软排线连接到开发板的 LCD 接口,开发板连接好 LCD 屏之后上电启动开发板、运行出厂系统。
不过我用的没上图那么大,我用的实4.3寸800*480那个。触摸屏与 LCD 液晶屏面板是粘合在一起的,也就是说触摸屏是直接贴在了 LCD 液晶屏上面,直接在LCD 屏上触摸、滑动操作即可。为了测试方便,可以将出厂系统的 GUI 应用程序退出,如何退出呢?点击屏幕进入设置页面,可以看到在该页面下有一个退出按钮选项,直接点击即可。退出后如下所示:
那触摸屏输入的时候是哪个事件节点?我们还是通过以下命令来查看:
1 | cat /proc/bus/input/devices |
可以看到如下打印信息:
其中这个goodix-ts就是我所使用的触摸屏的名称。我们使用这个测试程序:
1 |
|
编译后执行以下命令:
1 | ./app_demo /dev/input/event1 |
然后一个手指点击触摸屏先不松开,终端将会打印如下信息:
我不知道是不是因为我这个是5点触摸屏的缘故,和正点原子官方提供的文档中的差别还挺大,这里按文档中进行学习吧,毕竟我用的是另一块屏幕。文档中现象是这样的:
第一行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING|_ID(code=57)事件,并且 value 值等于 78,也就是 ID,这个 ID 是一个非负数,所以表示这是一个新的触摸点被创建,也就意味着触摸屏上产生了一个新的触摸点(手指按下) 。
第二行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_POSITION_X(code=53)事件,其 value对应的便是触摸点的 X 坐标;第三行上报了 ABS_MT_POSITION_Y(code=54)事件,其 value 值对应的便是触摸点 Y 坐标,所以由此可知该触摸点的坐标为(372, 381)。
第四行上报了按键类事件 EV_KEY(type=1)中的 BTN_TOUCH(code=330) , value 值等于 1,表示这是触摸屏上最先产生的触摸点(slot=0、也就是触摸点 0) 。
第五行和第六行分别上报了绝对位移事件 EV_ABS(type=3)中的 ABS_X(code=0)和 ABS_Y(code=1),其 value 分别对应的是触摸点的 X 坐标和 Y 坐标。 多点触摸设备也会通过 ABS_X、 ABS_Y 事件上报触摸点的 X、 Y 坐标, 但通常只有触摸点 0 支持,所以可以把多点触摸设备当成单点触摸设备来使用。
最后一行上报了同步类事件 EV_SYN(type=0)中的 SYN_REPORT(code=0)事件,表示此次触摸点的信息全部上报完毕。
在第一个触摸点的基础上,增加第二个触摸点,打印信息如下所示(还是按文档来学习):
1-7 行和上面一样;
第八行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_SLOT 事件(code=47),表示目前要更新 slot=1 所关联的触摸点(也就是触摸点 1) 对应的信息。
第九行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID 事件(code=57), ID=79,这是之前没有出现过的 ID,表示这是一个新的触摸点。
第十、十一行分别上报了 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件。
最后一行上报同步事件(type=0、 code=0) ,告知应用层数据完整。
当手指松开时,触摸点就会被销毁,上报 ABS_MT_TRACKING_ID 事件,并将 value 设置为-1(ID),如下所示:
不管是键盘也好、或者是鼠标、触摸屏,都可以像上面那样将输入设备的数据直接打印出来,然后再去分析,确定该输入设备上报事件的规则和流程,把这些弄懂之后再去编写程序验证结果。
四、获取触摸屏信息
我们怎么知道这个触摸屏支持几个点?触摸点的坐标在什么范围内?我们其实可以通过 ioctl()函数可以获取到这些信息
1. 函数简介
ioctl()是一个文件 I/O 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或设备文件,函数的原型如下:
1 |
|
第一个参数 fd 对应文件描述符;
第二个参数 request 与具体要操作的对象有关,没有统一值, 表示向文件描述符请求相应的操作,也就是请求指令;此函数是一个可变参函数, 第三个参数需要根据 request 参数来决定,配合 request 来使用。
2. 如何使用?
在 input.h 头文件有这样一些宏定义,如下所示 :
1 |
|
每一个宏定义后面都有相应的注释,对于 input 输入设备,对其执行 ioctl()操作需要使用这些宏, 不同的宏表示不同请求指令; 如使用 EVIOCGNAME 宏获取设备名称,使用方式如下:
1 | char name[100]; |
EVIOCGNAME(len)就表示用于接收字符串数据的缓冲区大小,而此时 ioctl()函数的第三个参数需要传入一个缓冲区的地址,该缓冲区用于存放设备名称对应的字符串数据。
EVIOCG(get)开头的表示获取信息, EVIOCS(set)开头表示设置;这里暂且不管其它宏,重点来看看 EVIOCGABS(abs)宏, 这个宏也是通常使用最多的, 如下所示:
1 |
通过这个宏可以获取到触摸屏 slot(slot<0>表示触摸点 0、 slot<1>表示触摸点 1、 slot<2>表示触摸点 2,以此类推)的取值范围, 可以看到使用该宏需要传入一个 abs 参数,该参数表示为一个 ABS_XXX 绝对位移事件,如 EVIOCGABS(ABS_MT_SLOT)表示获取触摸屏的 slot 信息,此时 ioctl()函数的第三个参数是一个 struct input_absinfo *的指针,指向一个 struct input_absinfo 对象,调用 ioctl()会将获取到的信息写入到struct input_absinfo 对象中。 struct input_absinfo 结构体(input.h - include/uapi/linux/input.h - Linux source code v4.15 - Bootlin)如下所示:
1 | struct input_absinfo { |
获取触摸屏支持的最大触摸点数:
1 | struct input_absinfo info; |
五、查看触摸屏节点
其实前面已经说过了,我们还是通过以下命令来查看:
1 | cat /proc/bus/input/devices |
可以看到如下打印信息:
其中这个goodix-ts就是我所使用的触摸屏的名称。