LV15-03-输入类设备-01-基础知识
本文主要是输入类设备控制——基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.6的64位版本(一开始使用的是16.04版本,后来进行了升级) | |
VMware® Workstation 17 Pro | 17.0.0 build-20800274 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) | |
linux内核 | linux-4.15(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
https://elixir.bootlin.com/linux/latest/source | 在线阅读linux kernel源码 | |
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) |
一、输入类设备编程简介
1. 什么是输入设备?
先来了解什么是输入设备(也称为 input 设备),常见的输入设备有鼠标、键盘、触摸屏、 遥控器、电脑画图板等,用户通过输入设备与系统进行交互。
2. input 子系统
由上面的介绍可知,输入设备种类非常多,每种设备上报的数据类型又不一样,那么 Linux 系统如何管理呢? Linux 系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就是 input 子系统。驱动开发人员基于 input 子系统开发输入设备的驱动程序。
input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。基于 input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件), 设备节点名称通常为 eventX(X 表示一个数字编号 0、 1、 2、 3 等),例如 /dev/input/event0、 /dev/input/event1、/dev/input/event2 等, 通过读取这些设备节点可以获取输入设备上报的数据。
3. 读取数据的流程
如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:
(1)应用程序打开/dev/input/event0 设备文件;
(2)应用程序发起读操作(例如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
(3)当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
(4)应用程序对读取到的数据进行解析。
当无数据可读时,程序会进入休眠状态(也就是阻塞),例如应用程序读触摸屏数据, 如果当前并没有去触碰触摸屏, 自然是无数据可读; 当我们用手指触摸触摸屏或者在屏上滑动时,此时就会产生触摸数据、应用程序就有数据可读了,应用程序会被唤醒,成功读取到数据。那么对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态(阻塞式 I/O 方式下), 当有数据可读时才会被唤醒。
4. 应用程序如何解析数据
先我们要知道,应用程序打开输入设备对应的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢? 其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据, 该结构体定义在<linux/input.h>头文件中,它的定义如下:
1 | struct input_event { |
结构体中的 time 成员变量是一个 struct timeval 类型的变量, 内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序。时间参数通常不是那么重要,而其它3 个成员变量 type、 code、 value 更为重要。
4.1 type
type 用于描述发生了哪一种类型的事件(对事件的分类) , Linux 系统所支持的输入事件类型如下所示:
1 | /* |
以上这些宏定义也是在<linux/input.h>头文件中,所以在应用程序中需要包含该头文件; 一种输入设备 通常可以产生多种不同类型的事件,例如点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键 类事件,移动鼠标时则会上报相对位移类事件。
在type成员中,我们看到有一个同步事件类型 EV_SYN, 同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 操作只能读取一个 struct input_event 类型数据,例如对于触摸屏来说,一个触摸点的信息包含了 X 坐标、 Y 坐标以及其它信息, 对于这样情况,应用程序需要执行多次 read 操作才能把一个触摸点的信息全部读取出来, 这样才能得到触摸点的完整信息。
那么应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的, 内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、 可以进行同步了。同步类事件中也包含了多种不同的事件,如下所示:
1 | /* |
所以的输入设备都需要上报同步事件, 上报的同步事件通常是 SYN_REPORT, 而 value 值通常为 0。
4.2 code
code 表示该类事件中的哪一个具体事件, 以上列举的每一种事件类型中都包含了一系列具体事件, 如一个键盘上通常有很多按键, 例如字母 A、B、 C、 D 或者数字 1、 2、 3、 4 等, 而 code变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件。
- 按键类事件:
1 |
|
- 相对位移事件:
1 |
- 绝对位移事件
触摸屏设备是一种绝对位移设备,它能够产生绝对位移事件; 例如对于触摸屏来说,一个触摸点所包含的信息可能有多种,例如触摸点的 X 轴坐标、 Y 轴坐标、 Z 轴坐标、按压力大小以及接触面积等, 所以 code变量告知应用程序当前上报的是触摸点的哪一种信息(X 坐标还是 Y 坐标、亦或者其它)。绝对位移事件如下:
1 |
|
除了以上列举出来的之外,还有很多,我们可以自己浏览<linux/input.h>头文件(这些宏其实是定义在input-event-codes.h 头文件中,该头文件被< linux/input.h >所包含了) , 关于这些具体的事件,后面遇到了再学习。
4.3 value
内核每次上报事件都会向应用层发送一个数据 value, 对 value 值的解释随着 code 的变化而变化。对于按键事件(type=1) 来说, 如果 code=2(键盘上的数字键 1,也就是 KEY_1), 那么如果 value 等于 1,则表示 KEY_1 键按下; value 等于 0 表示 KEY_1 键松开,如果 value 等于 2,则表示 KEY_1 键长按。再比如, 在绝对位移事件中(type=3),如果 code=0 (触摸点 X 坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值; 同理, 如果 code=1(触摸点 Y 坐标 ABS_Y),此时value 值便等于触摸点的 Y 轴坐标值; 所以对 value 值的解释需要根据不同的 code 值而定。
二、如何获取输入设备输入信息?
接下来通过一个简单的例子来看看怎么获取输入设备的输入信息。
1. 读取 struct input_event 数据
1 | /** ===================================================== |
执行程序时需要传入参数,这个参数就是对应的输入设备的设备节点(设备文件),程序中会对传参进行校验。程序中首先调用 open()函数打开设备文件,之后在 while 循环中调用 read()函数读取文件,将读取到的数据存放在 struct input_event 结构体对象中,之后将结构体对象中的各个成员变量打印出来。
程序中使用了阻塞式 I/O 方式读取设备文件,所以当无数据可读时 read 调用会被阻塞,直到有数据可读时才会被唤醒!
2. 如何确认设备信息?
ALPHA 开发板上有一个用户按键 KEY0, 它就是一个典型的输入设备, 如下图所示:
该按键是提供给用户使用的一个 GPIO 按键, 在出厂系统中,该按键驱动基于 input 子系统而实现, 所以在/dev/input 目录下存在 KEY0 的设备节点, 具体是哪个设备节点, 可以通过查看/proc/bus/input/devices 文件得知,查看该文件可以获取到系统中注册的所有输入设备相关的信息,如下所示 :
这里其实还有一种办法可以用来确认(这里使用的是出厂系统),我们的输入设备,其对应的设备文件在/dev/input/目录下 :
如何确定到底是哪个设备文件,可以通过对设备文件进行读取来判断,例如使用 od 命令:
1 | sudo od -x /dev/input/eventX # eventX表示上面的event多少 |
Tips:需要添加 sudo,在 Ubuntu 系统下,普通用户是无法对设备文件进行读取或写入操作。 要是直接在开发板中的话,看自己是什么用户权限了。
我们可以一个一个试,当执行完命令后按下按键,哪一个读取到了数据,就说明这个就是按键对应的输入设备文件。
比如这里是event2,按下按键的时候就会出现对应的打印信息。
3. 开发板验证
我们在串口终端执行以下命令:
1 | ./app_demo /dev/input/event2 |
程序运行后,执行按下 KEY0、松开 KEY0 等操作,终端将会打印出相应的信息,如上图所示。
第一行中 type 等于 1,表示上报的是按键事件 EV_KEY, code=114, 打开 input-event-codes.h 头文件进行查找,可以发现 cpde=114 对应的是键盘上的 KEY_VOLUMEDOWN 按键,这个是 ALPHA 开发板出厂系统已经配置好的。 而 value=1 表示按键按下,所以整个第一行的意思就是按键 KEY_VOLUMEDOWN被按下。
第二行, 表示上报了 EV_SYN 同步类事件(type=0)中的 SYN_REPORT 事件(code=0), 表示本轮数据已经完整、报告同步。
第三行, type 等于 1,表示按键类事件, code 等于 114、value 等于 0,所以表示按键 KEY_VOLUMEDOWN被松开。
第四行,又上报了同步事件。
所以整个上面 4 行的打印信息就是开发板上的 KEY0 按键被按下以及松开这个过程, 内核所上报的事件以及发送给应用层的数据 value。 我们试试长按按键 KEY0, 按住不放, 如下所示:
可以看到上报按键事件时,对应的 value 等于 2,表示长按状态。