1、input事件
对于所有的input设备,报告input事件时候都分这么几部分,首先在probe函数中设置设备发送的事件类型、按键类型,设置设备一些属性信息。然后在发送事件时候要根据probe的设置来发送事件,否则就会被判为无效忽略掉。
input子系统事件分为type、code、value三部分。type表示事件的类型,按键、绝对坐标等。code表示键值、触摸坐标等。value表示数值,如按键按下为1,抬起为0;对于触摸屏x、y坐标则为对应的数值。
输入子系统
1.1 输入设备事件
输入子系统事件类型(type)
linux输入子系统的事件类型定义在路径:include/uapi/linux/input.h
中,事件类型含义如下表
type | 数值 | 说明 |
---|---|---|
EV_SYN | 0x00 | 同步事件,用于事件间的分割标志 |
EV_KEY | 0x01 | 按键事件,如KEY_VOLUMEDOWN(音量减) |
EV_REL | 0x02 | 相对坐标,如鼠标向左方移动了100个单位 |
EV_ABS | 0x03 | 绝对坐标,如触摸屏上报的坐标 |
EV_MSC | 0x04 | 当不能匹配现有的类型时,使用该类型进行描述 |
EV_LED | 0x11 | 用于控制设备上的LED灯的开和关 |
EV_SND | 0x12 | 用来给设备输出提示声音 |
EV_REP | 0x14 | 用于可以自动重复的设备(autorepeating) |
EV_FF | 0x15 | 力反馈 |
EV_PWR | 0x16 | 特别用于电源开关的输入 |
EV_FF_STATUS | 0x17 | 用于接收设备的强制反馈状态 |
触摸屏驱动事件类型配置
对于触摸屏驱动,必须支持的事件类型有以下三个:
//设备同步,每次触摸完成以后都要发送一个同步事件,来表明这次触摸已经完成
__set_bit(EV_SYN, input->evbit);
//绝对坐标事件,触摸屏每次发送的坐标都是绝对坐标,不同于鼠标的相对坐标
__set_bit(EV_ABS, input->evbit);
//按键事件,每次触摸都有一个BTN_TOUCH的按键事件
__set_bit(EV_KEY, input->evbit);
1.2 输入设备按键
输入子系统按键键值(code)
linux输入子系统,按键类型定义在include/uapi/linux/input.h中,包括电脑使用的按键键值以及其他按键。诸如数字按键、字母按键等。
输入按键键值可以通过以下api配置:
__set_bit(KEY_1, input_dev->keybit);
input_set_capability(input, KEY_1);
具体按键键值定义这里就不再展开了。
触摸屏驱动按键键值配置
对于触摸屏驱动,必须支持的按键键值
__set_bit(BTN_TOUCH, input_dev->keybit); //touch类型按键
触摸屏可能存在触摸屏边缘带触摸按键的情况,例如安卓手机三个触摸按键,当使用这些触摸屏时,需要配置对应触摸按键键值。
如需要的四个按键键值分别为为KEY_LEFT\KEY_RIGHT\KEY_UP\KEY_DOWN(上下左右)。则需要在触摸屏驱动中依次添加这四个按键。
为安卓系统提供触摸屏驱动时,如果需要触摸唤醒(双击亮屏)功能,则需要支持KEY_POWER按键。在触摸事件来临时,判断系统是否处理休眠,如果处于休眠状态,则需要上报KEY_POWER按键键值,唤醒系统。
PS:安卓手机的电源键,本质上也是物理按键触发时,上报KEY_POWER键值。
1.3 绝对坐标事件
绝对坐标事件键值(code)
以下举例说明了部分绝对坐标事件,并进行相应的说明。ABS_MT_前缀的事件为多点事件(Multi touch)。
code | 数值 | 说明 |
---|---|---|
ABS_X | 0x00 | 单点触摸中心的X坐标 |
ABS_Y | 0x01 | 单点触摸中心的Y坐标 |
… | ||
ABS_MT_SLOT | 0x2f | 多点B协议(SLOT),报点前需要先发起一次SLOT事件 |
ABS_MT_TOUCH_MAJOR | 0x30 | 接触区域的长轴长度 |
ABS_MT_TOUCH_MINOR | 0x31 | 接触区域的短轴长度 |
ABS_MT_WIDTH_MAJOR | 0x32 | 工具轮廓区域长轴的表面单位长度 |
ABS_MT_WIDTH_MINOR | 0x33 | 工具轮廓区域短轴的表面单位长度 |
ABS_MT_POSITION_X | 0x35 | 触摸点接触中心的X坐标 |
ABS_MT_POSITION_Y | 0x36 | 触摸点接触中心的Y坐标 |
ABS_MT_TRACKING_ID | 0x39 | TRACKING_ID识别一个被启动的触控点的整个生命周期。TRACKING_ID的范围应该足够大,从而保证在足够长的时间内都可以维护一个唯一的值。对于type B设备,该事件由input核心层处理,驱动程序应该改为使用input_mt_report_slot_state()来上报该事件。 |
触摸屏驱动绝对坐标键值配置
单点触摸需要配置:
input_set_abs_params(pInputDev, ABS_X, 0, xmax, 0, 0);
input_set_abs_params(pInputDev, ABS_Y, 0, ymax, 0, 0);
单点触摸比较简单,先上报BTN_TOUCH按键按下事件,随后上报ABS_X\ABS_Y事件,然后调用input_sync通知输入子系统,处理设备产生的事件。触摸抬起时,上报BTN_TOUCH按键抬起事件,随后调用input_sync通知输入子系统,处理触摸抬起事件。
多点触控需要配置:
input_mt_init_slots(input, 5, INPUT_MT_DIRECT);
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0xFF, 0, 0);
input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 0xFF, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X, 0, config->x_max, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, config->y_max, 0, 0);
input_mt_init_slots,ABS_MT_POSITION_X,ABS_MT_POSITION_Y为必选项,ABS_MT_TOUCH_MAJOR,ABS_MT_WIDTH_MAJOR等可不配置(这两项和触控宽度有关,部分触摸IC有触控面积反馈,可以用来判断接触面积,如大拇指和食指按压屏幕接触面积就不一样)。
多店触控分为A/B协议,B协议(SLOT)相对A协议需要额外配置ABS_MT_TRACKING_ID,用ID来追踪不同触点的坐标,抬起情况。
input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, 0xFF, 0, 0);
1.4 输入子系统多点触控协议
协议说明
基于硬件的能力,该协议被分为两种类型。对于只能处理匿名接触(type A)的设备,该协议描述了如何把所有的原始触摸数据发送给接收者。对于那些有能力跟踪并识别每个触摸点的设备(type B),该协议描述了如何把每个触摸点的单独更新通过事件slots发送给接受者。
对于type A设备的驱动,在每个数据包的结尾用input_mt_sync()对多个触控包进行分割,这将会产生一个SYN_MT_REPORT事件,它通知接收者接受当前的触控信息并准备接收下一个信息。
对于type B设备的驱动,在每个数据包的开始,通过调用input_mt_slot()进行分割,同时带入一个参数:slot。这会产生一个ABS_MT_SLOT事件,它通知接收者准备更新给定slot的信息。.
两种类型的驱动通常都通过调用input_sync()函数来标记一个多点触摸数据传送的结束,这通知接收者对从上一个EV_SYN/SYN_REPORT以来的所有累加事件作出响应,并准备接收新的一组事件/数据包。
无状态的type A协议和有状态的type B slot协议之间的主要区别是通过识别相同接触点来减低发送到用户空间的数据量。slot协议需要使用到ABS_MT_TRACKING_ID,它要不由硬件来提供,或者通过原始数据进行计算。
对于type A设备,内核驱动应该根据设备表面上全部有效触控进行列举并生成事件。每个触控点数据包在这次事件流中的顺序并不重要。事件过滤和手指跟踪的工作留给用户空间来实现。
对于type B设备,内核驱动应该把每一个识别出的触控和一个slot相关联,并使用该slot来传播触摸状态的改变。通过修改关联slot的ABS_MT_TRACKING_ID来达到对触摸点的创建,替换和销毁。一个非负数的跟踪id被解释为有效的触摸,-1则代表一个不再使用的slot。一个之前没有出现过的跟踪id被认为是一个新的接触点,当一个跟踪id不再出现时则认为该接触点已经被移除。因为只有变化的部分被传播,每个被启动的接触点的状态信息必须驻留在接收端。每当接收到一个MT事件,只需对当前slot的相关属性进行一次简单的更新即可。
如果硬件支持,建议使用B协议。
A协议示例
对于一个两点触控的触摸信息,type A设备的最小的事件序列看起来就像下面这样:
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
SYN_MT_REPORT
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
SYN_REPORT
实际上,在移动其中一个触控点后的上报序列看起来是一样的、所有存在触控点的原始数据被发送,然后在它们之间用SYN_REPORT进行同步。
当第一个接触点离开后,事件序列如下:
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
SYN_REPORT
当第二个接触点离开后,事件序列如下:
SYN_MT_REPORT
SYN_REPORT
假如驱动在ABS_MT事件之外上报一个BTN_TOUCH 或ABS_PRESSURE事件,最后一个SYN_MT_REPORT可以省略掉,否则,最后的SYN_REPORT会被input核心层扔掉,结果就是一个0触控点事件被传到用户空间中。
B协议示例
对于一个两点触控的触摸信息,type B设备的最小的事件序列看起来就像下面这样:
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 45
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT
id 45的触控点在x方向移动后的事件序列如下:
ABS_MT_SLOT 0
ABS_MT_POSITION_X x[0]
SYN_REPORT
slot 0对应的接触点离开后,对应的事件序列如下:
ABS_MT_TRACKING_ID -1
SYN_REPORT
上一个被修改的slot也是0,所以ABS_MT_SLOT被省略掉。这一消息移除了接触点45相关联的slot 0,于是接触点45被销毁,slot 0被释放后可以被另一个接触点重用。
最后,第二个接触点离开后的时间序列如下:
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID -1
SYN_REPORT
2、多点触摸屏驱动示例
2.1 注册input设备
static int ts_event_init(void)
{
struct input_dev *input = NULL;
……
input = input_allocate_device(); (1)
if (!input) {
return -ENOMEM;
}
……
input->name = NAME; (2)
input->phys = "ts/input0";
input->id.bustype = BUS_I2C;
input->id.vendor = 0xdead;
input->id.product = 0xbeef;
input->id.version = 0x1111;
__set_bit(EV_SYN, input->evbit); (3)
__set_bit(EV_ABS, input->evbit);
__set_bit(EV_KEY, input->evbit);
__set_bit(BTN_TOUCH, input->keybit);
__set_bit(INPUT_PROP_DIRECT, input->propbit);
input_set_capability(input, EV_KEY, KEY_POWER); //触摸唤醒power (4)
input_mt_init_slots(input, 10, INPUT_MT_DIRECT); (5)
input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 0xFF, 0, 0);
input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 0xFF, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0);
input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0);
input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, 0xFF, 0, 0);
ret = input_register_device(input); (6)
if (!input) {
return ret;
}
……
}
(1)分配struct input_dev *input内存
(2)配置input数据结构,设备名、bustype、vid、pid等参数。
(3)设置支持的事件类型(SYN、ABS、KEY)和按键(BTN_TOUCH)
(4)设置其他按键键值,这里设置了电源按键,如果有其他的要一一设置。
(5)设置多点触控最大支持数,这里设置10点触控。此设置需要>触摸IC支持的最大点数。
(6)注册INPUT设备,注册成功会生成eventx设备节点。
2.2 逻辑层事件上报
A协议事件上报
for (i = 0; i < event->touch_point; i++) //循环处理 缓存中的所有点
{
input_mt_slot(data->input_dev, event->au8_finger_id[i]); //发送点的ID
if (event->au8_touch_event[i]== 0 || event->au8_touch_event[i] == 2) //如果点按下
{
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER, true); //手指按下
input_report_abs(data->input_dev,ABS_MT_POSITION_X,event->au16_x[i]); //x坐标
input_report_abs(data->input_dev, ABS_MT_POSITION_Y,event->au16_y[i]); //y坐标
input_report_abs(data->input_dev,ABS_MT_TOUCH_MAJOR,event->pressure); //触摸点长轴长度
}
else
{
input_mt_report_slot_state(data->input_dev, MT_TOOL_FINGER,false); //手指抬起
}
input_mt_report_pointer_emulation(input_dev, true);//用模拟点的方法,来告知此次触摸已经完成。
input_sync(data->input_dev); //sync 设备同步
}
B协议事件上报
input_report_key(input, BTN_TOUCH, event->touch_point > 0); (1)
for (i = 0; i < event->touch_point; i++) { (2)
input_mt_slot(input, event->au8_finger_id[i]); (3)
if (event->au8_touch_event[i] == 0 || event->au8_touch_event[i] == 2) { (4)
input_mt_report_slot_state(input, MT_TOOL_FINGER, true); (5)
input_report_abs(input,ABS_MT_TRACKING_ID,event->au8_finger_id[i]); (6)
/*motify coordinate info by mirror setting*/
if (priv->ts_config.x_mirror) {
event->au16_x[i] = priv->ts_config.x_max - event->au16_x[i];
}
input_report_abs(input, ABS_MT_POSITION_X, event->au16_x[i]); (7)
if (priv->ts_config.y_mirror) {
event->au16_y[i] = priv->ts_config.y_max - event->au16_y[i];
}
input_report_abs(input, ABS_MT_POSITION_Y, event->au16_y[i]); (8)
input_report_abs(input, ABS_MT_TOUCH_MAJOR, event->au16_w[i]); (9)
input_report_abs(input, ABS_MT_WIDTH_MAJOR, event->au16_w[i]);
printk(KERN_DEBUG "touch_point(%d) x(%d) y(%d)\n", event->au8_finger_id[i],
event->au16_x[i], event->au16_y[i]);
} else {
uppoint++;
printk(KERN_DEBUG "finger %d up \n", event->au8_finger_id[i]);
input_mt_report_slot_state(input, MT_TOOL_FINGER, false); (10)
}
}
if (event->touch_point == uppoint) {
input_report_key(input, BTN_TOUCH, 0); (11)
} else {
input_report_key(input, BTN_TOUCH, event->touch_point > 0); (12)
}
input_sync(input);
(1)上报触摸BTN_TOUCH按键键值,表示触摸按下
(2)循环处理驱动层上报的触控点数
(3)发送点的id作为slot记录的点
(4)如果点按下
(5)手指按下
(6)发送点的id事件
(7)上报X绝对坐标
(8)上报Y绝对坐标
(9)上报触摸点长轴宽度
(10)手指抬起,释放该点ID(-1)
(11)如果所有手指都抬起。上报BTN_TOUCH抬起事件
(12)如果还有手指没抬起,上报BTN_TOUCH按下
(13)sync 设备同步
2.3 逻辑层驱动示例
根据实际IC编写,IC厂家一般有demo程序。
注意:
当使用B协议时,驱动层一定要上报对应触点ID,input子系统根据ID来跟踪每个触点情况。
B协议需要硬件支持触点ID标记功能。
例如当手指0和1依次按下时,第一次中断id0先有数据,上报id0触点按下坐标、id等数据;第二次中断id0和id1都有数据,上报id0、id1触点按下坐标、id等数据;
当手指0率先抬起时,i2c读取到的id0没数据,id1有数据。此时需要上报id0为抬起,id1按下。告诉input子系统id0已经抬起了,结束对id0的跟踪。
随后手指1抬起,id1没数据,则上报id1抬起,告诉input子系统id0已经抬起,结束对id1的跟踪。
当所有按键都抬起时,上报BTN_TOUCH抬起,结束此次触摸事件。
3、安卓系统触摸屏调试
3.1 开发者选项显示触点
安卓系统触摸屏驱动调试,图形化显示可以借助安卓系统自带开发者选项设置。
开发者选项->输入。勾选显示点按操作反馈,指针位置。即可在触摸时显示触点,坐标及轨迹等。如果驱动配置了上报ABS_MT_TOUCH_MAJOR,ABS_MT_WIDTH_MAJOR,且硬件支持触摸宽度显示,则触摸时触点还会根据手指按压的宽度。显示不同的大小。
3.2 getevent/sendevent
通过命令行getevent sendevent可以很方便看到input事件。
getevent
# getevent -h
Usage: getevent [-t] [-n] [-s switchmask] [-S [-v [mask]] [-d] [-p] [-i] [-l] [-q] [-c count] [-r] [device]
-t: show time stamps
-n: don't print newlines
-s: print switch states for given bits
-S: print all switch states
-v: verbosity mask (errs=1, dev=2, name=4, info=8, vers=16, pos. events=32, props=64)
-d: show HID descriptor, if available
-p: show possible events (errs, dev, name, pos. events)
-i: show all device info and possible events
-l: label event types and names in plain text
-q: quiet (clear verbosity mask)
-c: print given number of events then exit
-r: print rate events are received
其中 [-t] 参数显示事件的时间戳,[-n] 取消事件显示时的换行符,[-s switchmask] 得到指定位的开关状态,[-S] 得到所有开关的状态,[-v [mask]] 根据mask的值显示相关信息,后面详细介绍mask的使用方法,[-p] 显示每个设备支持的事件类型和编码,[-q] 只显示事件数据,[-c count] 只显示count次事件的数据,[-r] 显示事件接收频率。下面看一下命令以及相关参数的使用结果:
直接输入getevent可以看到所有input设备。
# getevent
add device 1: /dev/input/event0
name: "self_ts"
add device 2: /dev/input/event1
name: "power_key"
输入getevent –v可以看到input设备注册时的一些信息。如bus、vid、pid、version等。
# getevent -v
add device 1: /dev/input/event0
bus: 0018
vendor dead
product beef
version 1111
name: "self_ts"
location: "touch_screen/input0"
id: ""
version: 1.0.1
add device 2: /dev/input/event0
bus: 0000
vendor 0000
product 0000
version 0000
name: "power_key"
location: "power_key/input0"
id: ""
version: 1.0.1
直接输入getevent或getevent –v,该可执行程序是阻塞的,输入完成后,当点击触摸屏时,会打印input设备上报的数据。
输入getevent –p可以看到当前系统存在的所有input设备,并把买个设备支持的事件类型编码举例出来
# getevent -p
add device 0: /dev/input/event0
name: "self_ts"
evens:
KEY (0001): 0074 00f9 014a
ABS (0003): 002f : value 0, min 0, max 4, fuzz 0, flat 0, resolution 0
0030 : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
0032 : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
0035 : value 0, min 0, max 1920, fuzz 0, flat 0, resolution 0
0036 : value 0, min 0, max 1080, fuzz 0, flat 0, resolution 0
0039 : value 0, min 0, max 255, fuzz 0, flat 0, resolution 0
input props:
INPUT_PROP_DIRECT
sendevent
首先看一下它的用法
# sendevent
use: sendevent device type code value
可以看到sendevent需要4个参数即:device,type,code,value。这些值可以由input子系统定义,也可以从getevent里面获取。
如我们要模拟一次KEY_POWER事件,则需要模拟一次power按下抬起
# sendevent /dev/input/event1 0 0x74 1
# sendevent /dev/input/event1 0 0x74 0
device需要是支持该按键的设备;type为1表示是按键事件;value为1表示按下,为0表示弹起,一次按键事件由按下和弹起两个操作组成。