10. input子系统:按键检测

本章介绍基于Linux input输入子系统驱动相关应用层程序相关编程。

10.1. input子系统

input子系统是Linux对输入设备提供的统一驱动框架。 如按键、键盘、触摸屏和鼠标等输入设备的驱动方式是类似的, 使用input子系统驱动的输入设备可以通过统一的数据结构提交给内核, 该数据结构包括输入的时间、类型、代号以及具体的键值或坐标, 内核通过/dev/input目录下的文件接口传递给用户空间。

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

本章以按键进行讲解input子系统的使用。

10.2. input事件目录

10.2.1. input事件结构

event文件包含的信息采用内核事件数据结构来记录内容, 当其它程序使用时需要把读取到的内容按数据的结构进行格式化转换,该数据结构定义如下所示。

input_event结构体(内核源码的/include/uapi/linux/input.h文件)
1
2
3
4
5
6
struct input_event {
   struct timeval time;
   __u16 type;
   __u16 code;
   __s32 value;
};
  • time:该变量用于记录事件产生的时间戳。

  • type:输入设备的事件类型。系统常用的默认类型有EV_KEY、 EV_REL和EV_ABS,分别用 于表示按键状态改变事件、相对坐标改变事件及绝对坐标改变事件,特别地,EV_SYN用于分隔事件,无特别意义。 相关的枚举值可以参考内核文件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是Ubuntu主机的鼠标设备):

未找到图片04|

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

10.2.2. input事件设备名

“/dev/input/event*”的事件编号与设备的联系不是固定的,它通常按系统检测 到设备的先后顺序安排event文件的编号,这对编写应用程序控制不太方便,我们 可以通过“/dev/input/by-id”或“/dev/input/by-path”目录查看具体的硬件设备,如 下图所示。

未找到图片05|

图中列出了by-path目录下的内容, 而且该文件名与硬件的关系是固定的,后面我们的实验就是采用这样的方式。

10.3. 开发板按键检测实验

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

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

1
2
root@lubancat:~# ls /dev/input/by-path/
platform-30370000.snvs:snvs-powerkey-event  platform-gpio-keys-event

其中

  • platform-30370000.snvs:snvs-powerkey-event 对应的为板卡上on/off按键。

  • platform-gpio-keys-event 对应板子上的KEY0按键。

如果在 /dev/input/by-path/目录下未出现platform-gpio-keys-event,请取消 /boot/uEnv.txt 中的 dtoverlay=/usr/lib/linux-image-5.4.47-imx8mm/freescale/overlays/imx8mm-fire-keys.dtbo 注释,然后重启板卡。

下面以 KEY0 为例编写按键检测的应用程序。

10.3.1. 实验代码分析

在输入事件检测的应用中,通常使用主线程直接 循环读取“/dev/input/event*”设备文件获取事件的数据结构,然后通过消 息队列通知其它子线程,从而响应输入操作。此处我们将使用 /dev/input/by-path/platform-gpio-keys-event 设备文件,读者可自行修改设备文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#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/by-path/platform-gpio-keys-event";

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;
}

在上报的事件中,通常会有很多类型为EV_SYN的事件,这种事件是用于分隔的, 无特别意义,所以代码中不输出这类型事件的内容。

最后在板卡上使用gcc进行编译即可。

1
gcc -o input_key input_key.c

10.4. 按键设备树插件修改

野火IMX8M提供了很多的设备树插件源码,若想要添加或修改不同的引脚作为按键引脚, 可参考:

其中按键的设备树插件文件为 imx8mm-fire-keys-overlay.dts ,源码如下所示

/arch/arm64/boot/dts/freescale/overlays/imx8mm-fire-keys-overlay.dts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/dts-v1/;
/plugin/;

#include "../imx8mm-pinfunc.h"
#include "dt-bindings/gpio/gpio.h"
#include "dt-bindings/input/linux-event-codes.h"


/ {
     fragment@0 {
             target-path = "/";
             __overlay__ {
                     gpio-keys {
                             compatible = "gpio-keys";
                             pinctrl-names = "default";
                             pinctrl-0 = <&pinctrl_gpio_keys>;

                             Key0 {
                                     label = "Key 0";
                                     gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
                                     linux,code=<KEY_0>;
                             };

                     };
             };
     };

     fragment@1 {
             target= <&iomuxc>;
             __overlay__{
                     pinctrl_gpio_keys: keysgrp{
                             fsl,pins = <
                                 MX8MM_IOMUXC_GPIO1_IO10_GPIO1_IO10      0x19
                             >;
                     };
             };
     };

关于输入子系统设备树的详细说明可参考内核目录下:Documentation/devicetree/bindings/input 文档。

假如想要再添加GPIO5_IO17引脚(此引脚为排针引脚,仅作为举例说明)作为按键引脚,修改也是相当容易, 只需按照以下方式改写设备树插件即可, 源码如下所示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/dts-v1/;
/plugin/;

#include "../imx8mm-pinfunc.h"
#include "dt-bindings/gpio/gpio.h"
#include "dt-bindings/input/linux-event-codes.h"

   / {
     fragment@0 {
             target-path = "/";
             __overlay__ {
                     gpio-keys {
                             compatible = "gpio-keys";
                                Key0{
                                pinctrl-names = "default";
                                pinctrl-0 = <&pinctrl_gpio_key0>;
                                label = "Key 0";
                                gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
                                linux,code=<KEY_0>;
                             };

                             Key1{
                                pinctrl-names = "default";
                                pinctrl-0 = <&pinctrl_gpio_key1>;
                                label = "Key 1";
                                gpios = <&gpio5 17 GPIO_ACTIVE_LOW>;
                                linux,code=<KEY_1>;
                             };

                     };
             };
     };

     fragment@1 {
             target = <&iomuxc>;
             __overlay__ {
                     pinctrl_gpio_key0: key0sgrp{
                     fsl,pins = <
                             MX8MM_IOMUXC_GPIO1_IO10_GPIO1_IO10    0x19
                     >;
                     };
                     pinctrl_gpio_key1: key1sgrp{
                     fsl,pins = <
                             MX8MM_IOMUXC_I2C2_SDA_GPIO5_IO17      0x19
                     >;
                     };
      };
   };
};

按照以上代码修改设备树,应用层的代码不需要改动 同样也是读取 /dev/input/by-path/platform-gpio-keys-event 设备文件内容。 但当按下不同的按键时得到的event.code值是不同的,由设备树插件中的 linux,code 指定。 KEY_0、KEY_1 宏定义在linux-event-codes.h文件中(在内核中的目录为:include/uapi/linux/input-event-codes.h)。