input子系统
input子系统是Linux对输入设备提供的统一驱动框架。如按键、键盘、触摸屏和鼠标等输入设备的驱动方式是类似的,当出现按键、触摸等操作时,硬件产生中断,然后CPU直接读取引脚电平,或通过SPI、I2C等通讯方式从设备的寄存器读取具体的按键值或触摸坐标,然后把这些信息提交给内核。使用input子系统 驱动的输入设备可以通过统一的数据结构提交给内核,该数据结构包括输入的时间、类型、代号以及具体的键值或坐标,而内则通过/dev/input目录下的文件接口传递给用户空间。
运行evtest工具,它列出了系统当前可用的/dev/input/event0~6输入事件 文件,并且列出了这些事件对应的设备名。
我们根据设备名的“VirtualBox mouse intergration”推猜它就是接入到电脑 的鼠标,所以输入了它对应的event6事件编号6,实验时请根据自己电脑的输出来选择。
输入编号后它列出了event6的一些设备信息,包括驱动版本、设备ID、设备 名、支持的事件类型、事件代号以及输入值的取值范围。
此时移动鼠标,可以看到它输出了详细的事件信息,如果移动后没有输出,说明 你选择的不是鼠标设备,请退出重新选择。输出信息中每一行包含了鼠标上报事件的具 体时间time、事件类型type 3(EV_ABS)、事件代号code 1或code0(ABS_Y或ABS_X)和具体的值value,该值就是鼠标X/Y的坐标。
在前面LED、GPIO子系统中,brightness、direction等设备文件直接使用字符串来记 录具体的信息,所以使用cat命令输出文件的内容时,字符串的形式非常方便我们阅读。但是event文件包 含的信息较多,使用字符串不方便其它程序处理,它采用了纯粹的内核事件数据结构来记录内容,其它 程序使用时 应把读取到的内容按数据的结构进行格式化转换,该数据结构定义如下所示。
input_event结构体(内核源码的/include/uapi/linux/input.h文件)
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time:该变量用于记录事件产生的时间戳,既evtest输出的time值。
type:输入设备的事件类型。系统常用的默认类型有EV_KEY、 EV_REL和EV_ABS,分别用 于表示按键状态改变事件、相对坐标改变事件及绝对坐标改变事件,特别地,EV_SYN用于分隔事件,无特别意义。如果选择鼠标(本章第一个图) evtest输出的type类型为EV_ABS。相关的枚举值可以参考内核文件include/uapi/linux/input-event-codes.h。
code:事件代号,它以更精确的方式表示事件。例如 在EV_KEY事件类型中,code的值常用于表 示键盘上具体的按键,其取值范围在0~127之间,例如按键Q对应的是KEY_Q,该枚举变量的 值为16。如果选择鼠标, evtest输出内容的code分别有ABS_X/ABS_Y,表示上报的是X或Y坐标。
value:事件的值。对于EV_KEY事件类型,当按键按下时,该值为1;按键松开时,该值为0。如果选择 鼠标,中evtest输出的内容里,ABS_X事件类型中的value值表示X坐标,ABS_Y类型中的value值表示Y坐标。
input事件设备名
“/dev/input/event*”的事件编号与设备的联系不是固定的,它通常按系统检测 到设备的先后顺序安排event文件的编号,这对编写应用程序控制不太方便,我们 可以通过“/dev/input/by-id”或“/dev/input/by-path”目录查看具体的硬件设备,如 下图所示。
图中列出了by-path目录下的内容,该目录下的文件实际上都是链接,如第 一行的“pci-0000:00:04.0-event-mouse -> …/event6”表示”pci-0000:00:04.0-event- mouse”文件就是event6的快捷方式,它就是本主机中使用的鼠标,也就是说访问该 文件就是访问该鼠标的事件设备,而且该文件名与硬件的关系是固定的,后面我们的实验就是采用这样的方式。
由于/dev下的设备都是通过/sys导出的,所以也可以通过“/sys/class/input”目 录查看,如下图所示。
“/sys/class/input”下包含了各个以事件命名的目录,其对应目录 下的device/name文件包含了事件对应的设备名,如本示例中 的“/sys/class/input/event6/device/name”文件的内容为”VirtualBox mouse integration”,evtest工具列出的事件与设备名的关系,就是从这里读取的。
按键检测
检测按键的设备文件:
/dev/input/by-path/platform-gpio-keys-event
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/input-event-codes.h>
//开发板上的KEY按键,请根据实际情况修改
const char default_path[] = "/dev/input/by-path/platform-gpio-keys-event";
//开发板上的ON_OFF按键,请根据实际情况修改
//const char default_path[] = "/dev/input/by-path/platform-20cc000.snvs:snvs-powerkey-event";
int main(int argc, char *argv[])
{
int fd;
struct input_event event;
char *path;
printf("This is a input device demo.\n");
//若无输入参数则使用默认事件设备
if(argc > 1)
path = argv[1];
else
path = (char *)default_path;
fd = open(path, O_RDONLY);
if(fd < 0){
printf("Fail to open device:%s.\n"
"Please confirm the path or you have permission to do this.\n", path);
exit(1);
}
printf("Test device: %s.\nWaiting for input...\n", path);
while(1){
if(read(fd, &event, sizeof(event)) == sizeof(event)){
//EV_SYN是事件分隔标志,不打印
if(event.type != EV_SYN)
printf("Event: time %ld.%ld, type %d, code %d,value %d\n",
event.time.tv_sec,event.time.tv_usec,
event.type,
event.code,
event.value);
}
}
close(fd);
return 0;
}
Hankin
2020.07.17