Linux学习第32天:Linux INPUT 子系统实验(一):接纳

2023-11-05 20:36

本文主要是介绍Linux学习第32天:Linux INPUT 子系统实验(一):接纳,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长 


           题目中用了“接纳”俩字。其实学习就是一个接纳的过程。接纳新的知识,从而转化为自己知识宝库的一部分。那今天学习的input子系统和今天的主题接纳有什么关系呢?接纳就是一个从输入转为为内在并输出应用的过程。

        在日常的工作学习中,学会接纳,接纳同事同学朋友老师,更要学会接纳自己,接纳这个社会的不完美。    

        本笔记主要学习Linux input子系统实验,主要内容包括input子系统相关基础知识、原理图分析、驱动程序开发及测试、Linux自带按键驱动程序的使用。其中Linux自带按键驱动程序的使用将在下一节中进行。

一、input子系统

        Linux内核专门做了一个input子系统框架来处理输入事件。输入设备本质上是字符设备,只是在此基础上套上了input框架,用户只需上报输入事件,input核心层负责处理这些事件。

1、input子系统简介

        input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点。

        input子系统框架如下图:

        所有的输入设备以文件的形式供用户空间应用程序使用。

        编写驱动程序时只需要关注中间的驱动层、核心层和时间层。

        驱动层:输入设备具体的驱动程序。

        核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。

        事件层:主要和用户空间进行交互。

2、input驱动编写流程

        input核心层会向Linux内核注册一个字符设备,input.c就是input输入子系统的核心层。

err = class_register(&input_class);

        注册一个 input 类,这样系统启动以后就会在/sys/class 目录下有一个 input 子目录。

err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");

        注册一个字符设备,主设备号为 INPUT_MAJOR,INPUT_MAJOR定义如下:

#define INPUT_MAJOR 13

        input子系统所有设备的主设备号都是13,在使用input处理输入设备的时候就不需要去注册字符设备了。只需向系统注册一个input_device即可。

1)、注册input_dev

        在使用input子系统时,只需要注册一个input设备即可。input_dev结构体标识input设备。

unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
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)]; /*开关状态的位图 */
......

        evbit、 keybit、 relbit 等等都是存放不同事件对应的值。

        使用input_allocate_device 函数来申请一个 input_dev。

struct input_dev *input_allocate_device(void)

        使用 input_free_device 函数来释放掉前面申请到的input_dev:

void input_free_device(struct input_dev *dev)

        初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)事件值(keybit)这两种

        input_dev初始化完成以后,需要使用input_register_device函数向Linux内核注册input_dev。

int input_register_device(struct input_dev *dev)

        同样,使用input_unregister_device向Linux内核注销input_dev.

void input_unregister_device(struct input_dev *dev)

        终上所述,注册过程如下:

①、使用 input_allocate_device 函数申请一个 input_dev。
②、初始化 input_dev 的事件类型以及事件值。
③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。
④、卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,
然后使用 input_free_device 函数释放掉前面申请的 input_dev。
 

struct input_dev *inputdev; /* input 结构体变量 */

        定义一个 input_dev 结构体指针变量。

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->evbit); /* 重复事件 */
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 }

        驱动入口函数,在此函数中完成 input_dev 的申请、设置、注册等工作。第 7行调用 input_allocate_device 函数申请一个 input_dev。第 10~23 行都是设置 input 设备事件和按键值,这里用了三种方法来设置事件和按键值。第 27 行调用 input_register_device 函数向 Linux内核注册 inputdev。

32 /* 驱动出口函数 */
33 static void __exit xxx_exit(void)
34 {
35 input_unregister_device(inputdev); /* 注销 input_dev */
36 input_free_device(inputdev); /* 删除 input_dev */
37 }

        驱动出口函数,第 35 行调用 input_unregister_device 函数注销前面注册的input_dev,第 36 行调用 input_free_device 函数删除前面申请的 input_dev。

2)、上报输入事件

        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 函数。

        上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束,
input_sync 函数本质是上报一个同步事件

2 void timer_function(unsigned long arg)
3 {
4 unsigned char value;
5 6
value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
7 if(value == 0){ /* 按下按键 */
8 /* 上报按键值 */
9 input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1, 按下 */
10 input_sync(inputdev); /* 同步事件 */
11 } else { /* 按键松开 */
12 input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0, 松开 */
13 input_sync(inputdev); /* 同步事件 */
14 }
15 }

3、input_event结构体

        Linux 内核使用 input_event 这个结构体来表示所有的输入事件。

24 struct input_event {
25 struct timeval time;
26 __u16 type;
27 __u16 code;
28 __s32 value;
29 };

        time:时间,也就是此事件发生的时间,为 timeval 结构体类型。

        type: 事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。

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

二、硬件原理图

三、试验程序编写

1、修改设备树文件

1)、添加pinctrl节点

1 pinctrl_key: keygrp {
2 fsl,pins = <
3 MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
4 >;
5 };

2)、添加KEY设备节点

1 key {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-key";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_key>;
7 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
8 status = "okay";
9 };

3)、检查 PIN 为 UART1_CTS_B 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO1_IO18这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。

2、按键input驱动程序编写

struct input_dev *inputdev; /* input 结构体 */

        在设备结构体中定义一个 input_dev 指针变量。

93 if(value == 0){ /* 按下按键 */
94 /* 上报按键值 */
95 //input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
96 input_report_key(dev->inputdev, keydesc->value, 1);/*1, 按下*/
97 input_sync(dev->inputdev);
98 } else { /* 按键松开 */
99 //input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
100 input_report_key(dev->inputdev, keydesc->value, 0);
101 input_sync(dev->inputdev);
102 }

        在按键消抖定时器处理函数中上报输入事件,也就是使用 input_report_key函数上报按键事件以及按键值,最后使用 input_sync 函数上报一个同步事件!

155 /* 申请 input_dev */
156 keyinputdev.inputdev = input_allocate_device();
157 keyinputdev.inputdev->name = KEYINPUT_NAME;
158 #if 0
159 /* 初始化 input_dev,设置产生哪些事件 */
160 __set_bit(EV_KEY, keyinputdev.inputdev->evbit); /*按键事件 */
161 __set_bit(EV_REP, keyinputdev.inputdev->evbit); /* 重复事件 */
162
163 /* 初始化 input_dev,设置产生哪些按键 */
164 __set_bit(KEY_0, keyinputdev.inputdev->keybit);
165 #endif
166
167 #if 0
168 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
169 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
BIT_MASK(KEY_0);
170 #endif
171

        使用 input_allocate_device 函数申请 input_dev,然后设置相应的事件以及事件码(也就是 KEY 模拟成那个按键,这里我们设置为 KEY_0)。最后使用 input_register_device函数向 Linux 内核注册 input_dev。

210 /* 释放 input_dev */
211 input_unregister_device(keyinputdev.inputdev);
212 input_free_device(keyinputdev.inputdev);

        当注销 input 设备驱动的时候使用 input_unregister_device 函数注销掉前面注册的 input_dev,最后使用 input_free_device 函数释放掉前面申请的 input_dev。

3、编写测试APP

static struct input_event inputevent;

        定义了一个 inputevent 变量,此变量为 input_event 结构体类型。

err = read(fd, &inputevent, sizeof(inputevent));

        当我们向 Linux 内核成功注册 input_dev 设备以后,会在/dev/input 目录下生成一个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX 就是对应的 input 设备文件。我们读取这个文件就可以获取到输入事件信息,比如按键值什么的。使用read函数读取输入设备文件,也就是/dev/input/eventX,读取到的数据按照 input_event 结构体组织起来。获取到输入事件以后(input_event 结构体类型)使用 switch case 语句来判断事件类型,本章实验我们设置的事件类型
为 EV_KEY,因此只需要处理 EV_KEY 事件即可。比如获取按键编号(KEY_0 的编号为 11)、
获取按键状态,按下还是松开的?



四、运行测试

1、编译驱动程序和测试APP

obj-m := keyinput.o

make -j32
编译成功以后就会生成一个名为“keyinput.ko”的驱动模块文件。
arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp
编译成功以后就会生成 keyinputApp 这个应用程序。
 

2、运行测试

        编译出来 keyinput.ko 和 keyinputApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15
目录中,重启开发板,进入到目录 lib/modules/4.1.15 中。

输入如下命令加载 keyinput.ko 这个驱动模块。

depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //加载驱动模块

        当驱动模块加载成功以后再来看一下/dev/input 目录:

        多了一个 event1 文件,因此/dev/input/event1 就是我们注册的驱动所对应的设备文件。

        输入如下命令:

./keyinputApp /dev/input/event1

        然后按下开发板上的 KEY 按键,结果如图:

        当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在 Linux 内核中 KEY_0 为 11。

五、总结:

        本笔记主要学习Linux input子系统实验,主要内容包括input子系统相关基础知识、原理图分析、驱动程序开发及测试、Linux自带按键驱动程序的使用。


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

这篇关于Linux学习第32天:Linux INPUT 子系统实验(一):接纳的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/352333

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss