【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十三章 输入子系统实验

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)


六十三章 输入子系统实验

本章导读

输入设备总类繁杂,包括按键,键盘,触摸屏,鼠标,摇杆等等,它们本身都是字符设备,不过内核

为了能将这些设备的共性抽象出来,简化驱动的开发,建立了一个 Input 子系统。用户只需要根据内核提供的 input 子系统下提供的 API 函数接口,完成设备的注册即可。在本章节中我们来学习一下如何使用 Linux内核中的 input 子系统。

63.1章节讲解了Input子系统简介好驱动程序编写流程,input_event结构体

63.2章节以实验的形式,使用输入子系统设计按键驱动。

本章内容对应视频讲解链接(在线观看):

输入子系统(一)  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=42

输入子系统(二)  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=43

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\020-输入子系统实验”路径下。

63.1 Input子系统

63.1.1 Input子系统简介

Input 子系统就是管理输入的子系统,和 pinctrl 和 gpio 子系统一样,都是 Linux 内核针对某一类设

备而创建的框架。 input 子系统处理输入事务,任何输入设备的驱动程序都可以通过 input 输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。

input 子系统是分层结构的,总共分为三层:硬件驱动层,子系统核心层,事件处理层。

(1)硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。

(2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。

(3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,

触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value),input 子系统支持

的所有事件都定义在 input.h 中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是 硬件

驱动层-->子系统核心-->事件处理层-->用户空间。在节点/dev/input下面则是我们输入设备的节点,如下图所示:

 

这些节点对应的则是我们当前系统的输入,我们可以使用命令来查看当前系统的输入设备,如下图所示:

cat /proc/bus/input/devices

 

那么我们要怎么确定哪个设备对应哪个节点呢?这里教大家一个简单的方法,可以使用命令hexdump确定,hexdump命令是Linux下查看二进制文本的工具。这里我给大家举一个例子:

比如我想确定键盘对应的是哪个节点,我就可以使用命令:

hexdump /dev/input/event0 或者

hexdump /dev/input/event1 或者

hexdump /dev/input/event1 或者

.....

输入完一条命令以后,我们按键盘的上的按键,如果有数据打印出来,则证明当前我们查看的这个节点是键盘这个设备对应的节点。比如,我现在在Ubuntu上输入命令:

 hexdump /dev/input/event1

然后按键盘的按键,这时候有打印信息出现,则证明/dev/input/event1为键盘对应的节点,如下图所示:

 

如上图所示的打印的信息都是什么意思呢?我们上报的数据要按照具体的格式上报给输入子系统的核心层,我们的应用就可以通过设备节点来获得按照具体格式上报来的数据了。封装数据是输入子系统的核心层来帮我们完成的,我们只需要按照指定的类型和这个类型对应的数据来上报给输入子系统的核心层即可。了解了这些概念以后,我们来看一下这个例子:

当我使用命令 hexdump /dev/input/event1后,按下键盘上的回车按键,打印以下内容:

 

那么在这9条信息里面,只有第3条信息代表的是我们回车按键按下的信息,如下图所示: 

其中0000 0001代表的是value,001c代表的是code,0001代表的是type,如下图所示: 

那么tpye等于1,代表的就是按键事件,如下图所示: 

code等于1c,我们把它换成10进制,就是28,对应的就是回车按键,如下图所示: 

value等于1,代表的就是按下,所以第三条信息代表的是按键按下。我们按一下有这么多的打印信息,如果我们只想获得回车按键的打印,我们要怎么做呢?我们可以通过代码来实现,我们一起来看一下:

我们拷贝第59.3章编写的应用程序app.c到Ubuntu的/home/topeet/imx8mm/20目录下,我们在此基础上进行修改,代码如下所示。

/*
 * @Author: topeet
 * @Description: 在Ubuntu系统读取输入事件
 */
#include <stdio.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[])
{
    int fd;

    struct input_event test_event;
    //打开设备节点
    fd = open("/dev/input/event1", O_RDWR);
    if (fd < 0)
    {
        //打开设备节点失败
        perror("open error \n");
        return fd;
    }
    while (1)
    {
        //循环读取设备节点
        read(fd, &test_event, sizeof(test_event));
        //如果输入事件类型为按键输入,则打印输入事件的类型和值
        if (test_event.type == EV_KEY)
        {
            printf("type is %#x \n", test_event.type);
            printf("value is %#x \n", test_event.value);
        }
    }
    close(fd);
    return 0;
}

 我们输入命令编译app.c,并且运行app,如下图所示:

gcc app.c -o app

./app

 

从上图可以看出,我们每次按enter键,会在终端上打印type和value。 

63.1.2 Input驱动程序编写流程

首先来看一下在 input 核心层实现了哪些功能,input 核心层文件是 input.c,路径:drivers/input/input.c,

部分内容如下:

1767 struct class input_class = {
1768 .name = "input",
1769 .devnode = input_devnode,
1770 };
......
2414 static int __init input_init(void)
2415 {
2416 int err;
2417
2418 err = class_register(&input_class);
2419 if (err) {
2420 pr_err("unable to register input_dev class\n");
2421 return err;
2422 }
2423
2424 err = input_proc_init();
2425 if (err)
2426 goto fail1;
2427
2428 err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
2429 INPUT_MAX_CHAR_DEVICES, "input");
2430 if (err) {
2431 pr_err("unable to register char major %d", INPUT_MAJOR);
2432 goto fail2;
2433 }
2434
2435 return 0;
2436
2437 fail2: input_proc_exit();
2438 fail1: class_unregister(&input_class);
2439 return err;
2440 }

第 2418 行,注册了一个 input 类,在系统启动后会在/sys/class 目录下生成一个 input 类的子目录,如下图所示:

第 2428、2489 行,注册了一个字符设备,所以 input 子系统本质上也是字符设备驱动,主设备号为

INPUT_MAJOR,INPUT_MAJOR 定义在 include/uapi/linux/major.h 文件中,定义如下:

#define INPUT_MAJOR 13

所以 input 子系统的所有设备主设备号都为 13,在使用 input 子系统处理输入设备的时候就不需要去

注册字符设备了,我们只需要向系统注册一个 input_device 即可。

1 、注册 input_dev

input_dev 结构体是 input 设备基本的设备结构,每个 input 驱动程序中都必须分配初始化这样一个结构,结构体定义在 include/linux/input.h 文件中,定义如下:

121 struct input_dev {
122 const char *name;
123 const char *phys;
124 const char *uniq;
125 struct input_id id;
126
127 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
128
129 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
130 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
131 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */
132 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */
133 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */
134 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */
135 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的位图 */
136 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */
137 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */
......
189 bool devres_managed;
190 };

第 129 行,evbit 表示输入事件类型,可选的事件类型定义在 include/uapi/linux/input.h 文件中,事件

类型如下:

#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 /* LED */
#define EV_SND 0x12 /* sound(声音) */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */

根据使用的不同设备选择不同的事件类型,在本章的实验中我们会用到按键设备,那么我们就需要选

择 EV_KEY 事件类型。在看 input_dev 结构体中的第 129~137 行的 evbit、keybit 等成员变量,都是对应的不同事件类型的值。比如按键事件对应的 keybit 成员,keybit 就是按键事件使用的位图,Linux 内核定义了很多按键值,这些按键值定义在 include/uapi/linux/input.h 文件中,按键值如下:

215 #define KEY_RESERVED 0
216 #define KEY_ESC 1
217 #define KEY_1 2
218 #define KEY_2 3
219 #define KEY_3 4
220 #define KEY_4 5
221 #define KEY_5 6
222 #define KEY_6 7
223 #define KEY_7 8
224 #define KEY_8 9
225 #define KEY_9 10
226 #define KEY_0 11
......
794 #define BTN_TRIGGER_HAPPY39 0x2e6
795 #define BTN_TRIGGER_HAPPY40 0x2e7

当我们编写 input 设备驱动时需要先创建一个 input_dev 结构体变量,但是不用我们手动创建,input

子系统提供了下面两个函数用于创建和注销 input_dev 结构体变量。

struct input_dev *input_allocate_device(void)     //申请 input_dev 结构体

void input_free_device(struct input_dev *dev//注销 input_dev 结构体

input_allocate_device 函数不需要参数,直接返回申请到的 input_dev 结构体。input_free_device 函数用来释放掉前面申请到的 input_dev 结构体。申请完 input_dev 结构体后,需要进行初始化,根据自己的设备来指定事件类型和事件值,比如按键设备的事件类型是 evbit,事件值是 keybit。

input_dev 结构体初始化完成后,使用 input_register_device 函数向 Linux 内核注册 input_dev 设备。函数原型如下:

函数

int input_register_device(struct input_dev *dev)

dev

要注册的 input_dev

返回值

0,input_dev 注册成功;负值,input_dev 注册失败。

功能

Linux 内核注册 input_dev 设备

同样的,注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册的input_dev,input_unregister_device 函数原型如下:

void input_unregister_device(struct input_dev *dev)

总结上面的内容,input_dev 注册过程分为下面几步:

① 首先使用 input_allocate_device 函数申请一个 input_dev。

② 初始化 input_dev 的事件类型以及事件值。

③ 使用 input_unregister_device 函数向 Linux 系统注册前面初始化好的 input_dev。

④ 卸载 input 驱动的时候需要先使用 input_unregister_device 函数注销掉注 input_dev, 然后使用 input_free_device 函数释放掉前面申请的 input_dev。

input_dev 注册过程实例代码如下:

1 struct input_dev *inputdev; /* input 结构体变量 */
2
3 /* 驱动入口函数 */
4 static int __init xxx_init(void)
5 {
6 ......
7 inputdev = input_allocate_device(); /* 申请 input_dev */
8 inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */
9
10 /*********第一种设置事件和事件值的方法***********/
11 __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
12 __set_bit(EV_REP, inputdev->repbit); /* 重复事件 */
13 __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
14 /************************************************/
15
16 /*********第二种设置事件和事件值的方法***********/
17 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
18 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
19 /************************************************/
20
21 /*********第三种设置事件和事件值的方法***********/
22 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
23 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
24 /************************************************/
25
26 /* 注册 input_dev */
27 input_register_device(inputdev);
28 ......
29 return 0;
30 }
31
32 /* 驱动出口函数 */
33 static void __exit xxx_exit(void)
34 {
35 input_unregister_device(inputdev); /* 注销 input_dev */
36 input_free_device(inputdev); /* 删除 input_dev */
37 }

第 10~23 行都是初始化 input 设备事件和按键值,这里用了三种方法来设置事件和按键值。

2、上报输入事件

在 input 设备驱动中申请、注册完成 input_dev 结构体后,还不能正常使用 input 子系统,因为 input 设备是输入一些信息,但是 Linux 内核还不清楚输入的信息表示什么意思,有什么作用,所以我们需要驱动获取到具体的输入值,或者说输入事件,然后将输入事件上报给 Linux 内核。比如按键设备,我们需要在按键产生后将按键值上报给 Linux 内核,Linux 内核获取到具体的按键值后,才会执行相应的功能。不同的事件上报的函数不同,我们分别来看一下有哪些常用的 API 函数。

input_event 函数:用于上报指定的事件以及对应的值。函数原型如下:

函数

void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value)

dev

需要上报的 input_dev

type

上报的事件类型,比如 EV_KEY

code

事件码,也就是我们注册的按键值,比如 KEY_0、KEY_1 等等。

value

事件值,比如 1 表示按键按下,0 表示按键松开。

返回值

功能

用于上报指定的事件以及对应的值

input_report_key 函数:上报按键事件。具体函数内容如下:

static inline void input_report_key(struct input_dev *devunsigned int codeint value)

{

    input_event(dev, EV_KEY, code, !!value);

}

可以看出,input_report_key 函数的本质就是 input_event 函数,当然使用哪个函数都没有问题,不同的设备使用对应的函数更加合适一点。

同样的还有一些其他事件对应的上报函数:

void input_report_rel(struct input_dev *devunsigned int codeint value)

void input_report_abs(struct input_dev *devunsigned int codeint value)

void input_report_ff_status(struct input_dev *devunsigned int codeint value)

void input_report_switch(struct input_dev *devunsigned int codeint value)

void input_mt_sync(struct input_dev *dev)

input_sync 函数用来告诉 Linux 内核 input 子系统上报结束。input_sync 函数本质上是上报一个同步事件,函数原型如下:

void input_sync(struct input_dev *dev)

列举了好几个函数,以按键设备为例,看一下如何使用:

/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
    if (value == 0)
    {    /* 按下按键 */
        /* 上报按键值 */
        input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1,按下 */
        input_sync(inputdev);                 /* 同步事件 */
    }
    else
    {   /* 按键松开 */
        input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0,松开 */
        input_sync(inputdev);                 /* 同步事件 */
    }
}

获取按键的值,然后判断按键是否按下,通过 input_report_key 函数上报按键的值,input_sync 函数表示上报结束。

63.1.3 input_event结构体

Linux 内核使用 input_event 这个结构体来表示所有的输入事件,input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下:

struct input_event
{
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};

依次来看一下 input_event 结构体中的各个成员变量:

input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体呈现给用户

的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值,比如按键值等。

按下,如果为 0 的话说明按键没有被按下或者按键松开了。

  • time:时间,也就是此事件发生的时间,为 timeval 结构体类型,timeval 结构体定义如下:
  • typedef long __kernel_long_t;
    typedef __kernel_long_t __kernel_time_t;
    typedef __kernel_long_t __kernel_suseconds_t;
    struct timeval
    {
        __kernel_time_t tv_sec;       /* 秒 */
        __kernel_suseconds_t tv_usec; /* 微秒 */
    };

  • tv_sec 和 tv_usec 这两个成员变量都为 long 类型,也就是 32 位,这个一定要记住,后面我们分析
  • event 事件上报数据的时候要用到。

  • type:事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。
  • code:事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0、KEY_1 等等这些按键。此成员变量为 16 位。
  • value:值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键

63.2编写实验程序

我们以IMX8MM开发板为例,将开发板上的音量+ 按键值设置为KEY_VOLUMEUP,使用输入子系统设计按键驱动。

63.2.1 编写驱动程序

我们新建driver.c文件到Ubuntu的/home/topeet/imx8m/20目录下,完整的代码如下所示:

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\020-输入子系统实验”路径下

/*
 * @Author:topeet
 * @Description:使用输入子系统设计按键驱动
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
//添加输入子系统的头文件
#include <linux/input.h>

static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0);
struct device_node *test_device_node;
struct property *test_node_property;
//定义一个输入设备test_dev
struct input_dev *test_dev; //定义一个输入设备test_dev
int irq;
int gpio_nu;
/**
 * @description:超时处理函数 
 * @param {*}
 * @return {*}
 */
static void timer_function(unsigned long data)
{
    int value;
    value = !gpio_get_value(gpio_nu);
    input_report_key(test_dev, KEY_VOLUMEUP, value); //上报 按键按下 的事件
    input_sync(test_dev);
}

//中断处理函数
irqreturn_t test_key(int irq, void *args)
{
    printk("test_key\n");
    test_timer.expires = jiffies + msecs_to_jiffies(20);
    //定时器注册到内核里面
    add_timer(&test_timer);
    return IRQ_RETVAL(IRQ_HANDLED);
}
/**
 * @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,
 * @param inode : 文件索引
 * @param file  : 文件
 * @return 成功返回 0           
*/
int led_probe(struct platform_device *pdev)
{
    int ret = 0;
    printk("led_probe\n");
    //of_find_node_by_path函数通过路径查找节点,/test是设备树下的节点路径
    test_device_node = of_find_node_by_path("/test");
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error \n");
        return -1;
    }
    //of_get_named_gpio函数获取 GPIO 编号
    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
    if (gpio_nu < 0)
    {
        printk("of_get_namd_gpio is error \n");
        return -1;
    }
    // 设置GPIO为输入模式
    gpio_direction_input(gpio_nu);
    irq = gpio_to_irq(gpio_nu);
    // 获取中断号
    // irq = irq_of_parse_and_map(test_device_node, 0);
    printk("irq is %d \n", irq);
    /*申请中断,irq:中断号名字  
     test_key:中断处理函数
     IRQF_TRIGGER_RISING:中断标志,意为上升沿触发
     "test_key":中断的名字
     */
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "test_key", NULL);
    if (ret < 0)
    {
        printk("request_irq is error \n");
        return -1;
    }
    //申请一个 input_dev输入设备
    test_dev = input_allocate_device();
    // 设置 input_dev 名字
    test_dev->name = "test_key";
    // 设置事件和事件值
    // 设置产生按键事件
    set_bit(EV_KEY, test_dev->evbit);
    //设置产生哪些按键值,表示这个设备要支持KEY_VOLUMEUP
    set_bit(KEY_VOLUMEUP, test_dev->keybit);
    //向 Linux内核注册 input_dev
    ret = input_register_device(test_dev);
    if (ret < 0)
    {
        printk("input_register_device is error \n");
        goto error_input_register;
    }
    return 0;
error_input_register:
    input_unregister_device(test_dev);
    return -1;
}

int led_remove(struct platform_device *pdev)
{
    printk("led_remove\n");
    return 0;
}
const struct platform_device_id led_idtable = {
    .name = "keys",
};
const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},
    {},
};
struct platform_driver led_driver = {
    //3. 在led_driver结构体中完成了led_probe和led_remove
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "led_test",
        .of_match_table = of_match_table_test},
    //4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配
    .id_table = &led_idtable};

static int led_driver_init(void)
{
    // 1.我们看驱动文件要从init函数开始看
    int ret = 0;
    //2. 在init函数里面注册了platform_driver
    ret = platform_driver_register(&led_driver);
    if (ret < 0)
    {
        printk("platform_driver_register error \n");
    }
    printk("platform_driver_register ok \n");

    return 0;
}

static void led_driver_exit(void)
{
    del_timer(&test_timer);
    free_irq(irq, NULL);
    input_unregister_device(test_dev);
    platform_driver_unregister(&led_driver);
    printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_LICENSE("GPL");

63.2.2 应用测试程序

编写应用测试程序apptest.c,如下所示,程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\020-输入子系统实验”路径下。

/*
 * @Author: topeet
 * @Description:应用测试程序
 */
#include <stdio.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[])
{
    int fd;
    //定义输入事件结构体
    struct input_event test_event;
    //打开设备节点
    fd = open("/dev/input/event1", O_RDWR);
    if (fd < 0)
    {
        //打开设备节点失败
        perror("open error \n");
        return fd;
    }
    while (1)

    {
        // 读取输入事件
        read(fd, &test_event, sizeof(test_event));
        // 如果输入事件类型为按键事件,则打印事件类型事件码和值
        if (test_event.type == EV_KEY)
        {
            printf("type is %#x \n", test_event.type);
            printf("code is %#x \n", test_event.code);
            printf("value is %#x \n", test_event.value);
        }
    }
    close(fd);
    return 0;
}

63.3运行测试

在运行测试之前。首先要修改设备树文件/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dtsi,修改成如下图所示:

 然后重新编译源码,烧写编译好的镜像之后再进行以下测试。

我们将刚刚编写的驱动代码编译为驱动模块,编译完如下图所示:

开发板启动后,我们输入命令ls /dev/input可以看到现在有的输入设备,如下图所示: 

我们输入命令cat /proc/bus/input/devices可以查看与event对应的相关设备信息,如下图所示: 

我们进入共享目录并且加载驱动模块,如下图所示:

 

我们输入命令cat /proc/bus/input/devices可以查看下我们系统的输入设备有没有增多,如下所示,加载驱动后,输入设备增加了input3  

从上图可知,输入设备的节点是event1,我们输入命令ls /dev/input查看下节点是否增多,如下图所示:

我们输入命令hexdump /dev/input/event1,然后再按开发板上的音量+按键,如下图所示: 

 

从上图可知TYPE的类型为0001,即EV_KEY;code为0073,即KEY_VOLUMEUP;value为0001,代表被按下了,0000代表被弹起了。

我们也可以通过应用程序app.c来读取上报的数据,拷贝第63.2.2章编写的apptest.c到Ubuntu的/home/topeet/imx8mm/20目录下,将节点改为event1,然后编译app.c,如下图所示:

 

运行编译好的app,然后按开发板上面的音量+按键,如下图所示: 

  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值