0.概念
输入设备:可以产生输入事件的设备
Linux系统设计了一个兼容所有输入设备的框架,就是input子系统,其直接向应用层提供了一套统一的接口,其在/dev/input目录下。
流程:如果要读取输入设备,一般遵循以下流程。
①、应用程序打开/dev/input/event0 设备文件;
②、应用程序发起读操作(譬如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
③、 当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
④、应用程序对读取到的数据进行解析。
如果无数据读时,程序会进入阻塞状态(休眠),直到产生输入设备,才会被唤醒去读取设备。
当程序打开设备文件,发起读(read)操作,其实每一次都是获取一个struct input_event 结构体类型数据。
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
type:用于描述发生了哪一种类型的事件(对事件的分类),其中Linux输入事件类型有:
#define EV_SYN 0x00 //同步类事件,用于同步事件
#define EV_KEY 0x01 //按键类事件
#define EV_REL 0x02 //相对位移类事件(譬如鼠标)
#define EV_ABS 0x03 //绝对位移类事件(譬如触摸屏)
#define EV_MSC 0x04 //其它杂类事件
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
以上这些宏定义也是在<linux/input.h>头文件中 |
code :表示该类事件中的哪一个具体事件, 以上列举的每一种事件类型中都包含了一系列具体事件, 譬如一个键盘上通常有很多按键, 譬如字母 A、 B、 C、 D 或者数字 1、 2、 3、 4 等, 而 code变量则告知应用程序是哪一个按键发生了输入事件。对于事件有:
按键类事件:
#define KEY_RESERVED 0
#define KEY_ESC 1 //ESC 键
#define KEY_1 2 //数字 1 键
#define KEY_2 3 //数字 2 键
#define KEY_TAB 15 //TAB 键
#define KEY_Q 16 //字母 Q 键
#define KEY_W 17 //字母 W 键
#define KEY_E 18 //字母 E 键
#define KEY_R 19 //字母 R 键
……
相对位移事件:
#define REL_X 0x00 //X 轴
#define REL_Y 0x01 //Y 轴
#define REL_Z 0x02 //Z 轴
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)
绝对位移事件:
define ABS_X 0x00 //X 轴
#define ABS_Y 0x01 //Y 轴
#define ABS_Z 0x02 //Z 轴
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ABS_THROTTLE 0x06
#define ABS_RUDDER 0x07
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
#define ABS_HAT1Y 0x13
#define ABS_HAT2X 0x14
#define ABS_HAT2Y 0x15
#define ABS_HAT3X 0x16
#define ABS_HAT3Y 0x17
#define ABS_PRESSURE 0x18
#define ABS_DISTANCE 0x19
#define ABS_TILT_X 0x1a
#define ABS_TILT_Y 0x1b
#define ABS_TOOL_WIDTH 0x1c
......
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 值而定!
数据同步:
在进行那么多操作后,程序需要读到完整数据,而这则就需要用到数据同步。
内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、 可以进行同步了,可以实现数据完整性。
同步类事件中也包含了多种不同的事件,如下所示:
#define SYN_REPORT 0
#define SYN_CONFIG 1
#define SYN_MT_REPORT 2
#define SYN_DROPPED 3
#define SYN_MAX 0xf
#define SYN_CNT (SYN_MAX+1)
上报的同步事件通常是 SYN_REPORT, 而 value 值通常为 0。
1.输入结构体数据读取实验
任务:从上可知,输入设备调用 read()会读取到一个 struct input_event 类型数据,因此该任务需要将读取到的struct input_event 类型数据中的每一个元素打印出来。
(Tips:设备文件不同于普通文件,读写设备文件之前无需设置读写位置偏移量)源码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
struct input_event in_ev = {0};
int fd = -1;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(-1);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(-1);
}
for ( ; ; ) {
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
exit(-1);
}
printf("type:%d code:%d value:%d\n",
in_ev.type, in_ev.code, in_ev.value);
}
}
程序理解:
1.struct input_event in_ev = {0};将结构体的所有成员初始化为零。可以确保结构体的所有成员都被初始化为已知的值,避免使用未初始化成员导致的未定义行为,而input_event 结构体在上面有提到。
2.
if (0 > (fd = open(argv[1], O_RDONLY)))
打开输入的第二个参数文件路径,并设为只读,所有你需要在终端输入你要打开的路径。
3.
for ( ; ; ) {
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
exit(-1);
}
printf("type:%d code:%d value:%d\n",
in_ev.type, in_ev.code, in_ev.value);
}
if (sizeof(struct input_event) != read(fd, &in_ev, sizeof(struct input_event)))
这是判断读取操作是否失败或读取的数据不完整。sizeof(struct input_event)是获取 struct input_event
结构体的大小(以字节为单位)。
read(fd, &in_ev, sizeof(struct input_event)):
fd
:文件描述符,通常是一个已经打开的文件或设备。&in_ev
:指向in_ev
变量的指针,表示读取的数据将被存储到这个变量中。sizeof(struct input_event)
:读取的字节数,等于struct input_event
的大小
为什么用这个语句判断?因为read函数读取失败返回-1,而读取成功的话会返回读取到的字节数。
最后打印出结构体的值。
上机测试:
首先在开发板执行cat /proc/bus/input/devices指令,可以看到设备信息:
后回到ubuntu上,
对read_input进行工具链编译,不会配置${CC}环境的可以看我另外一篇文章:
Linux环境变量 ${CC}的配置方法-CSDN博客
编译完后多了read_input文件
后连上开发板,使用scp -r 指令实现文件传输,利用mobaxterm传到开发板上。
scp -r book@192.168.5.12:/home/book/project/APP/app/17_input/read_input /home/root/app
scp -r 【用户名】@【ubuntu的ip地址】:【unbuntu上要传入文件的路径】 【开发板要存入的路径】
执行成功后,对其进行操作:./read_input /dev/input/event2,按下黄色按键可以发现有数据传入
可见上面的type、code、value信息,
type=1,说明是上报的是按键类事件EV_KEY(上面type介绍哪里有图),cpde=114 对应的是键盘上的 KEY_VOLUMEDOWN 按键,这儿开发板对应的是那个黄色按键
type=0那一行, 表示上报了 EV_SYN 同步类事件(type=0)中的 SYN_REPORT 事件(code=0), 表示本轮数据已经完整、报告同步。
如果长按,value则上报2。
2.按键应用实验
任务:如果是按下,则上报 KEY_A 事件时, value=1;如果是松开,则 value=0;如果是长按,则 value=2。接下来编写按键应用程序,读取按键状态并将结果打印出来。
加上注释的源码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
struct input_event in_ev = {0}; // 定义并初始化input_event结构体
int fd = -1; // 文件描述符
int value = -1; // 按键值
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(-1); // 如果参数个数不对,打印用法信息并退出
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(-1); // 打开文件失败,打印错误信息并退出
}
for ( ; ; ) {
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
exit(-1); // 读取数据失败,打印错误信息并退出
}
// 判断事件类型是否为按键事件
if (EV_KEY == in_ev.type) {
switch (in_ev.value) {
case 0:
printf("code<%d>: 松开\n", in_ev.code); // 按键松开事件
break;
case 1:
printf("code<%d>: 按下\n", in_ev.code); // 按键按下事件
break;
case 2:
printf("code<%d>: 长按\n", in_ev.code); // 按键长按事件
break;
}
}
}
}
测试:
工具链编译:
输入 ./read_key /dev/input/event2
code值可以查找到对应的按键