【北京迅为】《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_VOLUMEUPset_bit(KEY_VOLUMEUP, test_dev->keybit);//向 Linux内核注册 input_devret = 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_driverret = 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,然后按开发板上面的音量+按键,如下图所示: 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/386093.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IP 泄露: 原因与避免方法

始终关注您的IP信息&#xff01; 您的IP地址不仅显示您的位置&#xff0c;它包含几乎所有的互联网活动信息&#xff01; 如果出现IP泄漏&#xff0c;几乎所有的信息都会被捕获甚至非法利用&#xff01; 那么&#xff0c;网站究竟如何追踪您的IP地址&#xff1f;您又如何有效…

Catalyst优化器:让你的Spark SQL查询提速10倍

目录 1 逻辑优化阶段 2.1 逻辑计划解析 2.2 逻辑计划优化 2.2.1 Catalys的优化过程 2.2.2 Cache Manager优化 2 物理优化阶段 2.1 优化 Spark Plan 2.1.1 Catalyst 的 Join 策略 2.1.2 如何决定选择哪一种 Join 策略 2.2 Physical Plan 2.2.1 EnsureRequirements 规则 3 相关文…

【Unity2D 2022:Data】读取csv格式文件的数据

一、创建csv文件 1. 打开Excel&#xff0c;创建xlsx格式文件 2. 编辑卡牌数据&#xff1a;这里共写了两类卡牌&#xff0c;第一类是灵物卡&#xff0c;具有编号、卡名、生命、攻击四个属性&#xff1b;第二类是法术卡&#xff0c;具有编号、卡名、效果三个属性。每类卡的第一…

qt 如何制作动态库插件

首先 首先第一点要确定我们的接口是固定的&#xff0c;也就是要确定 #ifndef RTSPPLUGIN_H #define RTSPPLUGIN_H #include "rtspplugin_global.h" typedef void (*func_callback)(uint8_t* data,int len,uint32_t ssrc,uint32_t ts,const char* ipfrom,uint16_t f…

【Maven学习】-3.进阶

文章目录 3. 进阶3.1 maven依赖传递特性 3.2 依赖冲突3.2.1 自动选择原则3.2.2 手动排除 3.3 聚合工程3.3.1 继承介绍继承作用继承语法父工程依赖统一管理-dependencyManagement 3.3.2 工程聚合关系简介聚合作用聚合作用 3.4 私服3.4.1 简介3.4.2 Nexus下载安装Nexus3Nexus2 3.…

带你学会Git必会操作

文章目录 带你学会Git必会操作1Git的安装2.Git基本操作2.1本地仓库的创建2.2配置本地仓库 3.认识一些Git的基本概念3.1操作流程&#xff1a; 4.一些使用场景4.1添加文件场景一4.2查看git文件4.3修改文件4.4Git版本回退4.5git撤销修改 5.分支管理5.1查看分支5.2创建本地分支5.3切…

IOS-05 Swift循环控制语句

在 Swift 编程语言中&#xff0c;控制语句用于决定程序的执行流程&#xff0c;使我们能够根据不同的条件和情况来控制代码的执行顺序。下面我们将详细介绍几种常见的控制语句 一、for 循环 let names ["zhangsan","lisi"] for name in names{print(name…

set,map(java)

前言&#xff1a;要了解set和map&#xff0c;首先需要对搜索树和哈希有一定的了解&#xff0c;才能进一步深入的了解set和map。 1.搜索树 &#xff08;1&#xff09;性质&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有节点值都小于根节点的值。 若它的右子树不…

TypeScript学习篇-类型介绍使用、ts相关面试题

文章目录 基础知识基础类型: number, string, boolean, object, array, undefined, void(代表该函数没有返回值)unknownenum(枚举): 定义一个可枚举的对象联合类型: | (联合类型一次只能一种类型&#xff1b;而交叉类型每次都是多个类型的合并类型。)交叉类型: & (联合类型…

按图搜索新体验:阿里巴巴拍立淘API返回值详解

阿里巴巴拍立淘API是一项基于图片搜索的商品搜索服务&#xff0c;它允许用户通过上传商品图片&#xff0c;系统自动识别图片中的商品信息&#xff0c;并返回与之相关的搜索结果。以下是对阿里巴巴拍立淘API返回值的详细解析&#xff1a; 一、主要返回值内容 商品信息 商品列表…

【算法/学习】前缀和差分

前缀和&&差分目录 1. 前缀和的概念及作用 &#x1f308;概念 &#x1f308;用途 &#x1f319;一维前缀和 &#x1f319;二维前缀和 2. 差分的概念及用途 &#x1f308;概念&#xff1a; &#x1f308;用途 &#x1f319;一维差分 &#x1f319;二维差分 1. …

Linux系统编程——线程池

目录 一&#xff0c;池化技术 二&#xff0c;线程池概念 三&#xff0c;线程池实现 3.1 线程封装 3.2 预备头文件实现 3.3 线程池类的简单实现 3.4 主函数实现 3.5 效果展示 一&#xff0c;池化技术 池化技术是计算机编程领域非常常用的一种技术&#xff0c;该技术可以…

【前端/js】使用js读取本地文件(xml、二进制)内容

目录 说在前面FileReaderDOMParser文本文件二进制文件 说在前面 浏览器版本&#xff1a;Microsoft Edge 126.0.2 (正式版本) (64 位) FileReader MDNFileReader 接口允许 Web 应用程序异步读取存储在用户计算机上的文件&#xff08;或原始数据缓冲区&#xff09;的内容&#x…

CL4056D 1A锂离子电池线性充电器芯片IC

一般描述 CL4056D是一款ESOP8封装的独立线性锂离子电池充电器。由于外部元件较少&#xff0c;因此CL4056D非常适合用于各种便携式应用。充电电流可以通过外部电阻器进行编程。在待机模式下&#xff0c;供电电流将降低到约35uA。当输入电压断开时&#xff0c;CL4056 D将进…

UWA Gears正式上线,助力移动平台性能优化

亲爱的开发者朋友们&#xff0c; 我们非常激动地向大家宣布&#xff0c;UWA最新的无SDK性能分析工具 - UWA Gears&#xff0c;现已正式发布&#xff01;无论您使用的是哪种开发引擎&#xff0c;这款工具都能轻松应对&#xff0c;为您的项目保驾护航。更令人心动的是&#xff0c…

Lua编程

文章目录 概述lua数据类型元表注意 闭包表现 实现 lua/c 接口编程skynet中调用层次虚拟栈C闭包注册表userdatalightuserdata 小结 概述 这次是skynet&#xff0c;需要一些lua/c相关的。写一篇博客&#xff0c;记录下。希望有所收获。 lua数据类型 boolean , number , string…

在react中如何计算本地存储体积

1.定义useLocalStorageSize钩子函数 // 计算localStorage大小 function useLocalStorageSize() {const [size, setSize] useState(0);useEffect(() > {const calculateSize () > {let totalSize 0;for (let key in localStorage) {//过滤掉继承自原型链的属性if (loc…

Redis是多线程还是单线程?

文章目录 1、用户态和内核态2、阻塞IO3、非阻塞IO4、IO多路复用4.1 select4.2 poll4.3 epoll4.4 epoll中的ET和LT4.5 epoll的服务端流程 5、信号驱动6、异步IO7、对比8、Redis是单线程的吗&#xff1f;9、单线程多线程网络模型变更 1、用户态和内核态 1、ubuntu和Centos 都是Li…

基于PaddleClas的人物年龄分类项目

目录 一、任务概述 二、算法研发 2.1 下载数据集 2.2 数据集预处理 2.3 安装PaddleClas套件 2.4 算法训练 2.5 静态图导出 2.6 静态图推理 三、小结 一、任务概述 最近遇到个需求&#xff0c;需要将图像中的人物区分为成人和小孩&#xff0c;这是一个典型的二分类问题…

Python | Leetcode Python题解之第283题移动零

题目&#xff1a; 题解&#xff1a; class Solution:def moveZeroes(self, nums: List[int]) -> None:n len(nums)left right 0while right < n:if nums[right] ! 0:nums[left], nums[right] nums[right], nums[left]left 1right 1