Linux应用层例程3 输入设备应用编程

发布于:2022-11-09 ⋅ 阅读:(1132) ⋅ 点赞:(1)
        本章学习输入设备的应用编程,首先要知道什么是输入设备?输入设备其实就是能够产生输入事件的设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统。
        对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当前触摸点的 X、 Y 轴位置信息以及触摸屏当前处于按下还是松开状态。

驱动层 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 ;
};

3 个成员变量 typecodevalue 更为重要

type type 用于描述发生了哪一种类型的事件(对事件的分类), Linux 系统所支持的输入事件类型如下所示:(分类)
/*
* 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 code 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了一系列具体事件,譬如一个键盘上通常有很多按键,譬如字母 A B C D 或者数字 1 2 3 4 等,而 code变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件,譬如按键类事件:(哪一类的具体事件)
大家可以自己浏览 <linux/input.h> 头文件(这些宏其实是定义在 input-event-codes.h 头文件中,该头文件被 <linux/input.h> 所包含了
#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)
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 值而定!
数据同步
        应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的,内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。
 
/*
* Synchronization events.
*/
#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。
读取 struct input_event 数据
        根据前面的介绍可知,对输入设备调用 read() 会读取到一个 struct input_event 类型数据,本小节编写一个简单地应用程序,将读取到的 struct input_event 类型数据中的每一个元素打印出来、并对它们进行解析。
#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);
 }
}
执行程序时需要传入参数,这个参数就是对应的输入设备的设备节点(设备文件),程序中会对传参进行校验。程序中首先调用 open() 函数打开设备文件,之后在 for 循环中调用 read() 函数读取文件,将读取到的数据存放在 struct input_event 结构体对象中,之后将结构体对象中的各个成员变量打印出来。注意,程序中使用了阻塞式 I/O 方式读取设备文件,所以当无数据可读时 read 调用会被阻塞,知道有数据可读时才会被唤醒
使用开发板按键进行输入设备程序测试
        程序运行后,执行按下 KEY0 、松开 KEY0 等操作,终端将会打印出相应的信息,如上图所示。
        第一行中 type 等于 1 ,表示上报的是按键事件 EV_KEY code=114 ,打开 input-event-codes.h 头文件进行查找,可以发现 cpde=114 对应的是键盘上的 KEY_VOLUMEDOWN 按键,这个是 ALPHA/Mini 开发板出厂系统已经配置好的。而 value=1 表示按键按下,所以整个第一行的意思就是按键 KEY_VOLUMEDOWN 被按下。
        第二行,表示上报了 EV_SYN 同步类事件( type=0 )中的 SYN_REPORT 事件( code=0 ),表示本轮数据已经完整、报告同步。
        第三行,type 等于 1 ,表示按键类事件, code 等于 114 value 等于 0 ,所以表示按键 KEY_VOLUMEDOWN 被松开。
        第四行,又上报了同步事件。
可以看到上报按键事件时,对应的 value 等于 2 ,表示长按状态

按键应用编程(同时可以做触摸屏点触判断,单点和多点) 

        本小节编写一个应用程序,获取按键状态,判断按键当前是按下、松开或长按状态。从上面打印的信息可知,对于按键来说,它的事件上报流程如下所示:
# 以字母 A 键为例
KEY_A  //上报 KEY_A 事件
SYN_REPORT  //同步
        如果是按下,则上报 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};
 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;
 }
 }
 }
}
./testApp /dev/input/event2 # 测试开发板上的 KEY0

 

输入例程还可以做触摸屏点触输入,使用 tslib 库(移植)

        tslib 是专门为触摸屏设备所开发的 Linux 应用层函数库,并且是开源,也就意味着我们可以直接获取到tslib 的源代码,下一小节将向大家介绍如何获取到 tslib 的源代码。
        tslib 为触摸屏驱动和应用层之间的适配层,它把应用程序中读取触摸屏 struct input_event 类型数据(这是输入设备上报给应用层的原始数据)并进行解析的操作过程进行了封装,向使用者提供了封装好的 API 接口。tslib 从触摸屏中获得原始的坐标数据,并通过一系列的去噪、去抖、坐标变换等操作,来去除噪声并将原始的触摸屏坐标转换为相应的屏幕坐标。
        tslib 有一个配置文件 ts.conf ,该配置文件中提供了一些配置参数、用户可以对其进行修改,具体的配置信息稍后介绍!
        tslib 可以作为 Qt 的触摸屏输入插件,为 Qt 提供触摸输入支持,如果在嵌入式 Linux 硬件平台下开发过 Qt 应用程序的读者应该知道;当然,并不是只有 tslib 才能作为 Qt 的插件、为其提供触摸输入支持,还有很多插件都可以,只不过大部分都会选择使用 tslib
      

 

本文含有隐藏内容,请 开通VIP 后查看