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


            本章学习输入设备的应用编程,首先要知道什么是输入设备?输入设备其实就是能够产生输入事件的设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统。
            对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当前触摸点的 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 结构体类型数据,该结构体定义在 头文件中
    struct input_event {
    struct timeval time ;
    __u16 type ;
    __u16 code ;
    __s32 value ;
    };

    3 个成员变量 typecodevalue 更为重要

    type type 用于描述发生了哪一种类型的事件(对事件的分类), Linux 系统所支持的输入事件类型如下所示:(分类)
    1. /*
    2. * Event types
    3. */
    4. #define EV_SYN 0x00 //同步类事件,用于同步事件
    5. #define EV_KEY 0x01 //按键类事件
    6. #define EV_REL 0x02 //相对位移类事件(譬如鼠标)
    7. #define EV_ABS 0x03 //绝对位移类事件(譬如触摸屏)
    8. #define EV_MSC 0x04 //其它杂类事件
    9. #define EV_SW 0x05
    10. #define EV_LED 0x11
    11. #define EV_SND 0x12
    12. #define EV_REP 0x14
    13. #define EV_FF 0x15
    14. #define EV_PWR 0x16
    15. #define EV_FF_STATUS 0x17
    16. #define EV_MAX 0x1f
    17. #define EV_CNT (EV_MAX+1)

    code code 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了一系列具体事件,譬如一个键盘上通常有很多按键,譬如字母 A B C D 或者数字 1 2 3 4 等,而 code变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件,譬如按键类事件:(哪一类的具体事件)
    大家可以自己浏览 头文件(这些宏其实是定义在 input-event-codes.h 头文件中,该头文件被 所包含了
    1. #define KEY_RESERVED 0
    2. #define KEY_ESC 1 //ESC 键
    3. #define KEY_1 2 //数字 1 键
    4. #define KEY_2 3 //数字 2 键
    5. #define KEY_TAB 15 //TAB 键
    6. #define KEY_Q 16 //字母 Q 键
    7. #define KEY_W 17 //字母 W 键
    8. #define KEY_E 18 //字母 E 键
    9. #define KEY_R 19 //字母 R 键
    1. #define REL_X 0x00 //X 轴
    2. #define REL_Y 0x01 //Y 轴
    3. #define REL_Z 0x02 //Z 轴
    4. #define REL_RX 0x03
    5. #define REL_RY 0x04
    6. #define REL_RZ 0x05
    7. #define REL_HWHEEL 0x06
    8. #define REL_DIAL 0x07
    9. #define REL_WHEEL 0x08
    10. #define REL_MISC 0x09
    11. #define REL_MAX 0x0f
    12. #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 值而定!
    数据同步
            应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的,内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。
     
    1. /*
    2. * Synchronization events.
    3. */
    4. #define SYN_REPORT 0
    5. #define SYN_CONFIG 1
    6. #define SYN_MT_REPORT 2
    7. #define SYN_DROPPED 3
    8. #define SYN_MAX 0xf
    9. #define SYN_CNT (SYN_MAX+1)
    10. 所以的输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0
    读取 struct input_event 数据
            根据前面的介绍可知,对输入设备调用 read() 会读取到一个 struct input_event 类型数据,本小节编写一个简单地应用程序,将读取到的 struct input_event 类型数据中的每一个元素打印出来、并对它们进行解析。
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main(int argc, char *argv[])
    9. {
    10. struct input_event in_ev = {0};
    11. int fd = -1;
    12. /* 校验传参 */
    13. if (2 != argc) {
    14. fprintf(stderr, "usage: %s \n", argv[0]);
    15. exit(-1);
    16. }
    17. /* 打开文件 */
    18. if (0 > (fd = open(argv[1], O_RDONLY))) {
    19. perror("open error");
    20. exit(-1);
    21. }
    22. for ( ; ; ) {
    23. /* 循环读取数据 */
    24. if (sizeof(struct input_event) !=
    25. read(fd, &in_ev, sizeof(struct input_event))) {
    26. perror("read error");
    27. exit(-1);
    28. }
    29. printf("type:%d code:%d value:%d\n",
    30. in_ev.type, in_ev.code, in_ev.value);
    31. }
    32. }
    执行程序时需要传入参数,这个参数就是对应的输入设备的设备节点(设备文件),程序中会对传参进行校验。程序中首先调用 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 。接下来编写按键应用程序,读取按键状态并将结果打印出来,代码如下所示:
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main(int argc, char *argv[])
    9. {
    10. struct input_event in_ev = {0};
    11. int fd = -1;
    12. int value = -1;
    13. /* 校验传参 */
    14. if (2 != argc) {
    15. fprintf(stderr, "usage: %s \n", argv[0]);
    16. exit(-1);
    17. }
    18. /* 打开文件 */
    19. if (0 > (fd = open(argv[1], O_RDONLY))) {
    20. perror("open error");
    21. exit(-1);
    22. }
    23. for ( ; ; ) {
    24. /* 循环读取数据 */
    25. if (sizeof(struct input_event) !=
    26. read(fd, &in_ev, sizeof(struct input_event))) {
    27. perror("read error");
    28. exit(-1);
    29. }
    30. if (EV_KEY == in_ev.type) { //按键事件
    31. switch (in_ev.value) {
    32. case 0:
    33. printf("code<%d>: 松开\n", in_ev.code);
    34. break;
    35. case 1:
    36. printf("code<%d>: 按下\n", in_ev.code);
    37. break;
    38. case 2:
    39. printf("code<%d>: 长按\n", in_ev.code);
    40. break;
    41. }
    42. }
    43. }
    44. }
    ./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
          

     

  • 相关阅读:
    7-1 后序和中序构造二叉树
    golang单线程对比map与bigCache小对象存取性能差别
    多节点树的层序遍历
    GO Http 请求
    EAP-TLS实验之Ubuntu20.04环境搭建配置(FreeRADIUS3.0)(四)
    [网络工程师]-传输层协议-TCP协议
    Java8 Collectors.toMap() 的使用
    华为推出最速超级充电桩 号称1秒1公里 | 百能云芯
    前端入门(一)JavaScript语法、数据类型、运算、函数
    m认知无线电网络中频谱感知的按需路由算法matlab仿真
  • 原文地址:https://blog.csdn.net/internetv/article/details/127755424