正点原子学习笔记!!!仅学习使用,请支持正版!同样转载请标明出处。
正点原子论坛 : www.openedv.com
input子系统
重点是外设的程序对应struct input_event 结构体的事件及其上报的流程的信息处理:
无论外设如何变化,归根结底都是对struct input_event 结构体进行解析与程序处理!!!
而具体外设的程序应用重点均在与对应结构体中事件及其上报的流程的信息处理
先一定要搞懂第一个实例代码:
它打印出struct input_event代表的事件类型、具体事件编码、事件值,切入input子系统;
不管是键盘也好、或者是鼠标、触摸屏,都可以像上面那样将输入设备的数据直接打印出来!
后面搞懂三个实操代码,再看总结;只能说NB!!!!!
实例代码:获取结构体(按键)信息打印、长短按键、最大点数测试*、单/多点触摸!!!
解析按键设备上报的数据
应用程序打开输入设备对应的设备文件,每一次读向其发起读操作read 获取的都是一个 struct input_event 结构体类型数据,该结构体定义在<linux/input.h>头文件中:
struct input_event 结构体
/*示例代码 17.1.1 struct input_event 结构体*/
struct input_event
{
struct timeval time;
__u16 type; //事件的分类
__u16 code; //分类的事件中的具体事件
__s32 value; //上报事件的信息:上报 KEY_A 事件时,value=1;
//如果是松开,则 value=0;如果是长按,则 value=2。
};
-
time 成员变量是一个 struct timeval 类型的变量;
内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序;
-
type:type 用于描述发生了哪一种类型的事件(对事件的分类)
/* * Event types ★★★★★★!!!! */ #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 #define EV_SND 0x12 #define EV_REP 0x14 #define EV_FF 0x15 #define EV_PWR 0x16 #define EV_FF_STATUS 0x17 #define EV_MAX 0x1f #define EV_CNT (EV_MAX+1)
-
code:code 表示该类事件中的哪一个具体事件
-
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 值而定!
struct timeval 结构体
- 该结构体包含了两个成员变量 tv_sec 和 tv_usec,分别用于表示秒和微秒。
/*示例代码 5.6.3 struct timeval 结构体*/
struct timeval
{
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 */
};
典例
/*读取 struct input_event 类型数据*/
include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h> //input 子系统
int main(int argc, char *argv[])
{
struct input_event in_ev = {0};
int fd = -1;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(-1);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(-1);
}
for ( ; ; ) {
/* 循环读取数据:自动读取最新的sizeof(struct input_event)大小的数据 */
if (sizeof(struct input_event) !=read(fd, &in_ev, sizeof(struct input_event)))
{//通过比较实际读取的字节数和 sizeof(struct input_event)的大小
//来检查是否成功读取了整个 struct input_event 结构体的数据
perror("read error");
exit(-1);
}
/*打印出事件类型、具体事件编码、事件值*/
printf("type:%d code:%d value:%d\n",in_ev.type, in_ev.code, in_ev.value);
}
}
KEY应用
数据同步
那么应用程序如何得知本轮已经读取到完整的数据了呢?
其实这就是通过同步事件来实现的,内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。
譬如对于触摸屏来说,一个触摸点的信息包含了 X 坐标、Y 坐标以及其它信息,
这样情况,应用程序要多次执行read才能把一个触摸点的信息全读取,这样才能得到触摸点的完整信息。
同步事件类型 EV_SYN
-
同步事件类型 EV_SYN,同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。
-
应用程序读取输入设备上报的数据时,一次 read 操作只读取一个 struct input_event
同步类事件中也包含了多种不同的事件,如下所示:
/*
* Synchronization events.
*/
#define SYN_REPORT 0
#define SYN_CONFIG 1
#define SYN_MT_REPORT 2
#define SYN_DROPPED 3
#define SYN_MAX 0xf
#define SYN_CNT (SYN_MAX+1)
- 所有输入设备都需要上报同步事件,上报同步事件常是 SYN_REPORT,而 value 值常为 0。
实际应用
-
获取按键状态,判断按键当前是按下、松开或长按状态
# 以字母 A 键为例 KEY_A //上报 KEY_A 事件 SYN_REPORT //同步 #如果是按下,则上报 KEY_A 事件时,value=1; #如果是松开,则 value=0;如果是长按,则 value=2。
在 for 循环中,调用 read()读取输入设备上报的数据,当按键按下或松开(以及长按)动作发生时,read()会读取到输入设备上报的数据;
首先判断此次上报的事件是否是按键类事件(EV_KEY),如果是按键类事件、接着根据 value 值来判断按键当前的状态是松开、按下还是长按。
#include <stdio.h> #include <stdlib.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[]) { /* struct input_event { struct timeval time; __u16 type; //事件的分类 __u16 code; //分类的事件中的具体事件 __s32 value; //上报事件的信息:上报 KEY_A 事件时,value=1; 如果是松开,则 value=0;如果是长按,value=2。 }; # 以字母 A 键为例 //本示例代码是EV_KEY KEY_A //上报 KEY_A 事件 SYN_REPORT //同步 #如果是按下,则上报 KEY_A 事件时,value=1; #如果是松开,则 value=0;如果是长按,则 value=2。 */ */ struct input_event in_ev = {0}; int fd = -1; int value = -1; /* 校验传参 */ if (2 != argc) { fprintf(stderr, "usage: %s <input-dev>\n", argv[0]); exit(-1); } /* 打开文件 */ if (0 > (fd = open(argv[1], O_RDONLY))) { perror("open error"); exit(-1); } for ( ; ; ) { /* 循环读取数据 */ if (sizeof(struct input_event) !=read(fd, &in_ev, sizeof(struct input_event))) { perror("read error"); exit(-1); } /* EV_KEY事件 */ if (EV_KEY == in_ev.type) { //按键事件 switch (in_ev.value) { case 0: printf("code<%d>: 松开\n", in_ev.code); break; case 1: printf("code<%d>: 按下\n", in_ev.code); break; case 2: printf("code<%d>: 长按\n", in_ev.code); break; } } } }
触摸屏数据解析
- 触摸屏设备是一个绝对位移设备,可以上报绝对位移事件,绝对位移事件如下:
- 触摸屏分为多点触摸设备和单点触摸设备。
- 触摸信息底层原理(ioctl)可跳过,主要了解实际应用程序实现原理,后面实际应用理解更好;
/*重点看有注释的*/
//单点触摸设备以 ABS_XXX 事件承载、上报触摸点的信息
#define ABS_X 0x00 //X 轴坐标
#define ABS_Y 0x01 //Y 轴坐标
#define ABS_Z 0x02 //Z 轴坐标
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ABS_THROTTLE 0x06
#define ABS_RUDDER 0x07
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
#define ABS_HAT1Y 0x13
#define ABS_HAT2X 0x14
#define ABS_HAT2Y 0x15
#define ABS_HAT3X 0x16
#define ABS_HAT3Y 0x17
#define ABS_PRESSURE 0x18 //按压力
#define ABS_DISTANCE 0x19
#define ABS_TILT_X 0x1a
#define ABS_TILT_Y 0x1b
#define ABS_TOOL_WIDTH 0x1c
#define ABS_VOLUME 0x20
#define ABS_MISC 0x28
//多点触摸设备以ABS_MT_XXX(MT:Multi-touch,意思为:多点触摸)事件承载、上报触摸点的信息
#define ABS_MT_SLOT 0x2f /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */ //X 轴坐标
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */ //Y 轴坐标
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ ID
#define ABS_MT_PRESSURE 0x3a/* Pressure on contact area */ //按压力
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
#define ABS_MT_TOOL_X 0x3c/* Center X tool position */
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */
#define ABS_MAX 0x3f
#define ABS_CNT (ABS_MAX+1)
单点触摸
-
单点、多点触摸设备分别以ABS_XXX、ABS_MT_XXX事件承载、上报触摸点的信息
-
事件上报顺序★★★★★:
/*
当手指点击触摸屏时,首先上报 BTN_TOUCH 事件,此时 value=1,表示按下;
接着上报 ABS_X、ABS_Y事件将 X、Y 轴坐标数据发送给应用层,数据上报完成接着上报一个同步事件 SYN_REPORT,表示此次触摸点信息已经完整。
当手指在触摸屏上滑动时,不会上报 BTN_TOUCH 事件,因为滑动过程并未发生按下、松开动作。
当松开时,首先上报了 BTN_TOUCH 事件,此时 value=0,表示手指已经松开了触摸屏,接着上报一个同步事件 SYN_REPORT。
*/
# 点击触摸屏 BTN_TOUCH ABS_X ABS_YSYN_REPORT
# 滑动 ABS_X ABS_Y SYN_REPORT
# 松开 BTN_TOUCH SYN_REPORT
多点触摸Type B 协议
-
多点触摸设备使用多点触摸(MT)协议的Type B 协议上报各个触摸点的数据:
Type B 协议适用于能够追踪并区分触摸点的设备,开发板配套使用的触摸屏都属于这类设备; Type B协议的重点是通过 ABS_MT_SLOT 事件上报各个触摸点信息的更新!
-
事件上报顺序★★★★:
ABS_MT_SLOT 0 ABS_MT_TRACKING_ID 10 ABS_MT_POSITION_X ABS_MT_POSITION_Y ABS_MT_SLOT 1 ABS_MT_TRACKING_ID 11 ABS_MT_POSITION_X ABS_MT_POSITION_Y SYN_REPORT
-
多点触摸设备上报的一轮完整数据中可能包含多个触摸点的信息;
-
重点是通过 ABS_MT_SLOT 事件上报各个触摸点信息的更新!
-
测试触摸屏
对应的设备节点不同
cat /proc/bus/input/devices //查看
运行struct input_event 结构体示例代码:注意设备节点挂载不同
显然:触摸屏设备还可以上报按键类(长按不支持)事件和同步类事件。
不管是键盘也好、或者是鼠标、触摸屏,都可以像上面那样将输入设备的数据直接打印出来
LCD应用
获取触摸屏的信息
-
触摸屏支持的最大触摸点数、触摸屏 X、Y 坐标的范围等。
-
通过 ioctl()函数可获取到这些信息
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
不详细介绍,感兴趣可移步笔者另外文件IO的文章有详细介绍!
LCD支持最大触摸点数
- 触摸底层原理(ioctl)可跳过,主要了解实际应用程序实现原理,后面实际应用再来理解更好;
struct input_absinfo info;
int max_slots; //最大触摸点数
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info))
perror("ioctl error");
max_slots = info.maximum + 1 - info.minimum;
/*获取LCD支持最大触摸点数*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
/*
多点触摸事件上报流程
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 10
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 11
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_REPORT
*/
int main(int argc, char *argv[])
{
struct input_absinfo info;
int fd = -1;
int max_slots;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取slot信息: 触摸屏支持的最大触摸点数*/
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info)) {
perror("ioctl error");
close(fd);
exit(EXIT_FAILURE);
}
max_slots = info.maximum + 1 - info.minimum;
printf("max_slots: %d\n", max_slots);
/* 关闭、退出 */
close(fd);
exit(EXIT_SUCCESS);
}
单点/移动触摸应用程序
-
ALPHA/Mini 开发板配套使用的触摸屏均支持多点触摸,这里当成单点触摸设备用;
-
编写一个单点触摸应用程序,获取一个触摸点的坐标信息,并将其打印出来;
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
/*
# 点击触摸屏 BTN_TOUCH ABS_X ABS_YSYN_REPORT
# 滑动 ABS_X ABS_Y SYN_REPORT
# 松开 BTN_TOUCH SYN_REPORT
*/
int main(int argc, char *argv[])
{
struct input_event in_ev;
int x, y; //触摸点x和y坐标
int down; //用于记录BTN_TOUCH事件的value,1表示按下,0表示松开,-1表示移动
int valid; //用于记录数据是否有效(我们关注的信息发生更新表示有效,1表示有效,0表示无效)
int fd = -1;
/* 校验传参 */
if (2 != argc)
{
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY)))
{
perror("open error");
exit(EXIT_FAILURE);
}
x = y = 0; //初始化x和y坐标值
down = -1; //初始化<移动>
valid = 0;//初始化<无效>
for ( ; ; )
{
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event)))
{
perror("read error");
exit(EXIT_FAILURE);
}
switch (in_ev.type)
{
case EV_KEY: //按键事件
if (BTN_TOUCH == in_ev.code)
{
down = in_ev.value;
valid = 1;
}
break;
case EV_ABS: //绝对位移事件
switch (in_ev.code)
{
case ABS_X: //X坐标
x = in_ev.value;
valid = 1;
break;
case ABS_Y: //Y坐标
y = in_ev.value;
valid = 1;
break;
}
break;
case EV_SYN: //同步事件
if (SYN_REPORT == in_ev.code) {
if (valid) {//判断是否有效
switch (down) {//判断状态
case 1:
printf("按下(%d, %d)\n", x, y);
break;
case 0:
printf("松开\n");
break;
case -1:
printf("移动(%d, %d)\n", x, y);
break;
}
valid = 0; //重置valid
down = -1; //重置down
}
}
break;
}
}
}
多点触摸应用程序
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2021. All rights reserved.
文件名 : read_mt.c
作者 : 邓涛
版本 : V1.0
描述 : 触摸屏多点触摸应用程序示例代码
其他 : 无
论坛 : www.openedv.com
日志 : 初版 V1.0 2021/6/15 邓涛创建
***************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <linux/input.h>
/*
多点触摸事件上报流程
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 10
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 11
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_REPORT
*/
/* 用于描述MT多点触摸每一个触摸点的信息 */
struct ts_mt {
int x; //X坐标
int y; //Y坐标
int id; //对应ABS_MT_TRACKING_ID
int valid; //数据有效标志位(=1表示触摸点信息发生更新)
};
/* 一个触摸点的x坐标和y坐标 */
struct tp_xy {
int x;
int y;
};
static int ts_read(const int fd, const int max_slots,
struct ts_mt *mt)
{
struct input_event in_ev;
static int slot = 0;//用于保存上一个slot
static struct tp_xy xy[12] = {0};//用于保存上一次的x和y坐标值,假设触摸屏支持的最大触摸点数不会超过12
int i;
/* 对缓冲区初始化操作 */
memset(mt, 0x0, max_slots * sizeof(struct ts_mt)); //清零
for (i = 0; i < max_slots; i++)
mt[i].id = -2;//将id初始化为-2, id=-1表示触摸点删除, id>=0表示创建
for ( ; ; ) {
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
return -1;
}
switch (in_ev.type) {
case EV_ABS:
switch (in_ev.code) {
case ABS_MT_SLOT:
slot = in_ev.value;
break;
case ABS_MT_POSITION_X:
xy[slot].x = in_ev.value;
mt[slot].valid = 1;
break;
case ABS_MT_POSITION_Y:
xy[slot].y = in_ev.value;
mt[slot].valid = 1;
break;
case ABS_MT_TRACKING_ID:
mt[slot].id = in_ev.value;
mt[slot].valid = 1;
break;
}
break;
//case EV_KEY://按键事件对单点触摸应用比较有用
// break;
case EV_SYN:
if (SYN_REPORT == in_ev.code) {
for (i = 0; i < max_slots; i++) {
mt[i].x = xy[i].x;
mt[i].y = xy[i].y;
}
}
return 0;
}
}
}
int main(int argc, char *argv[])
{
struct input_absinfo slot;
struct ts_mt *mt = NULL;
int max_slots;
int fd;
int i;
/* 参数校验 */
if (2 != argc) {
fprintf(stderr,"usage: %s <input_dev>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开文件 */
fd = open(argv[1], O_RDONLY);
if (0 > fd) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取触摸屏支持的最大触摸点数 */
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &slot)) {
perror("ioctl error");
close(fd);
exit(EXIT_FAILURE);
}
max_slots = slot.maximum + 1 - slot.minimum;
printf("max_slots: %d\n", max_slots);
/* 申请内存空间并清零 */
mt = calloc(max_slots, sizeof(struct ts_mt));
/* 读数据 */
for ( ; ; ) {
if (0 > ts_read(fd, max_slots, mt))
break;
for (i = 0; i < max_slots; i++) {
if (mt[i].valid) {//判断每一个触摸点信息是否发生更新(关注的信息发生更新)
if (0 <= mt[i].id)
printf("slot<%d>, 按下(%d, %d)\n", i, mt[i].x, mt[i].y);
else if (-1 == mt[i].id)
printf("slot<%d>, 松开\n", i);
else
printf("slot<%d>, 移动(%d, %d)\n", i, mt[i].x, mt[i].y);
}
}
}
/* 关闭设备、退出 */
close(fd);
free(mt);
exit(EXIT_FAILURE);
}
事件上报流程总结
多个事件上报,再去看事件具体的值,组合实现真正的实际编程需求!!!!!
按键事件(EV_KEY…)
- 获取按键状态,判断按键当前是按下、松开或长按状态
# 以字母 A 键为例 //示例代码是EV_KEY事件
KEY_A //上报 KEY_A 事件
SYN_REPORT //同步
#如果是按下,则上报 KEY_A 事件时,value=1;
#如果是松开,则 value=0;如果是长按,则 value=2。
单点/移动事件
//单点/滑动/松开事件(EV_KEY)上报流程
# 点击触摸屏 BTN_TOUCH ABS_X ABS_YSYN_REPORT
# 滑动 ABS_X ABS_Y SYN_REPORT
# 松开 BTN_TOUCH SYN_REPORT
多点事件
//多点触摸事件上报流程
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 10
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 11
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_REPORT