嵌入式Linux—输入子系统

  • 嵌入式Linux—输入子系统已关闭评论
  • 115 次浏览
  • A+
所属分类:linux技术
摘要

常见的输入设备有键盘、鼠标、遥控杆、书写板、触摸屏等等,用户通过这些输入设备与Linux系统进行数据交换。


输入系统

常见的输入设备有键盘、鼠标、遥控杆、书写板、触摸屏等等,用户通过这些输入设备与Linux系统进行数据交换。

内核中怎样表示一个输入设备

// include/linux/input.h struct input_dev { 	const char *name;  //设备名称 	const char *phys;  //设备物理路径 	const char *uniq;  //设备唯一标识码 	struct input_id id;  	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];  	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];   //支持什么类型的输入事件 	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //支持按键输入事件的话,支持哪些按键(键盘) 	unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //支持相对位移事件的话,支持哪些 	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; 	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; 	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; 	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; 	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; 	unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; 	 	....... 	 	}; 

查看所有的输入设备:

ls /dev/input/* -l 

查看输入设备的信息:

cat /proc/bus/input/devices 

得到如下信息:

[root@imx6ull:~]# 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/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0 U: Uniq= H: Handlers=kbd event0 evbug B: PROP=0 B: EV=3 B: KEY=100000 0 0 0  I: Bus=0018 Vendor=dead Product=beef Version=28bb  //设备ID(定义在input.h的struct input_id结构体) N: Name="goodix-ts"      //名称 P: Phys=input/ts         //物理地址 S: Sysfs=/devices/virtual/input/input1  //sys系统地址 U: Uniq=          //标识号(无) H: Handlers=event1 evbug B: PROP=2        //设备属性 B: EV=b          //支持何种输入事件  B: KEY=1c00 0 0 0 0 0 0 0 0 0 0   //设备具有的键 B: ABS=6e18000 0  I: Bus=0019 Vendor=0001 Product=0001 Version=0100 N: Name="gpio-keys" P: Phys=gpio-keys/input0 S: Sysfs=/devices/soc0/gpio-keys/input/input2 U: Uniq= H: Handlers=kbd event2 evbug B: PROP=0 B: EV=3 B: KEY=c  

APP可以获得什么数据

// include/linux/input.h struct input_value { 	__u16 type;   //当前数据的事件类型 	__u16 code;   //当前事件类型下的哪一个事件 	__s32 value;  // }; 

Type的内容:

// include/uapi/linux/input-event-codes.h /*  * Event types  */  #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) 

code的内容(以EV_KEY举例)

// include/uapi/linux/input-event-codes.h #define KEY_RESERVED		0 #define KEY_ESC			1 #define KEY_1			2 #define KEY_2			3 #define KEY_3			4 #define KEY_4			5 #define KEY_5			6 #define KEY_6			7 #define KEY_7			8 #define KEY_8			9 #define KEY_9			10 #define KEY_0			11 

获取输入设备信息实例

两个ioctl的request参数说明(input.h)
request 说明
EVIOCGID 返回输入设备ID
EVIOCGBIT(ev,len) 获取输入设备支持的事件类型列表

ev值的说明:ev参数表示要获取的事件类型,它是一个整数值

  • 当ev=0,表示要获取输入设备支持的所有事件类型列表,包括键盘事件、鼠标事件、相对事件、绝对事件、事件同步、杂项事件等。
  • 当ev=1,表示要获取输入设备支持的键盘事件类型列表。
  • 当ev=2,表示要获取输入设备支持的相对事件类型列表。

EVIOCGBIT的iotcl调用说明:必须使用

len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);     //len是evbit的实际读取大小,如果单独使用sizeof(evbit)得到len,将发生段错误 
源码:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> #include <linux/input.h>  /* 用法:./get_input_info /dev/input/event0 */ int main(int argc, char const **argv) { 	int fd; 	struct input_id id; 	int err; 	unsigned char byte; 	unsigned int evbit[2]; 	int i; 	int bit; 	unsigned int len; 	char *ev_names[] = { 		"EV_SYN ", 		"EV_KEY ", 		"EV_REL ", 		"EV_ABS ", 		"EV_MSC ", 		"EV_SW	", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"NULL ", 		"EV_LED ", 		"EV_SND ", 		"NULL ", 		"EV_REP ", 		"EV_FF	", 		"EV_PWR ", 	};  	if(argc != 2)  	{ 		printf("Usage: %s <dev>n", argv[0]); 		return -1; 	} 	fd = open(argv[1], O_RDWR); 	if(fd == -1)  	{ 		printf("can not open %sn", argv[1]); 		return -1; 	} 	err = ioctl(fd, EVIOCGID, &id);      //返回输入设备ID 	if(err == 0) 	{ 		printf("bustype = 0x%xn", id.bustype ); 		printf("vendor	= 0x%xn", id.vendor  ); 		printf("product = 0x%xn", id.product ); 		printf("version = 0x%xn", id.version ); 	} 	len = ioctl(fd, EVIOCGBIT(0,sizeof(evbit)), evbit);   //返回输入事件类型 	printf("support ev type:n"); 	for(i = 0;i < len;i++) 	{ 		byte = ((unsigned char *)evbit)[i]; 		for(bit = 0;bit < 8;bit++) 		{ 			if(byte & (1<<bit)) 			{ 				printf("%s n", ev_names[i*8 + bit]); 			} 		}  	} 	return 0;  }  
实验结果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event0 bustype = 0x19 vendor  = 0x0 product = 0x0 version = 0x0 support ev type: EV_SYN EV_KEY [root@imx6ull:/mnt]# ./get_input_info /dev/input/event1 bustype = 0x18 vendor  = 0xdead product = 0xbeef version = 0x28bb support ev type: EV_SYN EV_KEY EV_ABS 
[root@imx6ull:~]# 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/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0 U: Uniq= H: Handlers=kbd event0 evbug 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/input1 U: Uniq= H: Handlers=event1 evbug B: PROP=2 B: EV=b B: KEY=1c00 0 0 0 0 0 0 0 0 0 0 B: ABS=6e18000 0  I: Bus=0019 Vendor=0001 Product=0001 Version=0100 N: Name="gpio-keys" P: Phys=gpio-keys/input0 S: Sysfs=/devices/soc0/gpio-keys/input/input2 U: Uniq= H: Handlers=kbd event2 evbug B: PROP=0 B: EV=3 B: KEY=c 

结论:EV值与程序输出的type结果一致

查询和休眠唤醒方式读输入事件

所谓的阻塞与非阻塞,是在open处声明。当设置为阻塞方式,如果没有输入事件,整个进程都在阻塞态

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> #include <linux/input.h> #include <unistd.h> #include <string.h>   /* 用法:./get_input_info /dev/input/event0 */ int main(int argc, char const **argv) { 	int fd; 	unsigned int len; 	struct input_event event;   //read读到的是input_event类型的结构体  	if(argc < 2)  	{ 		printf("Usage: %s <dev> [noblock]n", argv[0]); 		return -1; 	} 	if(argc == 3 && !strcmp(argv[2], "noblock")) 	{ 		fd = open(argv[1], O_RDWR | O_NONBLOCK);  //非阻塞(查询) 	} 	else 	{ 		fd = open(argv[1], O_RDWR); 	} 	if(fd == -1)  	{ 		printf("can not open %sn", argv[1]); 		return -1; 	}  	while(1) 	{ 		len = read(fd, &event, sizeof(event));     //阻塞方式下,进程阻塞在此 		if(len == sizeof(event)) 		{ 			printf("type = 0x%x, code = 0x%x, value = 0x%x", event.type, event.code, event.value); 		} 		else 		{ 			printf("read err %d", len); 		} 	} 	return 0;  }  
实验现象:
  • 查询方式(非阻塞):反复查询,输出"read err",直到操作输入设备时,输出内容更改为输入事件内容
  • 休眠-唤醒方式(阻塞):只有操作屏幕,才会输出事件内容

POLL方式读输入事件

poll会在设定的时间内进行监听,当改时间内有输入事件返回或超过设定时间没有事件返回,poll都将唤醒。poll/select函数可以监测多个文件,可以监测多种事件。

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> #include <linux/input.h> #include <unistd.h> #include <string.h> #include <poll.h>   /* 用法:./get_input_info /dev/input/event0 */ int main(int argc, char const **argv) { 	int fd; 	struct input_event event;   //read读到的是input_event类型的结构体 	struct pollfd pollfd; 	nfds_t nfds = 1;      //同时打开一个文件  	if(argc != 2)  	{ 		printf("Usage: %s <dev>n", argv[0]); 		return -1; 	} 	fd = open(argv[1], O_RDWR | O_NONBLOCK);  //非阻塞(查询) 	if(fd == -1)  	{ 		printf("can not open %sn", argv[1]); 		return -1; 	} 	while(1) 	{ 		pollfd.fd = fd; 		pollfd.events = POLLIN; 		pollfd.revents = 0;     //revents初始化为0,当有输入事件传入,内核改写revents 		poll(&pollfd, nfds, 3000);    //poll等待时间为3s 		if(pollfd.revents == POLLIN)      //只有poll函数返回了数据,才调用read 		{ 			while(read(fd, &event, sizeof(event)) == sizeof(event))    //把一次获取到的数据读完再退出 			{ 				printf("type = 0x%x, code = 0x%x, value = 0x%xn", event.type, event.code, event.value); 			} 		} 		else if(pollfd.revents == 0) 		{ 			printf("time outn"); 		} 		else 		{ 			printf("read errn"); 		} 	} 	return 0;  } 
关于POLL实现多路复用IO
struct pollfd pollfd[n];    //n为文件个数 nfds_t nfds = n;      //同时打开n个文件  .......  if(pollfd[0].revents == POLLIN){}     //依次访问revents if(pollfd[1].revents == POLLIN){}  ....... 

异步通知方式读输入事件

[补充]fcntl的五个功能:

  • 复制一个现有的描述符(cmd=F_DUPFD).
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  • 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> #include <linux/input.h> #include <unistd.h> #include <string.h> #include <signal.h>  int fd;  void sig_func(int sig) { 	struct input_event event; 	while(read(fd, &event, sizeof(event)) == sizeof(event)) 	{ 		printf("type = 0x%x, code = 0x%x, value = 0x%xn", event.type, event.code, event.value); 	} }   /* 用法:./get_input_info /dev/input/event0 */ int main(int argc, char const **argv) {	 	int count = 0; 	unsigned short flag; 	if(argc != 2)  	{ 		printf("Usage: %s <dev>n", argv[0]); 		return -1; 	} 	signal(SIGIO, sig_func);   //1.注册信号处理函数(信号类型为IO类型) 	fd = open(argv[1], O_RDWR | O_NONBLOCK);  //2.打开驱动(一定要用非阻塞方式,否则无输入事件进程一直被阻塞) 	if(fd == -1)  	{ 		printf("can not open %sn", argv[1]); 		return -1; 	} 	fcntl(fd ,F_SETOWN, getpid());   //3.告知驱动程序app进程ID 	flag = fcntl(fd, F_GETFL);       //4.获得文件状态标记 	fcntl(fd, F_SETFL, flag | FASYNC); //5.设置文件状态标记(将进程添加到驱动fasync事件等待队列) 	 	while(1) 	{ 		printf("count = %dn", count++); 		sleep(2); 	} 	return 0;  }  
实验结果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event1 count = 0 count = 1 count = 2               //无输入事件时正常计数 type = 0x3, code = 0x39, value = 0x6 type = 0x3, code = 0x35, value = 0x1a6 type = 0x3, code = 0x36, value = 0x131 type = 0x3, code = 0x30, value = 0x1f type = 0x3, code = 0x3a, value = 0x1f type = 0x1, code = 0x14a, value = 0x1 type = 0x0, code = 0x0, value = 0x0 count = 3 type = 0x3, code = 0x35, value = 0x1a7 type = 0x0, code = 0x0, value = 0x0 count = 4 type = 0x3, code = 0x35, value = 0x1a9 type = 0x0, code = 0x0, value = 0x0 count = 5 type = 0x3, code = 0x35, value = 0x1a8 type = 0x0, code = 0x0, value = 0x0 count = 6