首页 > 技术文章 > LINUX 输入子系统及按键监测记录

KnowledgeShare 2017-02-08 14:59 原文

  Input输入

  输入设备(如按键,键盘,触摸屏,鼠标等)是典型的字符设备,其一般的工作机制是低层在按键,触摸等动作发生时产生一个中断(或驱动通过timer定时查询),然后cpu通过SPI,I2C或者外部存储器总线读取键值,坐标等数据,放一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值,坐标等数据。

  Linux 输入子系统是 Linux内核用于管理各种输入设备(键盘,鼠标,遥控杆,书写板等等)的。输入子系统分为三块: input core, drivers和 event handlers。正常的路径是从底层硬件到驱动,从驱动到input core,从 input core到 event handler,从 event handler到user space。

  在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。

  定位输入设备位置(以键盘举例)

  通过 #cat /proc/bus/input/devices 以查看到当前input子系统下面的所有event设备。

I: Bus=0011 Vendor=0001 Product=0001 Version=ab83
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input4
U: Uniq=
H: Handlers=sysrq kbd event4 leds 
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7

The B in front stands for bitmapNPSUH are simply first letter in corresponding name value and I is for IDIn ordered fashion:

  • I => @id: id of the device (struct input_id)
    • Bus     => id.bustype
    • Vendor  => id.vendor
    • Product => id.product
    • Version => id.version
  • N => name of the device.
  • P => physical path to the device in the system hierarchy.
  • S => sysfs path.
  • U => unique identification code for the device (if device has it).
  • H => list of input handles associated with the device.
  • B => bitmaps
    • PROP => device properties and quirks.
    • EV   => types of events supported by the device.
    • KEY  => keys/buttons this device has.
    • MSC  => miscellaneous events supported by the device.
    • LED  => leds present on the device.

  如图是我的键盘设备,可以看到是event4

  EVENT事件结构体

  在/usr/include/linux/input.h中有定义,这个文件定义了event事件的结构体,API和标准按键的编码等;我们需要将要写入的按键编码填充到结构体中,然后写入键盘或者鼠标的事件设备驱动文件中。

输入事件的结构体:

struct input_event {

    struct timeval time;  //按键时间

    __u16 type;  //事件的类型

    __u16 code;  //要模拟成什么按键

    __s32 value;  //是按下1还是释放0

};

标准按键的编码:(只列举部分)

type:

事件的类型:

EV_KEY, 按键事件,如键盘的按键(按下哪个键),鼠标的左键右键(是非击下)等;

EV_REL, 相对坐标,主要是指鼠标的移动事件(相对位移);

EV_ABS, 绝对坐标,主要指触摸屏的移动事件,但好像这个不能用在鼠标上面,也就是说无法通过这个来获取鼠标的绝对坐标(鼠标是一个相对位移的设备)。

code:

事件的代码:

       如果事件的类型代码是EV_KEY,该代码code为设备键盘代码。代码植0~127为键盘上的按键代码,0x110~0x116为鼠标上按键代码,其中0x110(BTN_ LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux /input.h文件。该文件中会定义相应的宏来代表不同的按键。

       如果事件的类型代码是EV_REL,code值表示轨迹的类型。如指示鼠标的X轴方向REL_X(代码为0x00),指示鼠标的Y轴方向REL_Y,指示鼠标中轮子方向REL_WHEEL。

value:

事件的值:如果事件的类型代码是EV_KEY,当按键按下时值为1,松开时值为0;

       如果事件的类型代码是EV_ REL,value的正数值和负数值分别代表两个不同方向的值。例如:如果code是REL_X,value是10的话,就表示鼠标相对于上一次的坐标,往x轴向右移动10个像素点。

  代码示例

#include <stdio.h>
#include <linux/input.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>

void keyboard_test(){
    
    int fd=open("/dev/input/event4",O_RDWR);
    if(fd <= 0){
        printf("Can not open keyboard input file\n");
     }

    struct input_event *event;
    char buf[128]={0};
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(fd , &rfds);

    while (1) {
        int ret = select(fd +1, &rfds, NULL, NULL, NULL);
        if (ret < 0)
            continue;
        if (FD_ISSET(fd , &rfds)) {
           int readn = read(fd , buf, sizeof(struct input_event));
            if (readn <= 0) {
                printf("uart read error %d\n", readn);
                continue;
            }

            struct input_event *my_event=(struct input_event*)buf;
            if(my_event->type==EV_KEY){
                 printf("zhe shi ge anjian:%d %d\n",my_event->code,my_event->value);
            }
        }
    }
}
int main(){
    keyboard_test();
    return 0;
}
View Code

  调试过程中我遇到的几个错误:1、无法打开设备文件,后想到是权限问题,root权限后依然不行,发现少加了/

推荐阅读