input子系统理论与实例分析

前言

直接上图:(图片来源于正点原子笔记)
在这里插入图片描述
(输入子系统对触摸屏狠重要,会用 TS Handler 狠重要!)
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行
处理。
事件层:和用户空间进行交互。
驱动触发消息,内核收集后产生对应的回调任务,事件层就可以执行内核发布的回调任务。
为什么不直接内核执行任务?你当内核傻啊,内核主要就是调度任务,这个更重要。任务下发后就可以被更重要的任务阻塞。

基础知识

驱动程序上报的数据含义三项重要内容
type:哪类?例如 EV_KEY 按键类
code:哪个?例如 KEY_A
value:值,例如 0 按下,1 松开
而应用层可以得到一个输入事件,一个个 struct input_event:

数据结构

/* include/uapi/linux/input.h */
struct input_event{
    __u16 type;
    __u16 code;
    __u16 value;
};
/* include/uapi/linux/time.h */
struct timeval{
    __kernel_time_t tv_sec;          //秒
    __kernel_suseconds_t tv_usec;    //微秒
}
/* 每个输入事件 input_event 中含有发生时间,timeval 表示的是自系统启动以后过了多少时间。*/

所有输入设备都可以在 /proc/bus/input/devices 中显示。
描述输入设备的结构体:

/* include/uapi/linux/input.h */
struct input_dev {
    const char *name;//输入设备的名字
    const char *phys;//在系统层次结构中设备的物理路径;
    const char *uniq;
    struct input_id id;//输入设备id,包含总线类型,制造商id,产品id和版本号等;
 
    unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; //设备属性和怪异位图
 
    unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //设备支持的事件类型;EV_KEY, EV_REL;
    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   //设备具有的按键(能够报告的)
    unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   //设备相对轴
    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];   //绝对坐标的位图
    unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   //设备支持的杂项设备事件
    unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   //设备存在的led位图
    unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   //设备支持的声音效果
    unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];     //设备支持的力反馈效果位图
    unsigned long swbit[BITS_TO_LONGS(SW_CNT)];     //设备存在的开关位图
 /*以上支持哪些位就ke'y
    unsigned int hint_events_per_packet;
 
 unsigned int keycodemax;                //按键编码表的大小;
 unsigned int keycodesize;               //按键编码表中每个编码的大小
 void *keycode;//按键编码表
 
   
 int (*open)(struct input_dev *dev);     //打开设备 

 void (*close)(struct input_dev *dev);   //关闭设备
 ...
 struct device dev;                      //设备的驱动程序模型视图
 
 struct list_head    h_list;
 struct list_head    node;
};

evbit 上报事件类型

evbit 表示设备能够报告的事件类型; 常见取值如下:

#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 LED
#define EV_SND            0x12 声音事件
#define EV_REP            0x14 表设备支持长按键检测

https://blog.csdn.net/qq_39048131/article/details/126533233
如果是按键事件,那么 EV_KEY 对应的按键值就是:

/* include/uapi/linux/input.h */
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1   2
#define KEY_2   3
#define KEY_3   4
#define KEY_4   5
#define KEY_5   6
#define KEY_6   7
#define KEY_7   8
#define KEY_8   9
#define KEY_9   10
#define KEY_0   11
......
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7

input设备API

申请输入设备:

struct input_dev *input_allocate_device(void)

返回值:申请到的 input_dev。
注销输入设备:

void input_free_device(struct input_dev *dev)

初始化输入设备:
申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类
型 (evbit) 和事件值 (keybit) 这两种。

int input_register_device(struct input_dev *dev)

注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册
的 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。
input_dev 注册过程示例代码如下
所示:

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

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    ......
    inputdev = input_allocate_device(); /* 申请 input_dev */
    inputdev->name = "test_inputdev";   /* 设置 input_dev 名字 */
    /*********第一种设置事件和事件值的方法***********/
    __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
    __set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
    __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */

    /*********第二种设置事件和事件值的方法***********/
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);

    /*********第三种设置事件和事件值的方法***********/
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);

    /* 注册 input_dev */
    input_register_device(inputdev);
    ......
    return 0;
}

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

实际中的驱动,输入设备只是一个机制,主题还是各种协议对应的框架。然后输入一般都伴随着硬件中断,所以一般都是中断服务函数中实现输入事件和值的上报。

按键输入驱动实例

先说好啊,input 主要就学个框架,工作中最好不要把驱动自己全写了,一定要copy相同的类型的设备驱动,然后修改。
设备树:

pinctrl_key: keygrp {
 fsl,pins = <MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080>; 
 /* 将管脚号为MX6UL_PAD_UART1_CTS_B__GPIO1_IO18的管脚复用为0xF080功能: 普通GPIO功能 */
};

key {
 #address-cells = <1>;
 #size-cells = <1>;
 compatible = "atkalpha-key";
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_key>;
 key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* 初始化为低电平 */
 status = "okay";
};

按键输入驱动:

#define KEYINPUT_CNT 1             /* 设备号个数 */
#define KEYINPUT_NAME "keyinput"   /* 名字 */
#define KEY0VALUE 0X01             /* KEY0 按键值 */
#define INVAKEY 0XFF               /* 无效的按键值 */
#define KEY_NUM 1                  /* 按键数量 */

/* 中断 IO 描述结构体 */
 struct irq_keydesc {
 int gpio;                            /* gpio */
 int irqnum;                          /* 中断号 */
 unsigned char value;                 /* 按键对应的键值 */
 char name[10];                       /* 名字 */
 irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};

/* keyinput 设备结构体 */
 struct keyinput_dev{
 dev_t devid;                             /* 设备号 */
 struct cdev cdev;                        /* cdev */
 struct class *class;                     /* 类 */
 struct device *device;                   /* 设备 */
 struct device_node *nd;                  /* 设备节点 */
 struct timer_list timer;                 /* 定义一个定时器 */
 struct irq_keydesc irqkeydesc[KEY_NUM];  /* 按键描述数组 */
 unsigned char curkeynum;                 /* 当前的按键号 */
 struct input_dev *inputdev;              /* input 结构体 */
};

struct keyinput_dev keyinputdev; /* key input 设备 */

/* 中断服务函数,开启定时器,延时10ms */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;

 dev->curkeynum = 0;
 dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));    /* 10ms定时 */
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定时器服务函数,用于按键消抖,定时器到了以后再次读取按键值,如果按键还是处于按下状态就表示按键有效 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct keyinput_dev *dev = (struct keyinput_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];
    value = gpio_get_value(keydesc->gpio);     /* 读取IO值 */
    if(value == 0){                         /* 按下按键 */
        /* 上报按键值 */
        //input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
        input_report_key(dev->inputdev, keydesc->value, 1);/* 最后一个参数表示按下还是松开,1为按下,0为松开 */
        input_sync(dev->inputdev);
    } else {                                     /* 按键松开 */
        //input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
        input_report_key(dev->inputdev, keydesc->value, 0);
        input_sync(dev->inputdev);
    }
}

/* 按键IO初始化 */
static int keyio_init(void)
{
    unsigned char i = 0;
    char name[10];
    int ret = 0;

    keyinputdev.nd = of_find_node_by_path("/key");
    if (keyinputdev.nd== NULL){
        printk("key node not find!\r\n");
        return -EINVAL;
    } 

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd ,"key-gpio", i);
        if (keyinputdev.irqkeydesc[i].gpio < 0) {
            printk("can't get key%d\r\n", i);
        }
    }
    
    /* 初始化key所使用的IO,并且设置成中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(name));    /* 缓冲区清零 */
        sprintf(keyinputdev.irqkeydesc[i].name, "KEY%d", i);        /* 组合名字 */
        gpio_request(keyinputdev.irqkeydesc[i].gpio, name);
        gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);    
        keyinputdev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keyinputdev.nd, i);
    }
    /* 申请中断 */
    keyinputdev.irqkeydesc[0].handler = key0_handler;
    keyinputdev.irqkeydesc[0].value = KEY_0;
    
    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev.irqkeydesc[i].handler, 
                         IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, keyinputdev.irqkeydesc[i].name, &keyinputdev);
        if(ret < 0){
            printk("irq %d request failed!\r\n", keyinputdev.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    /* 创建定时器 */
    init_timer(&keyinputdev.timer);
    keyinputdev.timer.function = timer_function;

    /* 申请input_dev */
    keyinputdev.inputdev = input_allocate_device();
    keyinputdev.inputdev->name = KEYINPUT_NAME;
#if 0
    /* 初始化input_dev,设置产生哪些事件 */
    __set_bit(EV_KEY, keyinputdev.inputdev->evbit);    /* 设置产生按键事件          */
    __set_bit(EV_REP, keyinputdev.inputdev->evbit);    /* 重复事件,比如按下去不放开,就会一直输出信息          */

    /* 初始化input_dev,设置产生哪些按键 */
    __set_bit(KEY_0, keyinputdev.inputdev->keybit);    
#endif

#if 0
    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif

    keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
    input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);

    /* 注册输入设备 */
    ret = input_register_device(keyinputdev.inputdev);
    if (ret) {
        printk("register input device failed!\r\n");
        return ret;
    }
    return 0;
}

static int __init keyinput_init(void)
{
    keyio_init();
    return 0;
}

static void __exit keyinput_exit(void)
{
    unsigned int i = 0;
    /* 删除定时器 */
    del_timer_sync(&keyinputdev.timer);    /* 删除定时器 */
        
    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);
    }
    /* 释放input_dev */
    input_unregister_device(keyinputdev.inputdev);
    input_free_device(keyinputdev.inputdev);
}

上报输入事件

如按键,我们需要在按
键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才
能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同。
如按键,我们需要在按
键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才
能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同。

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_event 函数可以上报所有的事件类型和事件值,Linux 内核也提供了其他的针对具体
事件的上报函数,这些函数其实都用到了 input_event 函数。比如上报按键所使用的
input_report_key 函数,此函数内容如下:

static inline void input_report_key(struct input_dev *dev,
                                    unsigned int code, int value)
{
 input_event(dev, EV_KEY, code, !!value);
}

同样的还有一些其他的事件上报函数:

void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

当我们上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束,
input_sync 函数本质是上报一个同步事件,此函数原型如下所示:

void input_sync(struct input_dev *dev)

按键的上报事件的参考代码如下所示:

/* 定时器服务函数 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
    if(value == 0){ /* 按下按键 */
        /* 上报按键值 */
        input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1,按下 */
        input_sync(inputdev); /* 同步事件 */
    } else { 
        /* 按键松开 */
        input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0,松开 */
        input_sync(inputdev); /* 同步事件 */
    } 
}

参考一下上报的使用就可以了。

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

/* include/uapi/linux/input.h */
struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};
/*
type:事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。
code:事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0、KEY_1 等等这些按键。此成员变量为 16 位。
value:值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。
*/

/* time:时间,也就是此事件发生的时间 */
typedef long             __kernel_long_t;
typedef __kernel_long_t  __kernel_time_t;
typedef __kernel_long_t  __kernel_suseconds_t;
struct timeval {
    __kernel_time_t 
    tv_sec;                       /* 秒 */
    __kernel_suseconds_t tv_usec; /* 微秒 */
};

input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体
呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值。

触摸屏部分

电阻屏数据

驱动会上报触点的 X、Y 数据,这不是LCD的坐标值,需要APP再次处理才能转换为 LCD 坐标值。
对应的 input_event 结构体中,type、code、value 如下:

/* 按下时 */
EV_KEY    BTN_TOUCH        1        按下
EV_ABS    ABS_PRESSURE     1        压力值,可有可无
EV_ABS    ABS_X            x_value  X坐标
EV_ABS    ABS_Y            y_value  y坐标
EV_SYNC   0                0        同步事件
/* 松开时 */
EV_ABS    BTN_TOUCH        0        松开
EV_ABS    ABS_PRESSURE     0        压力值
EV_SYNC   0                0        同步事件  

电容屏数据

多点触摸协议(MT)

input子系统下的多点触摸协议称为MT协议,其文档为:Documentation/input/multitouch-protocol.txt。
MT协议被分为两种类型,取决于硬件的兼容性:
Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据
Type B:能分辨时哪一个触点,上报数据时会先上报触点ID,再上报它的数据。此类型设备都通过slot更新某一个触摸点的信息
Type A 已经过时了,内核也不支持了。
触摸点的信息通过一系列的ABS_MT事件上报给Linux内核,定义在文件 include/uapi/linux/input.h 中:

#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 */
#define ABS_MT_POSITION_Y     0x36    /* Center Y touch position */
#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 */
#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 */

其中最常用的是:

ABS_MT_SLOT:用来上报触摸点ID
ABS_MT_POSITION_X和ABS_MT_POSITION_Y:用来上报触摸点的 (X, Y) 坐标信息
ABS_MT_TRACKING_ID:对于Type B类型的设备,需要用该事件来区分触摸点

当有2个触点时,type、code、value 如下:

EV_ABS    ABS_MT_SLOT           0       表示要上报一个触点信息了
EV_ABS    ABS_MT_TRACKING_ID    45      这个触点的ID是45
EV_ABS    ABS_MT_POSITION_X     x[0]    触点X坐标
EV_ABS    ABS_MT_POSITION_Y     y[0]    触点Y坐标
EV_ABS    ABS_MT_SLOT           1       表示要上报一个触点信息了
EV_ABS    ABS_MT_TRACKING_ID    46      这个触点的ID是46
EV_ABS    ABS_MT_POSITION_X     x[1]    触点X坐标
EV_ABS    ABS_MT_POSITION_Y     y[1]    触点Y坐标
EV_SYNC   SYN_REPORT            0       全部数据上报完毕

当ID为45的触点正在移动:

EV_ABS    ABS_MT_SLOT           0       表示要上报一个触点信息了

/* 同一个slot不需要再上报一次ID*/

EV_ABS    ABS_MT_POSITION_X     x[0]    触点X坐标
EV_SYNC   SYN_REPORT            0       全部数据上报完毕

松开ID为45的触点时(前面slot已经设置为0,这里需要重新设置slot,slot就像一个全局变量,如果没有变化,就无需再次设置)

EV_ABS    ABS_MT_TRACKING_ID    -1
EV_SYNC   SYN_REPORT            0       全部数据上报完毕

最后,松开ID为46的触点:

EV_ABS    ABS_MT_SLOT           1       表示要上报一个触点信息了
/* 前面设置过slot 1的ID为46 */
EV_ABS    ABS_MT_TRACKING_ID    -1      -1表示slot 1 被松开,即ID为46的触点被松开
EV_SYNC   SYN_REPORT            0       全部数据上报完毕

执行 hexdump /dev/input/event0
再用一个手指触碰电容屏,得到这些数据:
在这里插入图片描述
可以看到一个手指的时候,并没有上报 slot
使用了两个手指时:
在这里插入图片描述
基本上都有兼容了老程序。

实战:基于tslib,不断打印2个触点的距离

tslib库的用处

tslib是一个触摸屏的开源库,可以使用它来访问触摸屏设备,可以给输入设备添加各种“filter”(过滤库,就是各种处理)。
tslib 框架分析、交叉编译tslib库、测试:
参考资料:https://blog.csdn.net/weixin_42373086/article/details/130138000
触摸屏可能支持多个触点,比如5个,tslib为了简化处理,即使只有两个触点,ts_read_mt函数也会返回五个触点的数据。
驱动程序中使用slot、tracing_id来标识一个触点,当tracing_id等于-1时,标识这个触点被松开了。
所以可以根据这个标识来判断数据是否有效,所以当有两个触点有效时,就打印它俩之间的距离。

数据结构

  • 核心函数:ts_read_mt
  • 四个参数:tsdev结构体、max_slots(最大点数)、ts_sample_mt结构体(存数据)、nr
int ts_read_mt(struct tsdev *ts, struct ts_sample_mt **samp, int max_slots, int nr)
{
    int result;
#ifdef DEBUG
    int i, j;
#endif
    result = ts->list->ops->read_mt(ts->list, samp, max_slots, nr);
#ifdef DEBUG
    for(i=0; i<max_slots; i++){
        if(!(samp[j][i].valid & TSLIB_MT_VALID))
            continue;
        ts_error("TS_READ_MT----> slot %d: x = %d, y = %d, pressure = %d\n",
                 samp[j][i].slot, samp[j][i].x, samp[j][i].y,
                 samp[j][i].pressure)
    }
#endif
    return result;
}
/*
    参数 ts :指向一个触摸屏设备句柄。
    参数samp:指向一个struct ts_sample_mt *对象。
    参数max_slots :表示触摸屏支持的最大触摸点数(可以通过调用 ioctl()函数获取到)。
    参数 nr :表示对一个触摸点的采样数。
*/
struct ts_sample_mt {
    /* ABS_MT_* event codes. linux/include/uapi/linux/input-event-codes.h
    * has the definitions.
    */
    int x; //X 坐标
    int y; //Y 坐标
    unsigned int pressure; //按压力大小
    int slot; //触摸点 slot
    int tracking_id; //ID
    int tool_type;
    int tool_x;
    int tool_y;
    unsigned int touch_major;
    unsigned int width_major;
    unsigned int touch_minor;
    unsigned int width_minor;
    int orientation;
    int distance;
    int blob_id;
    struct timeval tv; //时间
    /* BTN_TOUCH state */
    short pen_down; //BTN_TOUCH 的状态
    /* valid is set != 0 if this sample
    * contains new data; see below for the
    * bits that get set.
    * valid is set to 0 otherwise
    */
    short valid; //此次样本是否有效标志 触摸点数据是否发生更新
};

应用程序

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
 
#include <linux/input.h>
 
#include <sys/ioctl.h>
 
#include <tslib.h>
 
int distance(struct ts_sample_mt *point1, struct ts_sample_mt *point2)
{
    int x = point1->x - point2->x;
    int y = point1->y - point2->y;
 
    return x*x + y*y;
}
 
int main(int argc, char **argv)
{
    struct tsdev *ts;
    int i;
    int ret;
    //定义新旧触点sample结构体
    struct ts_sample_mt **samp_mt;
    struct ts_sample_mt **pre_samp_mt;
    int max_slots;
    int point_pressed[20];
    struct input_absinfo slot;
    int touch_cnt = 0;
 
    //阻塞方式
 ts = ts_setup(NULL, 0);    //寻找并初始化触摸屏设备
    if (!ts)
    {
        printf("ts_setup err\n");
        return -1;
    }
    
    //读取设备节点,获取属性---同时支持多少个触点,得到max_slots
    if (ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot) < 0) {
        perror("ioctl EVIOGABS");
        ts_close(ts);
        return errno;
    }
 
 max_slots = slot.maximum + 1 - slot.minimum;
 
    
    //参照测试程序初始samp_mt和pre_samp_mt结构体数组
 samp_mt = malloc(sizeof(struct ts_sample_mt *));
    if (!samp_mt) {
        ts_close(ts);
        return -ENOMEM;
    }
 samp_mt[0] = calloc(max_slots, sizeof(struct ts_sample_mt));
    if (!samp_mt[0]) {
        free(samp_mt);
        ts_close(ts);
        return -ENOMEM;
    }
 
 pre_samp_mt = malloc(sizeof(struct ts_sample_mt *));
    if (!pre_samp_mt) {
        ts_close(ts);
        return -ENOMEM;
    }
 pre_samp_mt[0] = calloc(max_slots, sizeof(struct ts_sample_mt));
    if (!pre_samp_mt[0]) {
        free(pre_samp_mt);
        ts_close(ts);
        return -ENOMEM;
    }
 
    for ( i = 0; i < max_slots; i++)
 pre_samp_mt[0][i].valid = 0;
 
    
 
    while (1)
    {
        //第一步:读取触点数据
        ret = ts_read_mt(ts, samp_mt, max_slots, 1);
        if (ret < 0) {
            printf("ts_read_mt err\n");
            ts_close(ts);
            return -1;
        }
 
        //第二步:判断是否更新,将新数据拷贝到旧数据里
        for (i = 0; i < max_slots; i++)
        {
            if (samp_mt[0][i].valid)

                memcpy(&pre_samp_mt[0][i], &samp_mt[0][i], sizeof(struct ts_sample_mt));

        }
 
        //第三步:判断是否有两个触点,如果是两个,则执行打印
        touch_cnt = 0;
        for (i = 0; i < max_slots; i++)
        {
            if (pre_samp_mt[0][i].valid && pre_samp_mt[0][i].tracking_id != -1)
                point_pressed[touch_cnt++] = i;
        }
 
        if (touch_cnt == 2)

            printf("distance: %08d\n", distance(&pre_samp_mt[0][point_pressed[0]], &pre_samp_mt[0][point_pressed[1]]));

    }

    return 0;
}

其他参考资料
https://blog.csdn.net/weixin_45499326/article/details/131130227
https://blog.csdn.net/weixin_44453694/article/details/126906896

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值