【北京迅为】《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 *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!value);
}
可以看出,input_report_key 函数的本质就是 input_event 函数,当然使用哪个函数都没有问题,不同的设备使用对应的函数更加合适一点。
同样的还有一些其他事件对应的上报函数:
void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int 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,然后按开发板上面的音量+按键,如下图所示:
相关文章:

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十三章 输入子系统实验
i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…...
[补题记录]Leetcode 15. 三数之和
传送门:三数之和 思路 为了去重,需要先排序。 排序之后,显然每一个 n u m s [ i ] nums[i] nums[i] 就可以作为三数之中的第一个数。 因此,对于每一个 i i i,第二、三个数只能在 [ i 1 , n ] [i 1, n] [i1,n]…...
什么是sql注入攻击,如何预防介绍一下mysql中的常见数据类型
什么是sql注入攻击,如何预防 sql注入攻击指的是应用程序对用户输入数据的合法性没有判断或者过滤不严格,在sql语句中插入任意的恶意语句进行非法操作。 预防方式1:使用预编译语句比如PrepareStatement,用户输入的所有数据都以参数…...

史上最全的Seata教学并且连接springcloudAlibaba进行使用
来都来了点个赞收藏一下在走呗~~🌹🌹玫瑰 一、Seata是什么 Seata(Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架)是一种分布式事务解决方案,旨在解决分布式系统中的事务…...

InternLM Git 基础知识
提交一份自我介绍。 创建并提交一个项目。...

【Unity模型】古代亚洲建筑
在Unity Asset Store上,一款名为"Ancient Asian Buildings Pack"(古代亚洲建筑包)的3D模型资源包,为广大开发者和设计师提供了一个将古代亚洲建筑风格融入Unity项目的机会。本文将详细介绍这款资源包的特点、使用方式以…...

木马后门实验
实验拓扑 实验步骤 防火墙 配置防火墙—充当边界NAT路由器 边界防火墙实现内部 DHCP 分配和边界NAT需求,其配置如下 登录网页 编辑接口 配置e0/0 配置e0/1 编辑策略 测试:内部主机能获得IP,且能与外部kali通信 kali 接下来开启 kali 虚…...

【React】useState:状态更新规则详解
文章目录 一、基本用法二、直接修改状态 vs 使用 setState 更新状态三、对象状态的更新四、深层次对象的更新五、函数式更新六、优化性能的建议 在 React 中,useState 是一个非常重要的 Hook,用于在函数组件中添加状态管理功能。正确理解和使用 useState…...
C#中的异步编程:Task、Await 和 Async
public async void DoSth() {await Task.Run(() > {//...DoSth...}); } ①函数的返回类型前加上: async ②函数内加上: await Task.Run(() > { }); ③在上面{ ... } 内添加要处理的程序代码, 这样运行到 DoSth() 函数就…...

SSRF-labs-master靶场
目录 file_get_content.php sql_connect.php download.php dns-spoofing.php dns_rebinding.php 访问链接 http://127.0.0.1/SSRF/# file_get_content.php 在编程语言中,有一些函数可以获取本地保存文件的内容。这些功能可能能够从远程URL以及本地文件 如果没…...

HBuilder X中配置vue-cli项目和UI库
目录 一.前端项目结构 二.在HBuilder X中搭建vue-cli项目 1. 安装node.js前端环境 2. HBuilder X创建一个vue-cli项目 3. vue-cli项目结构 4. 如何运行前端项目 5. 创建组件 6. 组件路由(页面跳转) 6.1 创建router目录 6.2 使用路由 6.3 在main.js中配置路由 6.4 路…...

如何用PostMan按照规律进行循环访问接口
①设置动态变量 步骤一: 设置环境变量 1. 创建环境变量集合 在 Postman 左上角选择 "环境",然后点击 "添加" 来创建一个新的环境变量集合。给它起一个名称,比如 "uploadDemo". 2. 添加初始变量 在新创建的环境变量集…...

稳态准直太阳光模拟器仪器光伏电池组件IV测试
太阳能模拟器电池IV测试仪、单体测试仪,配备匹配标准的AAA Class稳态太阳能模拟器及相关测试附件,可对太阳能电池片的IV性能进行测量、分级分选等; 介绍 AAA class太阳光模拟器整合完整的IV测量系统,针对各种太阳能电池的性能&a…...
vue3 reactive原理(二)-代理Set和Map及ref原理
Set和Map类型的数据也属于异质对象,它们有特定的属性和方法用来操作自身。因此创建代理时,针对特殊的方法需要特殊的对待。 Vue 的ref 是基于reactive函数实现的,它在其基础上,增加了基本类型的响应性、解决reactive在解构时丢失…...

Python自然语言处理库之NLTK与spaCy使用详解
概要 自然语言处理(NLP)是人工智能和数据科学领域的重要分支,致力于让计算机理解、解释和生成人类语言。在Python中,NLTK(Natural Language Toolkit)和spaCy是两个广泛使用的NLP库。本文将详细介绍NLTK和spaCy的特点、功能及其使用方法,并通过具体示例展示如何使用这两…...

Hive-内部表和外部表
区别 内部表实例 准备数据 查看数据 删除数据 外部表实例 准备数据 查看数据 删除数据 区别 内部表:管理元数据(记录数据的文件和目录的信息)和数据。当删除内部表时,会删除数据和表的元数据,所以当多个表关…...
Java并发编程(三)
Java并发编程 1、什么是 Executors 框架 Executors框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。 无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以…...

Flink Doirs Connector 常见问题:Doris目前不支持流读
常见问题 Doris Source 在数据读取完成后,流为什么就结束了? 目前 Doris Source 是有界流,不支持 CDC 方式读取。 问题:对于 Flink Doris DataStream,Flink 想要在 流式读取 Doirs / 实时读 Doris,目前读…...

期末复习资料——计算机系统基础
第一章 1、下列关于机器字长、指令字长和存储字长的说法中,正确的时_②、③_ ①三者在数值上总是相等的。②三者在数值上可能不相等。③存储字长是存放在一个存储单元中的二进制代码位数。④数据字长就是MDR的位数。 机器字长、指令字长和存储字长,三…...
一天搞定Recat(5)——ReactRouter(上)【已完结】
Hello!大家好,今天带来的是React前端JS库的学习,课程来自黑马的往期课程,具体连接地址我也没有找到,大家可以广搜巡查一下,但是总体来说,这套课程教学质量非常高,每个知识点都有一个…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...