Bootstrap

嵌入式Linux C应用层开发3 输入事件子系统与按钮输入

输入事件子系统

我们下面来考虑一下输入事件的子系统。在这里笔者的输出如下:

cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/platform/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0
​
I: Bus=0018 Vendor=dead Product=beef Version=28bb
N: Name="goodix-ts"
P: Phys=input/ts
S: Sysfs=/devices/virtual/input/input2
U: Uniq=
H: Handlers=event1 
B: PROP=3
B: EV=b
B: KEY=e520 0 0 0 0 0 0 0 0 0 0
B: ABS=2658000 0
​
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio_keys@0"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/gpio_keys@0/input/input3
U: Uniq=
H: Handlers=kbd event2 
B: PROP=0
B: EV=100003
B: KEY=40000 0 0 0
​

目前对于我们而言比较好操作的就是Key0这个摁扭,在我们的开发板上就是key0,对应于kbd event2。

下面我们尝试做简单的编程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/input.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
    struct input_event in_env = {0};
​
    if(argc != 2){
        fprintf(stderr, "usage: %s <input-device>\n", argv[0]);
        return 0;
    }
​
    int fd = open(argv[1], O_RDONLY);
    if(0 > fd){
        perror("open error");
        exit(-1);
    }
​
    for(;;)
    {
        if(sizeof(struct input_event) 
            != read(fd, &in_env, sizeof(struct input_event)))
        {
            perror("read error");
            exit(-1);
        }
        printf("type: %d code %d value: %d\n", in_env.type, in_env.code, in_env.value);
    }
}

我们把结果放到板子里跑一下:

./input /dev/input/event2
type: 1 code 114 value: 1
type: 0 code 0 value: 0
type: 1 code 114 value: 0
type: 0 code 0 value: 0
type: 1 code 114 value: 1
type: 0 code 0 value: 0
type: 1 code 114 value: 0
type: 0 code 0 value: 0
type: 1 code 114 value: 1
type: 0 code 0 value: 0
type: 1 code 114 value: 2
type: 0 code 0 value: 1

现在来看看概念

常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。

input 子系统

由上面的介绍可知,输入设备种类非常多,每种设备上报的数据类型又不一样,那么Linux 系统如何管理呢?Linux 系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就是 input 子系统。驱动开发人员基于 input 子系统开发输入设备的驱动程序,input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。 基于input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件),设备节点名称通常为 eventX(X 表示一个数字编号0、1、2、3 等),譬如/dev/input/event0、/dev/input/event1、/dev/input/event2 等,通过读取这些设备节点可以获取输入设备上报的数据。

读取数据的流程

如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程 如下:

  • 应用程序打开对应的设备文件,比如说在这块板子上是/dev/input/event0 设备文件;

  • 应用程序发起读操作(譬如调用read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);

  • 当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;

  • 应用程序对读取到的数据进行解析。

当无数据可读时,程序会进入休眠状态(也就是阻塞),譬如应用程序读触摸屏数据,如果当前并没有去触碰触摸屏,自然是无数据可读;当我们用手指触摸触摸屏或者在屏上滑动时,此时就会产生触摸数据、应用程序就有数据可读了,应用程序会被唤醒,成功读取到数据。那么对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态(阻塞式I/O 方式下),当有数据可读时才会被唤醒。

应用程序如何解析数据

首先我们要知道,应用程序打开输入设备对应的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢?其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据,该结构体定义在<linux/input.h>头文件中,它的定义如下:

struct input_event { 
 struct timeval time; 
 __u16 type; 
 __u16 code; 
 __s32 value; 
}; 

结构体中的 time 成员变量是一个 struct timeval 类型的变量,该结构体在前面给大家介绍过,内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序。时间参数通常不是那么重要,而其它3 个成员变量type、code、value 更为重要。

type:type 用于描述发生了哪一种类型的事件(对事件的分类)

value:内核每次上报事件都会向应用层发送一个数据 value,对value 值的解释随着code 的变化而变化。譬如对于按键事件(type=1)来说,如果 code=2(键盘上的数字键 1,也就是KEY_1),那么如果value 等于1,则表示KEY_1 键按下;value 等于0 表示KEY_1 键松开,如果value 等于2则表示KEY_1 键长按。再比如,在绝对位移事件中(type=3),如果code=0(触摸点X 坐标ABS_X),那么 value 值就等于触摸点的 X 轴坐标值;同理,如果 code=1(触摸点 Y 坐标 ABS_Y),此时value 值便等于触摸点的Y 轴坐标值;所以对 value 值的解释需要根据不同的 code 值而定!

数据同步

同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次read 操作只能读取一个struct input_event 类型数据,譬如对于触摸屏来说,一个触摸点的信息包含了 X 坐标、Y 坐标以及其它信息,对于这样情况,应用程序需要执行多次read 操作才能把一个触摸点的信息全部读取出来,这样才能得到触摸点的完整信息。 那么应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的,内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。

下一篇我们来尝试一下对触摸屏进行编程!

;