第十八节 检测按键输入(input 子系统)

本章讲解Linux input 输入子系统驱动相关应用层程序的控制原理。

input 子系统

input 子系统是Linux 对输入设备提供的统一驱动框架。如按键、键盘、触摸屏和鼠标等输入设备的驱动方式是类似的,当出现按键、触摸等操作时,硬件产生中断,然后CPU 直接读取引脚电平,或通过SPI、I2C 等通讯方式从设备的寄存器读取具体的按键值或触摸坐标,然后把这些信息提交给内核。使用input 子系统驱动的输入设备可以通过统一的数据结构提交给内核,该数据结构包括输入的时间、类型、代号以及具体的键值或坐标,而内则通过/dev/input 目录下的文件接口传递给用户空间。

在Linux 内核源码的“Documentation/input”目录包含了input 子系统相关的说明。

本开发板默认的出厂镜像中,按键、触摸屏、鼠标、键盘都使用了input 子系统驱动,本章使用按键进行讲解。

input 事件目录

使用evtest 工具测试

在开发input 子系统驱动时,常常会使用evtest 工具进行测试,此处我们通过该工具来了解Ubuntu主机或开发板上的输入设备。

在Ubuntu 主机下使用如下命令测试:

# 在主机下执行如下命令
# 安装evtest 工具
sudo apt install evtest -y
# 使用sudo 权限运行evtest 工具
sudo evtest
# 根据自己主机的输出来选择某个设备测试,下图选择的是“6”,鼠标
# 根据选择的设备测试,如选择的键盘就按键盘,选择鼠标就移植鼠标

如下图:

在这里插入图片描述
上图的执行过程说明如下:

  • 运行evtest 工具,它列出了系统当前可用的/dev/input/event0~6 输入事件文件,并且列出了这些事件对应的设备名。

  • 我们根据设备名的“VirtualBox mouse intergration”推猜它就是接入到电脑的鼠标,所以输入了它对应的event6事件编号6,实验时请根据自己电脑的输出来选择。

  • 输入编号后它列出了event6 的一些设备信息,包括驱动版本、设备ID、设备名、支持的事件类型、事件代号以及输入值的取值范围。

  • 此时移动鼠标,可以看到它输出了详细的事件信息,如果移动后没有输出,说明你选择的不是鼠标设备,请退出重新选择。输出信息中每一行包含了鼠标上报事件的具体时间time、事件类型type3(EV_ABS)、事件代号code1 或code0(ABS_Y 或ABS_X)和具体的值value,该值就是鼠标X/Y 的坐标。

input 事件结构

evtest 工具的原理并不神秘,学习本章节后也可以尝试自己使用代码实现它的部分功能。列出可用事件时,它就是通过查看目录“/dev/input/”实现的。本示例中主机的“/dev/input”目录的内容如下图所示。

在这里插入图片描述

可看到“/dev/input”目录下,有event*、js*、mouse* 及mice 文件,它们分别是驱动层evdev(通用输入事件)、joydev(游戏杆)及遗留的mousedev(鼠标)设备暴露到用户空间的访问接口文件,读取这些文件的内容可获取到设备上报的信息。

在前面LED、GPIO 子系统中,brightness、direction 等设备文件直接使用字符串来记录具体的信息,所以使用cat 命令输出文件的内容时,字符串的形式非常方便我们阅读。但是event 文件包含的信息较多,使用字符串不方便其它程序处理,它采用了纯粹的内核事件数据结构来记录内容,其它程序使用时应把读取到的内容按数据的结构进行格式化转换,该数据结构定义如下所示。

列表1: input_event 结构体

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 坐标。

如果同样使用cat 命令查看事件文件,当事件出现时,cat 把内容转化成字符串,会看到乱码,使用这样的方式可以简单地查看设备是否上报了事件。

可使用以下方式可进行测试:

# 根据自己主机上的事件号修改要查看的具体事件文件
# 此处使用的event6 是本主机的鼠标设备,注意要使用sudo 权限
sudo cat /dev/input/event6
# 输入命令后移动鼠标,会看到字符

如下图;

在这里插入图片描述

与其它文件不同,通常cat 命令读取文件内容后就会返回,而此处读取event 文件时,命令会持续地等待输入。

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 mouseintegration”,evtest 工具列出的事件与设备名的关系,就是从这里读取的。

开发板按键检测实验

前面在Ubuntu 上介绍的input 子系统知识点完全适用于开发板上的按键控制,也可以通过sudoapt install evtest -y 命令下载evtest,使用evtest 进行测试。

在我们编写应用程序读取按键状态前,先查看/dev/input/ 下的文件确定需要控制的按键设备。如下所示

root@npi:/home/debian# ls /dev/input/
by-path event0

在STM32MP157 板子上只存在一个event0,可通过判断event0 的code 的值判断由哪个按键触发。

实验代码分析

在输入事件检测的应用中,通常使用主线程直接循环读取“/dev/input/event*”设备文件获取事件的数据结构,然后通过消息队列通知其它子线程,从而响应输入操作。

本实验仅使用了一个main.c 文件,如下所示。

列表2: 输入设备检测

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <linux/input.h>
#include <linux/input-event-codes.h>

const char * path = "/dev/input/event0";

int main(char argc,char *argv[])
{
	int ret;

	int fd;
	struct input_event event;

	fd = open(path,O_RDONLY);
	if(fd < 0)
	{
		perror(path);
		exit(-1);
	}

	while(1)
	{
		ret = read(fd,&event,sizeof(struct input_event));
		if(ret == sizeof(struct input_event))
		{
			//EV_SYN 是事件分隔标志,不打印
			if(event.type != EV_SYN)
			{
				printf("Event: time %ld.%ld,",event.time.tv_sec,event.time.tv_usec);
				printf("type:%d,code:%d,value:%d\n", event.type,event.code,event.value);
			}
		}
	}

	close(fd);

	return 0;
}

本代码的说明如下:

  • 第12 行:定义默认设备路径,此处使用的是开发板KEY 按键。

  • 第21 行:使用O_RDONLY 模式打开事件设备文件,O_RDONLY 模式默认是阻塞型的,而且事件设备文件支持阻塞操作,也就是说,若后面使用read 函数读取时,它会等待事件上报,一直等待至读取成功或失败才会返回。

  • 第28 行:在while 循环里通过read系统调用读取事件文件,读取到的内容存储在“structinput_event”类型的event 变量中,“struct input_event”类型就是前面介绍的内核事件数据结构。若成功读取,我们就可以通过该变量的结构体成员访问到事件的时间戳、类型、代号和值。

  • 第34-38 行:输出读取到的event 变量的各个成员值,在上报的事件中,通常会有很多类型为EV_SYN的事件,这种事件是用于分隔的,无特别意义,所以代码中不输出这类型事件的内容。

假如我们使用的是GPIO 子系统框架来编写按键驱动程序,在应用层的操作中,需要使用“/sys/class/gpio/gpio*/direction”文件配置为输入方向,然后使用循环读取“/sys/class/gpio/gpio*/value”文件的值来获得按键的状态,但由于对value 文件的read 读取操作不会阻塞,所以进程会不停地读取文件内容来判断按键值,占用CPU 宝贵的运算资源。

由于read 事件文件操作会阻塞,那么采用这种方式就无法同时检测两个输入设备了,这种时候可以通过select 或poll 等IO 多路复用的操作达成目的,这在后续的章节再进行讲解。

编译及测试

本实验使用的Makefile 相对于前面的章节仅修改了最终的可执行文件名为input_demo。

x86 架构

本实验的main.c 实验代码使用的事件设备文件默认是开发板上的KEY 按键,在Ubuntu 主机上并没有这样的设备,如果想尝试在主机上使用,可以根据自己Ubuntu 主机上存在的事件修改作为程序的打开设备,在x86 平台的编译测试过程如下:

# 在主机的实验代码Makefile 目录下编译
# 默认编译x86 平台的程序
make

ARM 架构

对于ARM 架构的程序,可使用如下步骤进行编译:

# 在主机的实验代码Makefile 目录下编译
# 编译arm 平台的程序
make ARCH=arm

编译后生成的ARM 平台程序为build_arm/input_demo,使用网络文件系统共享至开发板,程序默认使用KEY 按键检测。

# 以下命令在开发板上的终端执行
# 在NFS 共享的工程目录路径执行
# 使用默认的KEY 按键运行
# 运行需要root 权限,要使用sudo 运行
sudo ./build_arm/input_demo
# 按下开发板的KEY 按键会有输出

依次按下板子上的“WAKE UP”、“KEY1”、“KEY2”,现象如下

root@npi:/home/debian# ./key_input
Event: time 1614912082.418929,type:1,code:28,value:0
Event: time 1614912082.588967,type:1,code:28,value:1
Event: time 1614912083.298926,type:1,code:11,value:1
Event: time 1614912083.438973,type:1,code:11,value:0
Event: time 1614912084.188999,type:1,code:2,value:1
Event: time 1614912084.358951,type:1,code:2,value:0

其中对应按键WAKE UP 对应的code 值为28,KEY1 对应11,KEY2 对应2。


参考资料:Linux 基础与应用开发实战指南——基于STM32MP1 系列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值