目录
前言
直接上图:(图片来源于正点原子笔记)
(输入子系统对触摸屏狠重要,会用 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