基于触摸屏的 输入系统应用分析

有时候想给输入系统的驱动写个应用也会发现无从下手,今天就对触摸屏写应用进行分析

输入系统调用流程

调用过程

有各种输入设备,就拿触摸屏举例,各种触摸屏经过驱动适配后,会给上层提供一个统一的接口
就便于app直接调用
在这里插入图片描述
1.开始运行app open设备节点,暂时没有数据,app进行休眠
2.按下屏幕产生中断,驱动直接获得数据,变成标准的输入事件,发送给输入核心层
3.核心层把数据变成标准格式,传递给事件层
4.事件层唤醒app,把之前标准格式的数据发送给app

传输的数据格式

根据上面所说,在核心层把各种设备传递数据变成统一的格式
把整理过的数据,在交给app处理----> 所以是什么样格式的数据

对于驱动

在驱动里面我们通常用一个 input_dev 结构体来表示输入设备
我们在 include/linux/input.h
下面的数组表示一个大类的事件,比如绝对位移事件里面的会分为 x位移,y位移,z位移等
用这个数组表明这些驱动支持什么事件
在这里插入图片描述

app接收到的转换后数据的格式

得到的是一个个输入事件,也就是一个个 struct input_event 结构体
我们在 /include/uapi/linux/input.h
在这里插入图片描述
我们在该文件上 找到 #include “input-event-codes.h” ,所以这些事件值的定义就在这个头文件里

type 表示对应的是哪个事件

比如 EV_KEY 表示按键类、EV_REL 表示相对位移(比如鼠标),EV_ABS 表示绝对位置(比如触摸屏)

/*
 * 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:表示该类事件下的哪一个事件

比如对于 EV_KEY(按键)类事件,它表示键盘。键盘上有很多按键,比如数字键 1、2、3,字母键 A、B、 C 里等
在这里插入图片描述

value:表示事件值

对于按键,它的 value 可以是 0(表示按键被按下)、1(表示按键被松开)、2(表示长按);
对于触摸屏,它的 value 就是坐标值、压力值。

最后同步

当上报完成的时候,驱动会上报一个同步事件,表明自己已经上报完成
同步事件也是一个 input_event 结构体,它的 type、code、value 三项都是 0。

查看一个驱动对app发送的信息

cat /proc/bus/input/devices
驱动信息对照

查看这个驱动对应的信息,方便我们app编写

在这里插入图片描述
最左边的意思

I:id of the device(设备 ID)

该参数由结构体 struct input_id 来进行描述,驱动程序中会定义这样的结构体:在这里插入图片描述

N:name of the device

设备名称

P:physical path to the device in the system hierarchy

系统层次结构中设备的物理路径。

S:sysfs path

位于 sys 文件系统的路径

U:unique identification code for the device(if device has it)

设备的唯一标识码

H:list of input handles associated with the device.

与设备关联的输入句柄列表。
比如 H: Handlers=kbd event1 evbug
对应的设备节点就是 /dev/input/event1

B:bitmaps(位图)

PROP:device properties and quirks(设备属性)
EV:types of events supported by the device(设备支持的事件类型)
KEY:keys/buttons this device has(此设备具有的键/按钮)
MSC:miscellaneous events supported by the device(设备支持的其他事件)
LED:leds present on the device(设备上的指示灯)

EV 表示支持什么样的输入事件
在这里插入图片描述

从图上看EV==b 翻译过b的二进制是0x1011 也就是0,1,3位为1,支持下表里面的
0,1,3事件 —> EV_SYN,EV_KEY,EV_ABS事件
在这里插入图片描述

B: ABS=2658000 3
它表示该设备支持 EV_ABS 这一类事件中的哪一些事件。这是 2 个 32 位的数字:0x2658000、0x3,高位
在前低位在后,组成一个 64 位的数字:“0x2658000,00000003”,数值为 1 的位有:0、1、47、48、50、53、 54,即:0、1、0x2f、0x30、0x32、0x35、0x36
在这里插入图片描述

app接收的数据格式

以触摸屏收到的数据为例子
在这里插入图片描述
上图中的 type 为 3,对应 EV_ABS;code 为 0x35 对应 ABS_MT_POSITION_X;code 为 0x36 对应
ABS_MT_POSITION_Y。
上图中还发现有 2 个同步事件:它的 type、code、value 都为 0。表示电容屏上报了 2 次完整的数据。

app编写

从上面的分析开始,现在可以写一个app来获取信息了

app读取设参数和信息

想操控一个输入系统驱动 肯定要用到 ioctl 函数 所以具体要传入什么信息呢
一般我们用到的是 evdev.c 这个文件,在里面寻找ioctl函数调用方法
在这里插入图片描述

//把函数截取一部分
//根据这个函数,我们就知道ioctl函数要传入的参数是什么
//比如cmd传入EVIOCGID,和一个struct input_id,input_id就能收到参数
static long evdev_do_ioctl(struct file *file, unsigned int cmd,
			   void __user *p, int compat_mode)
{
	struct evdev_client *client = file->private_data;
	struct evdev *evdev = client->evdev;
	struct input_dev *dev = evdev->handle.dev;
	struct input_absinfo abs;
	struct input_mask mask;
	struct ff_effect effect;
	int __user *ip = (int __user *)p;
	unsigned int i, t, u, v;
	unsigned int size;
	int error;

	/* First we check for fixed-length commands */
	switch (cmd) {

	case EVIOCGVERSION:
		return put_user(EV_VERSION, ip);

	case EVIOCGID:
		if (copy_to_user(p, &dev->id, sizeof(struct input_id)))
			return -EFAULT;
		return 0;

	case EVIOCGREP:
		if (!test_bit(EV_REP, dev->evbit))
			return -ENOSYS;
		if (put_user(dev->rep[REP_DELAY], ip))
			return -EFAULT;
		if (put_user(dev->rep[REP_PERIOD], ip + 1))
			return -EFAULT;
		return 0;

APP非阻塞(NO_BLOCK)方式读取数据(查询)

APP 调用 open 函数时,传入“O_NONBLOCK”表示“非阻塞”。 APP 调用 read 函数读取数据时,如果驱动程序中有数据,那么 APP 的 read 函数会返回数据,否则也会
立刻返回错误。

APP阻塞方式读取数据(休眠-唤醒方式)

APP 调用 open 函数时,不要传入“O_NONBLOCK”。
APP 调用 read 函数读取数据时,如果驱动程序中有数据,那么 APP 的 read 函数会返回数据;否则 APP
就会在内核态休眠,当有数据时驱动程序会把 APP 唤醒,read 函数恢复执行并返回数据给 APP。

APP的 POLL/SELECT 方式

简单地说,它们就是“定个闹钟”:在调用 poll、select 函数时可以传入“超时时间”。在这段时间内,
条件合适时(比如有数据可读、有空间可写)就会立刻返回,否则等到“超时时间”结束时返回错误。
APP 先调用 open 函数时。
APP 不是直接调用 read 函数,而是先调用 poll 或 select 函数,这 2 个函数中可以传入“超时时间”。
它们的作用是:如果驱动程序中有数据,则立刻返回;否则就休眠。在休眠期间,如果有人操作了硬件,驱
动程序获得数据后就会把 APP 唤醒,导致 poll 或 select 立刻返回;如果在“超时时间”内无人操作硬件,
则时间到后 poll 或 select 函数也会返回。APP 可以根据函数的返回值判断返回原因:有数据?无数据超时
返回?
APP 根据 poll 或 select 的返回值判断有数据之后,就调用 read 函数读取数据时,这时就会立刻获得
数据

APP的异步通知

所谓同步,就是“你慢我等你”。
那么异步就是:你慢那你就自己玩,我做自己的事去了,有情况再通知我。
所谓异步通知,就是 APP 可以忙自己的事,当驱动程序用数据时它会主动给 APP 发信号,这会导致 APP
执行信号处理函数。
APP处理流程是
① 谁发:驱动程序发
② 发什么:信号
③ 发什么信号:SIGIO 表示有驱动的io消息信号
④ 怎么发:内核里提供有函数
⑤ 发给谁:APP,APP 要把自己告诉驱动
⑥ APP 收到后做什么:执行信号处理函数
⑦ 信号处理函数和信号,之间怎么挂钩:APP 注册信号处理函数
想知道获得到什么信号 —> 在signal.h 里面进行分析
在这里插入图片描述
驱动程序通知 APP 时,它会发出“SIGIO”这个信号,表示有“IO 事件”要处理。
就 APP 而言,你想处理 SIGIO 信息,那么需要提供信号处理函数,并且要跟 SIGIO 挂钩。这可以通过一
个 signal 函数来“给某个信号注册处理函数”,用法如下:

#include <signal.h>
typedef void (*sighandle_t)(int); //handle处理函数
sighandler_t signal(int signum,sighandler_t handler)
				 // 哪一个信号   信号处理函数

除了注册 SIGIO 的处理函数,APP 还要做什么事?想想这几个问题:
① 内核里有那么多驱动,你想让哪一个驱动给你发 SIGIO 信号?
APP 要打开驱动程序的设备节点。
② 驱动程序怎么知道要发信号给你而不是别人?
APP 要把自己的进程 ID 告诉驱动程序。
③ APP 有时候想收到信号,有时候又不想收到信号:
应该可以把 APP 的意愿告诉驱动:设置 Flag 里面的 FASYNC 位为 1,使能“异步通知”

app编写

app–>ioctl 读取输入设备的数据

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>

/* ./01_get_input_info /dev/input/event0 */
int main(int argc, char **argv)
{
	int fd;
	int err;
	int len;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	char *ev_names[] = {
		"EV_SYN ",
		"EV_KEY ",
		"EV_REL ",
		"EV_ABS ",
		"EV_MSC ",
		"EV_SW	",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"EV_LED ",
		"EV_SND ",
		"NULL ",
		"EV_REP ",
		"EV_FF	",
		"EV_PWR ",
		};
	
	if (argc != 2)
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}

	err = ioctl(fd, EVIOCGID, &id);
	if (err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}

	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}

	return 0;
}


app的阻塞和非阻塞编写

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


/* ./01_get_input_info /dev/input/event0 noblock */
int main(int argc, char **argv)
{
	int fd;
	int err;
	int len;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	struct input_event event;
	
	char *ev_names[] = {
		"EV_SYN ",
		"EV_KEY ",
		"EV_REL ",
		"EV_ABS ",
		"EV_MSC ",
		"EV_SW	",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"EV_LED ",
		"EV_SND ",
		"NULL ",
		"EV_REP ",
		"EV_FF	",
		"EV_PWR ",
		};
	
	if (argc < 2)
	{
		printf("Usage: %s <dev> [noblock]\n", argv[0]);
		return -1;
	}

	if (argc == 3 && !strcmp(argv[2], "noblock"))
	{
		fd = open(argv[1], O_RDWR | O_NONBLOCK);
	}
	else
	{
		fd = open(argv[1], O_RDWR);
	}
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}

	err = ioctl(fd, EVIOCGID, &id);
	if (err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}

	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}

	while (1)
	{
		len = read(fd, &event, sizeof(event));
		if (len == sizeof(event))
		{
			printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
		}
		else
		{
			printf("read err %d\n", len);
		}
	}

	return 0;
}


app异步编写

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


/* ./01_get_input_info /dev/input/event0 */
int main(int argc, char **argv)
{
	int fd;
	int err;
	int len;
	int ret;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	struct input_event event;
	struct pollfd fds[1];  //设置poll数组的大小(控制几个设备?)
	nfds_t nfds = 1; //再次表明设备的个数
	
	char *ev_names[] = {
		"EV_SYN ",
		"EV_KEY ",
		"EV_REL ",
		"EV_ABS ",
		"EV_MSC ",
		"EV_SW	",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"EV_LED ",
		"EV_SND ",
		"NULL ",
		"EV_REP ",
		"EV_FF	",
		"EV_PWR ",
		};
	
	if (argc != 2)
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}

	err = ioctl(fd, EVIOCGID, &id);
	if (err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}

	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}

	while (1)
	{
		fds[0].fd = fd; //监测的文件
		fds[0].events  = POLLIN; //触发的事件
		fds[0].revents = 0;   //清零
		ret = poll(fds, nfds, 5000);
		if (ret > 0)
		{
			if (fds[0].revents == POLLIN)
			{
				while (read(fd, &event, sizeof(event)) == sizeof(event))
				{
					printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
				}
			}
		}
		else if (ret == 0)
		{
			printf("time out\n");
		}
		else
		{
			printf("poll err\n");
		}
	}

	return 0;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值