• 嵌入式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 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    /* 用法:./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 \n", argv[0]);
    		return -1;
    	}
    	fd = open(argv[1], O_RDWR);
    	if(fd == -1) 
    	{
    		printf("can not open %s\n", argv[1]);
    		return -1;
    	}
    	err = ioctl(fd, EVIOCGID, &id);      //返回输入设备ID
    	if(err == 0)
    	{
    		printf("bustype = 0x%x\n", id.bustype );
    		printf("vendor	= 0x%x\n", id.vendor  );
    		printf("product = 0x%x\n", id.product );
    		printf("version = 0x%x\n", 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<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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    /* 用法:./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  [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 %s\n", 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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    /* 用法:./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 \n", argv[0]);
    		return -1;
    	}
    	fd = open(argv[1], O_RDWR | O_NONBLOCK);  //非阻塞(查询)
    	if(fd == -1) 
    	{
    		printf("can not open %s\n", 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%x\n", event.type, event.code, event.value);
    			}
    		}
    		else if(pollfd.revents == 0)
    		{
    			printf("time out\n");
    		}
    		else
    		{
    			printf("read err\n");
    		}
    	}
    	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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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%x\n", 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 \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 %s\n", 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 = %d\n", 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
    
  • 相关阅读:
    Weblogic+Oracle11g设置开机自启动
    ResNet网络详解
    洛谷 P5268 [SNOI2017]一个简单的询问(莫队,差分)
    kettle数据迁移从oracle到mysql
    golang的new和make
    嵌入式 QT多界面切换
    在Winform系统开发中,对表格列表中的内容进行分组展示
    行业案例 | 全面防护 赛宁助力能源工控安全建设
    探索Lighthouse性能分数计算背后的奥秘
    pipeline流水线语法
  • 原文地址:https://www.cnblogs.com/Tayoou/p/17157871.html