触摸屏应用编程(基于正点)
触摸屏设备是一个绝对位移设备,可以上报绝对位移事件,绝对位移事件如下:
#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
#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_X(value 值对应的是 X 轴坐标值)、 ABS_Y(value 值对应的是 Y 轴坐标值)等绝对位移事件,而有些设备可能还支持 Z 轴坐标(通过 ABS_Z 事件上报、 value 值对应的便是 Z 轴坐标值)、 按压力大小(通过 ABS_PRESSURE 事件上报、 value 值对应的便是按压力大小) 以及接触面积等属性。
多点:对于多点触摸设备, 一轮完整的数据可能包含多个触摸点信息。 多点触摸设备则是以 ABS_MT_XXX (MT 是 Multi-touch, 意思为: 多点触摸)事件承载、上报触摸点的信息, 如 ABS_MT_POSITION_X(X 轴坐标) 、 ABS_MT_POSITION_Y(Y 轴坐标)等绝对位移事件
触摸屏设备除了上报绝对位移事件之外,还可以上报按键类事件和同步类事件。同步事件很好理解,因为几乎每一个输入设备都会上报同步事件、告知应用层本轮数据是否完整;当手指点击触摸屏或手指从触摸屏离开时,此时就会上报按键类事件, 用于描述按下触摸屏和松开触摸屏; 具体的按键事件为BTN_TOUCH(code=0x14a,也就是 330) ,当然,手指在触摸屏上滑动不会上报 BTN_TOUCH 事件。
{Tips: BTN_TOUCH 事件不支持长按状态,故其 value 不会等于 2。 对于多点触摸设备来说,只有第一个点按下时上报 BTN_TOUCH 事件 value=1、当最后一个点离开触摸屏时上报 BTN_TOUCH 事件 value=0。)
单点触摸设备-事件上报顺序
# 点击触摸屏时
BTN_TOUCH
ABS_X
ABS_Y
SYN_REPORT
# 滑动
ABS_X
ABS_Y
SYN_REPORT
# 松开
BTN_TOUCH
SYN_REPORT
以上列举出只是一个大致流程,实际上对于不同的触摸屏设备,能够获取到的信息量大小是不相同的。
多点触摸设备--事件上报的顺序
多点触摸的一轮可能包含多个触摸数据(比如5根手指同时滑动就会更新5个触摸点,正点开发板配屏幕就是多点触摸设备)
在 Linux 内核中, 多点触摸设备使用多点触摸(MT)协议上报各个触摸点的数据, MT 协议分为两种类型: Type A 和 Type B, Type A 协议实际使用中用的比较少,几乎处于淘汰的边缘, 这里就不再给大家介绍了,我们重点来看看 Type B 协议
MT 协议之 Type B 协议
Type B 协议适用于能够追踪并区分触摸点的设备,开发板配套使用的触摸屏都属于这类设备。 Type B协议的重点是通过 ABS_MT_SLOT 事件上报各个触摸点信息的更新!
其能够追踪并区分触摸点的设备通常在硬件上能够区分不同的触摸点, 譬如对于一个 5 点触摸设备来说,硬件能够为每一个识别到的触摸点与一个 slot 进行关联,这个 slot 就是一个编号, 触摸点 0、触摸点 1、触摸点 2 等。底层驱动向应用层上报 ABS_MT_SLOT 事件, 此事件会告诉接收者当前正在更新的是哪个触摸点的数据, ABS_MT_SLOT 事件中对应的 value 数据存放的便是一个 slot、以告知应用层当前正在更新 slot关联的触摸点对应的信息。
每个识别出来的触摸点分配一个 slot, 与该 slot 关联起来, 利用这个 slot 来传递对应触点的变化。 除了ABS_MT_SLOT 事 件 之 外 , Type B 协 议 还 会 使 用 到 ABS_MT_TRACTKING_ID 事 件 , 其用于触摸点的创建、替换和销毁工作。
ABS_MT_TRACTKING_ID 事件携带的数据 value 表示一个 ID,一个非负数的 ID(ID>=0) 表示一个有效的触摸点,如果 ID 等于-1 表示该触摸点已经不存在、被移除了;一个以前不存在的 ID 表示这是一个新的触摸点。
Type B 协议可以减少发送到用户空间的数据,只有发生了变更的数据才会上报,譬如某个触摸点发生了移动,但仅仅只改变了 X 轴坐标、而未改变 Y 轴坐标,那么内核只会将改变后的 X 坐标值通过ABS_MT_POSITION_X 事件发送给应用层。
Type B 协议下多点触摸设备上报数据的流程列举如下
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
关于上面提到很多关于slot和ID事件,solt是硬件上的一个概念,而ID则可认为是软件上的概念 。假设某个触摸屏最大支持5个触摸点,那么每个触摸点都有个自己的编号:0、1、2等,这个编号就是solt。其通常是按照时间先后顺序来的,譬如第一根手指先触碰到触摸屏,那第一根手指就对应触摸点 0(slot=0),接着第二根手指触碰到触摸屏则对应触摸点 1(slot=1)以此类推! 这个通常是硬件所支持的。
而 ID 可认为是软件上的一个概念,它也用于区分不同的触摸点,但是它跟 slot 不同, 不是同一层级的概念;举个例子,譬如一根手指触碰到触摸屏之后拿开,然后再次触碰触摸屏,这个过程中,假设只有这一根手指进行触碰操作,那么两次触碰对应都是触摸点 0(slot=0)。但从触摸点的生命周期来看, 它们是同一个触摸点吗?答案肯定不是,为啥呢?手指从触摸屏上离开后,该触摸点就消失了、被删除了, 该触摸点的生命周期也就到此结束了,所以它们自然是不同的触摸点, 所以它们的 ID 是不同的。
1. 触摸点实验测试:
开始前需要将开发板的桌面程序退出,以免影响到读取。步骤:开发板开机->设置->退出桌面程序。
利用mobaxterm连接开发板后,输入cat /proc/bus/input/devices可以看到触摸屏对应的设备节
点。
可以看到其为event1,打开上篇文章的程序read_input,在开发板执行./read_input /dev/input/event1(下图用的是正点的图,是./testAPP,换成自己的工程名就行)后点击触摸屏,一个手指点击触摸屏先不松开,可得以下指令:
首先第一行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID(code=57)事件,并且 value 值等于 78,也就是 ID,这个 ID 是一个非负数,所以表示这是一个新的触摸点被创建,也就意味着触摸屏上产生了一个新的触摸点(手指按下) 。
第二行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_POSITION_X(code=53)事件,其 value对应的便是触摸点的 X 坐标;第三行上报了 ABS_MT_POSITION_Y(code=54)事件,其 value 值对应的便是触摸点 Y 坐标,所以由此可知该触摸点的坐标为(372, 381)。
第四行上报了按键类事件 EV_KEY(type=1)中的 BTN_TOUCH(code=330) , value 值等于 1,表示这是触摸屏上最先产生的触摸点(slot=0、也就是触摸点 0) 。
第五行和第六行分别上报了绝对位移事件 EV_ABS(type=3)中的 ABS_X(code=0)和 ABS_Y(code=1),其 value 分别对应的是触摸点的 X 坐标和 Y 坐标。 多点触摸设备也会通过 ABS_X、 ABS_Y 事件上报触摸点的 X、 Y 坐标, 但通常只有触摸点 0 支持,所以可以把多点触摸设备当成单点触摸设备来使用。
最后一行上报了同步类事件 EV_SYN(type=0)中的 SYN_REPORT(code=0)事件,表示此次触摸点的信息全部上报完毕
在第一个触摸点的基础上,增加第二个触摸点,打印信息如下所示:
1~7 行不再解释,第八行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_SLOT 事件(code=47),表示目前要更新 slot=1 所关联的触摸点(也就是触摸点 1) 对应的信息。
第九行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID 事件(code=57), ID=79,这是之前没有出现过的 ID,表示这是一个新的触摸点。
第十、十一行分别上报了 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件。
最后一行上报同步事件(type=0、 code=0) ,告知应用层数据完整。
当手指松开时,触摸点就会被销毁,上报 ABS_MT_TRACKING_ID 事件,并将 value 设置为-1(ID),如下所示:
2.获取触摸屏支持的最大触摸点数程序:
源码(加了注释):
#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_absinfo info; // 用于存储输入设备的绝对轴信息int fd = -1; // 文件描述符,初始化为-1int max_slots; // 最大的触摸点数量/* 校验传参,确保输入参数数量正确,程序需要一个输入设备路径作为参数 */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); // 退出程序}/* 获取 ABS_MT_SLOT 信息,用于多点触控 */if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info)) {perror("ioctl error"); // 输出错误信息close(fd); // 关闭文件exit(EXIT_FAILURE); // 退出程序}/* 计算最大 slot 数量(触摸点数量),计算公式为最大值减去最小值再加1 */max_slots = info.maximum + 1 - info.minimum;printf("max_slots: %d\n", max_slots); // 输出最大触摸点数量/* 关闭文件,退出程序 */close(fd);exit(EXIT_SUCCESS);
}
程序解释:
1.
/* 打开输入设备文件,以只读方式打开 */fd = open(argv[1], O_RDONLY);if (0 > fd) {perror("open error"); // 输出错误信息exit(EXIT_FAILURE); // 退出程序}
打开设备文件,说白了在终端还需输入文件路径。路径为/dev/input/event1
2.
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info)) {perror("ioctl error"); // 输出错误信息close(fd); // 关闭文件exit(EXIT_FAILURE); // 退出程序}
EVIOCGABS(ABS_MT_SLOT)函数:
关于了解这个函数,我们得先要知道一些概念:ioctl()函数
ioctl()函数可以获取触摸屏设备的信息,譬如触摸屏支持的最大触摸点数、触摸屏 X、 Y 坐标的范围等。
其原型为
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
第一个参数 fd 对应文件描述符;第二个参数 request 与具体要操作的对象有关,没有统一值, 表示向文件描述符请求相应的操作,也就是请求指令;此函数是一个可变参函数, 第三个参数需要根据 request 参数来决定,配合 request 来使用。
先来看下输入设备的 ioctl()该怎么用,在 input.h 头文件有这样一些宏定义,如下所示:
#define EVIOCGVERSION _IOR('E', 0x01, int) /* 获取驱动版本 */
#define EVIOCGID _IOR('E', 0x02, struct input_id) /* 获取设备ID */
#define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) /* 获取重复设置 */
#define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) /* 设置重复设置 */
#define EVIOCGKEYCODE _IOR('E', 0x04, unsigned int[2]) /* 获取按键码 */
#define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry) /* 获取按键码 (版本2) */
#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2]) /* 设置按键码 */
#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry) /* 设置按键码 (版本2) */
#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) /* 获取设备名称 */
#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len) /* 获取物理位置 */
#define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len) /* 获取唯一标识符 */
#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len) /* 获取设备属性 */
#define EVIOCGMTSLOTS(len) _IOC(_IOC_READ, 'E', 0x0a, len) /* 获取多点触控槽 */
#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len) /* 获取全局按键状态 */
#define EVIOCGLED(len) _IOC(_IOC_READ, 'E', 0x19, len) /* 获取所有LED状态 */
#define EVIOCGSND(len) _IOC(_IOC_READ, 'E', 0x1a, len) /* 获取所有声音状态 */
#define EVIOCGSW(len) _IOC(_IOC_READ, 'E', 0x1b, len) /* 获取所有开关状态 */
#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) /* 获取事件位 */
#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) /* 获取绝对轴值/范围 */
#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo) /* 设置绝对轴值/范围 */
#define EVIOCSFF _IOW('E', 0x80, struct ff_effect) /* 向力反馈设备发送一个力效应 */
#define EVIOCRMFF _IOW('E', 0x81, int) /* 删除一个力效应 */
#define EVIOCGEFFECTS _IOR('E', 0x84, int) /* 报告同时可播放的效果数量 */
#define EVIOCGRAB _IOW('E', 0x90, int) /* 抓取/释放设备 */
#define EVIOCREVOKE _IOW('E', 0x91, int) /* 撤销设备访问 */
每一个宏定义后面都有相应的注释,对于 input 输入设备,对其执行 ioctl()操作需要使用这些宏, 不同的宏表示不同请求指令; 譬如使用 EVIOCGNAME 宏获取设备名称,使用方式如下:
char name[100];
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
EVIOCGNAME(len)就表示用于接收字符串数据的缓冲区大小,而此时 ioctl()函数的第三个参数需要传入一个缓冲区的地址,该缓冲区用于存放设备名称对应的字符串数据。
EVIOCG(get)开头的表示获取信息, EVIOCS(set)开头表示设置;这里暂且不管其它宏,重点来看看 EVIOCGABS(abs)宏, 这个宏也是通常使用最多的, 如下所示:
#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo)
通过这个宏可以获取到触摸屏 slot(slot<0>表示触摸点 0、 slot<1>表示触摸点 1、 slot<2>表示触摸点 2,以此类推!)的取值范围, 可以看到使用该宏需要传入一个 abs 参数,该参数表示为一个 ABS_XXX 绝对位移事件,譬如 EVIOCGABS(ABS_MT_SLOT)表示获取触摸屏的 slot 信息,此时 ioctl()函数的第三个参数是一个 struct input_absinfo *的指针,指向一个 struct input_absinfo 对象,调用 ioctl()会将获取到的信息写入到struct input_absinfo 对象中。 struct input_absinfo 结构体如下所示:
struct input_absinfo {
__s32 value; //最新的报告值
__s32 minimum; //最小值
__s32 maximum; //最大值
__s32 fuzz;
__s32 flat;
__s32 resolution;
};
获取触摸屏支持的最大触摸点数:
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;
了解了这些,我们重新回到2.的程序解释。
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info));
ioctl函数
系统调用来获取与多点触控槽相关的绝对轴信息,并将这些信息存储在 info
结构体中。如果调用失败,则输出错误信息并退出程序。
3.max_slots = info.maximum + 1 - info.minimum;
因为触摸点从0开始,所以最大触摸点就是最大值-最小值+1。
上机测试:
在ubuntu编译
利用scp -r将文件传到开发板。
scp -r book@192.168.5.12:/home/book/project/APP/app/17_input/read_slot /home/root/app
scp -r 【用户名】@【ubuntu的ip地址】:【unbuntu上要传入文件的路径】 【开发板要存入的路径】
输入./read_slot /dev/input/event1
得到此显示屏最多支持5个触摸点。
3.单点触摸应用程序
任务:获取一个触摸点的坐标信息并打印出来。
加了注释的代码:
#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 in_ev; // 定义输入事件结构体int x, y; // 触摸点的 x 和 y 坐标int down; // 用于记录 BTN_TOUCH 事件的值,1 表示按下,0 表示松开,-1 表示移动int valid; // 用于记录数据是否有效,1 表示有效,0 表示无效int fd = -1; // 文件描述符,初始化为 -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 坐标值为 0down = -1; // 初始化 down 状态为 -1(移动)valid = 0; // 初始化 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; // 更新 down 状态valid = 1; // 数据有效}break;case EV_ABS: // 绝对位移事件switch (in_ev.code) {case ABS_X: // X 坐标x = in_ev.value; // 更新 x 坐标valid = 1; // 数据有效break;case ABS_Y: // Y 坐标y = in_ev.value; // 更新 y 坐标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;}}
}
上机测试:
scp传输到开发板
正点测试:将程序名换成自己的就行。
4.多点触摸应用程序
加上注释的源码:
#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>/* 用于描述MT多点触摸每一个触摸点的信息 */
struct ts_mt {int x; // X坐标int y; // Y坐标int id; // 对应ABS_MT_TRACKING_IDint valid; // 数据有效标志位(=1表示触摸点信息发生更新)
};/* 一个触摸点的x坐标和y坐标 */
struct tp_xy {int x;int y;
};/*** 读取触摸屏数据* * @param fd: 文件描述符* @param max_slots: 最大触摸点数* @param mt: 用于存储触摸点信息的数组* * @return 0 表示成功, -1 表示失败*/
static int ts_read(const int fd, const int max_slots, struct ts_mt *mt)
{struct input_event in_ev; // 输入事件结构体static int slot = 0; // 用于保存当前处理的slotstatic struct tp_xy xy[12] = {0}; // 用于保存上一次的x和y坐标值,假设触摸屏支持的最大触摸点数不会超过12int 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; // 更新当前处理的slotbreak;case ABS_MT_POSITION_X:xy[slot].x = in_ev.value; // 更新x坐标mt[slot].valid = 1; // 标记数据有效break;case ABS_MT_POSITION_Y:xy[slot].y = in_ev.value; // 更新y坐标mt[slot].valid = 1; // 标记数据有效break;case ABS_MT_TRACKING_ID:mt[slot].id = in_ev.value; // 更新触摸点IDmt[slot].valid = 1; // 标记数据有效break;}break;case EV_SYN: // 同步事件if (SYN_REPORT == in_ev.code) {for (i = 0; i < max_slots; i++) {mt[i].x = xy[i].x; // 更新触摸点的x坐标mt[i].y = xy[i].y; // 更新触摸点的y坐标}return 0; // 成功读取并更新数据}break;}}
}int main(int argc, char *argv[])
{struct input_absinfo slot; // 用于存储ABS_MT_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"); // 输出ioctl错误信息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));if (NULL == mt) {perror("calloc error"); // 输出内存分配错误信息close(fd);exit(EXIT_FAILURE);}/* 读取数据 */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); // 输出松开信息elseprintf("slot<%d>, 移动(%d, %d)\n", i, mt[i].x, mt[i].y); // 输出移动坐标}}}/* 关闭设备、退出 */close(fd);free(mt);exit(EXIT_SUCCESS);
}
程序解释:
1.
memset(mt, 0x0, max_slots * sizeof(struct ts_mt)); // 清零
memset
是一个标准库函数,用于将指定内存区域的每个字节设置为指定的值。
mt
:指向要设置的内存区域的指针,这里是mt
数组的首地址。0x0
:要设置的值,这里是 0,表示将内存区域的每个字节都设置为 0。max_slots * sizeof(struct ts_mt)
:要设置的字节数。max_slots
是触摸点的最大数量,sizeof(struct ts_mt)
是每个触摸点信息结构体的大小。这个表达式计算出整个mt
数组的总大小。
具体作用:
memset(mt, 0x0, max_slots * sizeof(struct ts_mt));
将 mt
数组的所有字节设置为 0,相当于将 mt
数组清零。这样可以确保数组中的每个元素的所有字段都被初始化为 0,避免使用未初始化的数据
2.
mt = calloc(max_slots, sizeof(struct ts_mt));if (NULL == mt) {perror("calloc error"); // 输出内存分配错误信息close(fd);exit(EXIT_FAILURE);}
mt = calloc(max_slots, sizeof(struct ts_mt));作用是为 mt
数组动态分配内存,并将分配的内存区域初始化为 0
calloc
是一个标准库函数,用于动态分配内存,并将分配的内存区域初始化为 0。
max_slots
:要分配的元素数量,这里是触摸点的最大数量。sizeof(struct ts_mt)
:每个元素的大小,这里是struct ts_mt
结构体的大小。
-
具体作用:
calloc(max_slots, sizeof(struct ts_mt));
分配max_slots
个struct ts_mt
结构体的内存空间,并将这块内存的每个字节都初始化为 0。mt
是一个指向struct ts_mt
类型的指针,通过calloc
分配的内存将存储在mt
指针中。
-
返回值:
- 如果分配成功,
calloc
返回一个指向分配内存的指针。 - 如果分配失败,
calloc
返回NULL
。
- 如果分配成功,
注意:
calloc
:用于动态分配内存,并初始化为 0。适用于需要在程序运行时根据情况分配内存的场景。memset
:用于将已有的内存区域设置为指定的值(通常是 0)。适用于重置或初始化已经分配好的内存。
3.为什么要用到memset和
calloc?
calloc
用于在程序运行时根据需要动态分配内存。在这段程序中,触摸点的数量 max_slots
是运行时才知道的,所以需要动态分配内存来存储这些触摸点的信息。
memset:
在循环读取输入事件时,你需要在每次读取前将 mt
数组清零,以确保每次读取的数据是新的,而不是上一次的数据残留。这时 memset
可以快速将整个数组清零。
上机测试:
scp指令:
测试成功: