输入设备之触摸屏

目录

一、解析触摸屏设备上报的数据

单点触摸和多点触摸

单点触摸设备事件上报的流程大概如下:

 # 点击触摸屏时

# 滑动 

多点触摸设备--事件上报的顺序

MT 协议之 Type B 协议

测试

二、获取触摸屏的信息

 代码编写

代码如下

验证

三、多点触摸APP

验证


一、解析触摸屏设备上报的数据

        触摸屏设备是一个绝对位移设备,可以上报绝对位移事件,绝对位移事件如下:

单点触摸和多点触摸

        触摸屏分为多点触摸设备和单点触摸设备。 单点触摸设备只支持单点触摸, 一轮(一个同步事件称为一轮) 完整的数据只包含一个触摸点信息; 单点触摸设备以 ABS_XXX 事件承载、上报触摸点的信息,譬如 ABS_X(value 值对应的是 X 轴坐标值)、 ABS_Y(value 值对应的是 Y 轴坐标值)等绝对位移事件,而有些设备可能还支持 Z 轴坐标(通过 ABS_Z 事件上报、 value 值对应的便是 Z 轴坐标值)、 按压力大小(通过 ABS_PRESSURE 事件上报、 value 值对应的便是按压力大小) 以及接触面积等属性。 大部分的单点触摸设备都会上报 ABS_X 和 ABS_Y 事件,而其它绝对位移事件则根据具体的设备以及驱动的实现而定!

        多点触摸设备可支持多点触摸,对于多点触摸设备, 一轮完整的数据可能包含多个触摸点信息。 多点触摸设备则是以 ABS_MT_XXX(MT 是 Multi-touch, 意思为: 多点触摸)事件承载、上报触摸点的信息, 如 ABS_MT_POSITION_X(X 轴坐标) 、 ABS_MT_POSITION_Y(Y 轴坐标)等绝对位移事件

        触摸屏设备除了上报绝对位移事件之外,还可以上报按键类事件和同步类事件。因为几乎每一个输入设备都会上报同步事件、告知应用层本轮数据是否完整;当手指点击触摸屏或手指从触
摸屏离开时,此时就会上报按键类事件, 用于描述按下触摸屏和松开触摸屏; 具体的按键事件为
BTN_TOUCH(code=0x14a,也就是 330) ,手指在触摸屏上滑动不会上报 BTN_TOUCH 事件。 BTN_TOUCH 事件不支持长按状态,故其 value 不会等于 2。 对于多点触摸设备来说,只有第一个点按下时上报 BTN_TOUCH 事件 value=1、当最后一个点离开触摸屏时上报 BTN_TOUCH 事件 value=0。

单点触摸设备事件上报的流程大概如下:

 # 点击触摸屏时

BTN_TOUCH
ABS_X
ABS_Y
SYN_REPORT

        当手指点击触摸屏时,首先上报 BTN_TOUCH 事件,此时 value=1,表示按下;接着上报 ABS_X、 ABS_Y事件将 X、 Y 轴坐标数据发送给应用层;数据上报完成接着上报一个同步事件 SYN_REPORT,表示此次触摸点信息已经完整

# 滑动 

         此时状态手指已经在屏幕上,是接着上面已经完成点击屏幕但手指没松开的

ABS_X
ABS_Y
SYN_REPORT

        手指在触摸屏上滑动时,并不会上报 BTN_TOUCH 事件,因为滑动过程并未发生按下、松开这种动作。 

# 松开 

BTN_TOUCH
SYN_REPORT

         当松开时,首先上报了 BTN_TOUCH 事件,此时 value=0,表示手指已经松开了触摸屏,接着上报一个同步事件 SYN_REPORT。

        上面列举出只是一个大致流程,实际上对于不同的触摸屏设备,能够获取到的信息量大小是不相同的,譬如某设备只能读取到触摸点的 X 和 Y 坐标、而另一设备却能读取 X、 Y 坐标以及按压力大小、 触点面积等信息, 总之这些数据都会在 SYN_REPORT 同步事件之前上报给应用层

多点触摸设备--事件上报的顺序

        多点触摸设备上报的一轮完整数据中可能包含多个触摸点的信息,譬如 5 点触摸设备,如果 5 个手指同时在触摸屏上滑动,那么硬件就会更新 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 事件则用于触摸点的创建、替换和销毁工作,ABS_MT_TRACTKING_ID 事件携带的数据 value 表示一个 ID,一个非负数的 ID(ID>=0) 表示一个有效的触摸点,如果 ID 等于-1 表示该触摸点已经不存在、被移除了;一个以前不存在的 ID 表示这是一个新的触摸点

        Type B 协议可以减少发送到用户空间的数据,只有发生了变更的数据才会上报,譬如某个触摸点发生了移动,但仅仅只改变了 X 轴坐标、而未改变 Y 轴坐标,那么内核只会将改变后的 X 坐标值通过ABS_MT_POSITION_X 事件发送给应用层

测试

用上一章读取input_event结构体数据的代码测试一下触摸屏,首先在测试触摸屏之前,需要保证开发板上已经连接了 LCD 屏,使用命令"cat /proc/bus/input/devices",确定触摸屏对应的设备节点

 这里使用名字是goodix-ts,使用的是其它屏,那么查看到的名称可能不是"goodix-ts",设备文件是event1

加载APP测试一下

        APP是上章使用读取input_event结构体数据的,动作是先放第一个手指在触摸屏上不松开

        第一行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID(code=57)事件,并且 value 值等于 0,也就是 ID,这个 ID 是一个非负数,所以表示这是一个新的触摸点被创建,也就意味着触摸屏上产生了一个新的触摸点(手指按下)

        第二行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_POSITION_X(code=53)事件,其 value对应的便是触摸点的 X 坐标;
        第三行上报了 ABS_MT_POSITION_Y(code=54)事件,其 value 值对应的便是触摸点 Y 坐标,所以由此可知该触摸点的坐标为(314, 237)

        第四行上报了ABS_MT_TOUCH_MAJOR(code=48)事件
       第五行上报了 ABS_MT_WIDTH_MAJOR(code=50)事件
        最后一行上报了同步类事件 EV_SYN(type=0)中的 SYN_REPORT(code=0)事件,表示此次触摸点的信息全部上报完毕。

在第一个触摸点的基础上,增加第二个触摸点,打印信息如下所示

         1-6行与上同理,第7行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_SLOT 事件(code=47),表示目前要更新 slot=1 所关联的触摸点(也就是触摸点 1) 对应的信息

        第8行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID 事(code=57), ID=1,这是之前没有出现过的 ID,表示这是一个新的触摸点

        第九、十、十一十二行分别上报了 ABS_MT_POSITION_X事件 、 ABS_MT_POSITION_Y 事件、ABS_MT_TOUCH_MAJOR(code=48事件和 ABS_MT_WIDTH_MAJOR(code=50)事件

        最后一行上报同步事件(type=0、 code=0) ,告知应用层数据完整

当松开时,触摸点就会被销毁,上报 ABS_MT_TRACKING_ID 事件,并将 value 设置为-1(ID)

         不管是键盘也好、或者是鼠标、触摸屏,都可以像上面那样将输入设备的数据直接打印出来,然后自己再去分析,确定该输入设备上报事件的规则和流程,把这些弄懂之后再去编写程序验证结果

二、获取触摸屏的信息

        先了解一下ioctl()函数,原型如下

int ioctl(int fd, unsigned long request, ...);

        第一个参数 fd 对应文件描述符;第二个参数 request 与具体要操作的对象有关,没有统一值, 表示向文件描述符请求相应的操作,也就是请求指令;此函数是一个可变参函数, 第三个参数需要根据 request 参数来决定,配合 request 来使用

        ioctl()是一个文件 I/O 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或设备文件
在 input.h 头文件有这样一些宏定义

      每一个宏定义后面都有注释,对于 input 输入设备,对其执行 ioctl()操作需要使用这些宏, 不同的宏表示不同请求指令,EVIOCG(get)开头的表示获取信息, EVIOCS(set)开头表示设置

      比如使用 EVIOCGNAME 宏获取设备名称,使用方式如下:

char name[100];
ioctl(fd, EVIOCGNAME(sizeof(name)), name);

        EVIOCGNAME(len)就表示用于接收字符串数据的缓冲区大小,而此时 ioctl()函数的第三个参数需要传入一个缓冲区的地址,该缓冲区用于存放设备名称对应的字符串数据

        想获取触摸屏支持的最大触摸点数,首先了解这个EVIOCGABS(abs)宏,原型如下

#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo)

         可以看到使用该宏需要传入一个 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;
};

 代码编写

 25行把ABS_MT_SLOT传给宏EVIOCGABS,表示获取触摸屏的 slot 信息,,后面为 struct input_absinfo 结构体,用于接收数据

31行,计算最大触摸点,因为 slot 数组下标从0开始,所以需要将 slot.maximum 加1

代码如下

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
    struct input_absinfo info;
    int fd = -1, max_slots;
    if (argc != 2)
    {
        fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    if ((fd = open(argv[1], O_RDONLY)) < 0)
    {
        perror("open error");
        exit(EXIT_FAILURE);
    }
    if (ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info) < 0)
    {
        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);
}

验证

  所以这个屏是一个 5 点触摸屏

三、多点触摸APP

        编写代码获取触摸点的坐标信息,并将其打印出来,全部代码如下

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <linux/input.h>

struct ts_mt
{
    int x;
    int y;
    int id;
    int valid;
};

struct tp_xy
{
    int x;
    int y;
};
static int ts_read(const int fd, const int max_slots,
                   struct ts_mt *mt)
{
    int i;
    struct input_event in_ev;
    static int slot = 0;
    static struct tp_xy xy[12] = {0};
    memset(mt, 0x0, max_slots * sizeof(struct ts_mt));
    for (i = 0; i < max_slots; i++)
        mt[i].id = -2;
    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_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, 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 (ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &slot) < 0)
    {
        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 (ts_read(fd, max_slots, mt) < 0)
        {
            break;
        }
        for (i = 0; i < max_slots; i++)
        {
            if (mt[i].valid)
            {
                if (mt[i].id >= 0)
                    printf("slot<%d>, 按下(%d, %d)\n", i, mt[i].x, mt[i].y);
                else if (mt[i].id == -1)
                    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_SUCCESS);
}

        代码开头定义两个结构体,第一个结构体用于描述多点触摸中每一个触摸点的信息,第二个结构体就是一个触摸点中的x和y坐标

        在main函数中,实例一个input_absinfo 用来描述输入设备上的一个信息的结构体,接着定义mt结构体指针,下面用if判断传参是否等于2,接收打开文件,获取触摸屏支持的最大触摸点数max_slots,然后给mt指针申请空间,calloc()在动态分配完内存后,自动初始化该内存空间为零

        进入for死循环中,把文件描述符,最大触摸点传给ts_read函数。

        进入ts_read函数,这个函数用于读取输入设备文件中的事件数据。首先定义一个input_event 结构体用于描述输入设备事件,定义的结构体数组xy为12个元素,主要是用于保存上一次的x和y坐标值,这里假设触摸屏支持的最大触摸点数不会超过12,然后用memset函数初始化缓冲区,全部清零,然后把触摸点的idc全部初始化为-2(因为 id=-1表示触摸点删除, id>=0表示创建),进入ts_read函数的for死循环,先用if来判断读取量是否相等,不相等则意味着读取的数据大小不正确,可能会导致错误。开始判断type类型,如果事件类型是EV_ABS(表示绝对事件),则根据事件代码来处理。例如,如果事件代码是ABS_MT_SLOT(表示触摸点的槽位),则更新当前的槽位;如果事件代码是ABS_MT_POSITION_X或ABS_MT_POSITION_Y(表示触摸点的x坐标和y坐标),则更新当前槽位对应的xy坐标,并将该触摸点的有效标志位设为1;如果事件代码是ABS_MT_TRACKING_ID(表示跟踪ID),则将该跟踪ID保存到当前槽位对应的mt结构体中,并将该触摸点的有效标志位设为1。如果事件类型是EV_SYN(表示同步事件),则根据事件代码来处理。例如,如果事件代码是SYN_REPORT(表示一组事件已经结束),则将保存在xy数组中的坐标值赋值给mt结构体,并返回0。

        返回到main函数的for死循环中,下一步就是在读取事件数据成功后,程序遍历mt结构体数组,判断每一个触摸点信息是否发生更新(关注的信息发生更新),如果发生更新,则输出相应的信息,例如按下、松开或移动等。最后,程序关闭输入设备文件,释放内存,并退出。

验证

实现5点触摸的信息读取

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值