【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程

文章目录

  • 前言
  • 1、输入系统应用编程
    • 1.1、输入系统框架及调试
      • 1.1.1、框架概述
      • 1.1.2、编写APP需要的基础知识
    • 1.2、调试技巧
      • 1.2.1、查看设备信息
      • 1.2.2、使用命令查看节点数据
    • 1.3、 不使用库的应用程序示例
      • 1.3.1、APP访问硬件的四种方式
      • 1.3.2、获取设备信息
      • 1.3.3、查询方式
      • 1.3.4、POLL/SELECT方式
      • 1.3.5、异步通知方式
    • 1.4、电容屏和电阻屏
    • 1.5、tslib
      • 1.5.1、tslib框架分析
      • 1.5.2、交叉编译、测试tslib
      • 1.5.3、 写一个测试程序
  • 参考资料

前言

韦东山嵌入式Linux应用开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容

视频教程地址: https://www.bilibili.com/video/BV1kk4y117Tu

1、输入系统应用编程

1.1、输入系统框架及调试

1.1.1、框架概述

图片来自:嵌入式Linux应用开发完全手册V4.0_韦东山全系列视频文档-STM32MP157开发板
【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程_第1张图片

▲输入系统框架

  假设用户程序直接访问/dev/input/event0设备节点,或者使用tslib访问设备节点,数据的流程如下:
① APP发起读操作,若无数据则休眠;
② 用户操作设备,硬件上产生中断;
③ 输入系统驱动层对应的驱动程序处理中断:
  读取到数据,转换为标准的输入事件,向核心层汇报。
  所谓输入事件就是一个“struct input_event”结构体。
④ 核心层可以决定把输入事件转发给上面哪个handler来处理:
  从handler的名字来看,它就是用来处输入操作的。有多种handler,比如: evdev_handler、kbd_handler、joydev_handler等等。
  最常用的是evdev_handler:它只是把input_event结构体保存在内核buffer等,APP来读取时就原原本本地返回。它支持多个APP同时访问输入设备,每个APP都可以获得同一份输入事件。
  当APP正在等待数据时,evdev_handler会把它唤醒,这样APP就可以返回数据。
⑤ APP对输入事件的处理:
  APP获得数据的方法有2种:直接访问设备节点(比如/dev/input/event0,1,2,…),或者通过tslib、libinput这类库来间接访问设备节点。这些库简化了对数据的处理。

1.1.2、编写APP需要的基础知识

内核中如何表示一个输入设备?
include/linux/input.h

	struct input_dev {
	const char *name;
	const char *phys;
	const char *uniq;
	struct input_id id;

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];										//支持哪类事件? key/real/abs ?
	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)];
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

APP可以获得什么数据?
APP可以得到一系列的输入事件,这些事件使用的一个一个的struct input_event来表示,定义如下
include/uapi/linux/input.h

struct input_event {
	struct timeval time;
	__u16 type;							//事件类型
	__u16 code;							//事件
	__s32 value;							//事件值
};

三者辨析详见【Linux】Linux input子系统之Input event codes

include/uapi/linux/time.h

struct timeval {									//自系统启动以来过了多少时间
	__kernel_time_t		tv_sec;		/* seconds */
	__kernel_suseconds_t	tv_usec;	/* microseconds */
};

这里贴出一些事件类型和事件
include/uapi/linux/input-event-code.h事件类型

/*
 * 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)

include/uapi/linux/input-event-code.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 KEY_MINUS		12
#define KEY_EQUAL		13
#define KEY_BACKSPACE		14
#define KEY_TAB			15

对于触摸屏而言的,APP需要的信息有绝对位置信息,有X方向、Y方向,还有压力值,所以code的值有这些:

/*
 * Absolute axes
 */

#define ABS_X			0x00
#define ABS_Y			0x01
#define ABS_Z			0x02

这些事件对应的value就是其坐标值、压力值
APP怎么知道它已经读到了完整的数据?
  驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。APP读到“同步事件”时,就知道已经读完了当前的数据
  同步事件也是一个input_event结构体,其type、code、value三项的值都是0

输入子系统支持完整的API操作

【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程_第2张图片

▲APP访问硬件的四种方式

1.2、调试技巧

1.2.1、查看设备信息

查看设备节点
ls /dev/input/* -l
在这里插入图片描述

▲ls /dev/input/* -l

查看设备节点对应的硬件

[root@100ask:~]# cat /proc/bus/input/devices
I: Bus=0018 Vendor=0416 Product=038f Version=1060													//设备ID
N: Name="Goodix Capacitive TouchScreen"																		//设备名称
P: Phys=input/ts																																//系统参差结构中设备的物理路径
S: Sysfs=/devices/platform/soc/5c002000.i2c/i2c-2/2-005d/input/input0			//位于sys文件系统的路径
U: Uniq=																																				//设备的唯一标识码
H: Handlers=kbd event0 																												//与设备关联的输入句柄列表
B: PROP=2																																			//位图:设备属性
B: EV=b																																				//位图:设备支持的事件类型
B: KEY=400 0 0 0 0 0 0 20000000 0 0 0																						//位图:此设备具有的键/按钮
B: ABS=2658000 3																															//位图:此设备支持EV_ABS类型事件的事件组合

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="joystick"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/platform/joystick/input/input1
U: Uniq=
H: Handlers=kbd event1 
B: PROP=0
B: EV=3
B: KEY=50000000

“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,对应以下这些宏:
include/uapi/linux/input-event-codes.h

#define ABS_X			0x00
#define ABS_Y			0x01

#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 */

  即这款输入设备支持上述的ABS_X、ABS_Y、ABS_MT_SLOT、ABS_MT_TOUCH_MAJOR、ABS_MT_WIDTH_MAJOR、ABS_MT_POSITION_X、ABS_MT_POSITION_Y这些绝对位置事件(具体含义见:屏幕&屏幕事件)

1.2.2、使用命令查看节点数据

[root@100ask:~]# hexdump /dev/input/event0
0000000 8da7 5e3d d9f5 0005 0003 0039 0002 0000
0000010 8da7 5e3d d9f5 0005 0003 0035 03aa 0000
0000020 8da7 5e3d d9f5 0005 0003 0036 0163 0000
0000030 8da7 5e3d d9f5 0005 0003 0030 0012 0000
0000040 8da7 5e3d d9f5 0005 0003 0032 0012 0000
0000050 8da7 5e3d d9f5 0005 0001 014a 0001 0000
0000060 8da7 5e3d d9f5 0005 0003 0000 03aa 0000
0000070 8da7 5e3d d9f5 0005 0003 0001 0163 0000
0000080 8da7 5e3d d9f5 0005 0000 0000 0000 0000
0000090 8da7 5e3d 828c 0006 0003 0039 ffff ffff
00000a0 8da7 5e3d 828c 0006 0001 014a 0000 0000
00000b0 8da7 5e3d 828c 0006 0000 0000 0000 0000

00000b0|8da7 5e3d|828c 0006|0000|0000|0000 0000|
  序号				秒				微秒	   type  code      value

1.3、 不使用库的应用程序示例

1.3.1、APP访问硬件的四种方式

此图请配合后面的程序食用
【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程_第3张图片

▲APP访问硬件的四种方式

1.3.2、获取设备信息

int ioctl(int fd, unsigned long request, ...);
   有些驱动程序对request的格式有要求,它的格式如下:
include/uapi/asm-generic/ioctl.h

#define _IOC(dir,type,nr,size) \
	(((dir)  << _IOC_DIRSHIFT) | \				//bit29		//为_IOC_READ(即2)时,表示APP要读数据;为_IOC_WRITE(即4)时,表示APP要写数据
	 ((type) << _IOC_TYPESHIFT) | \			//bit8		//含义由具体的驱动程序决定
	 ((nr)   << _IOC_NRSHIFT) | \				//bit0		//含义由具体的驱动程序决定
	 ((size) << _IOC_SIZESHIFT))				//bit16		//表示这个ioctl能传输数据的最大字节数	

   比如要读取输入设备的evbit即支持哪些事件时,ioctl的request要写为“EVIOCGBIT(0, size)”,size的大小可以由你决定:你想读多少字节就设置为多少。这个宏的定义如下:
在这里插入图片描述

▲EVIOCGBIT

部分程序分析
01_get_input_info.c:使用./01_get_input_info 来输出设备支持的事件类型于屏幕上

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 \n", argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}
	//获取fd设备节点的id信息
	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 );
	}

	//获取fd设备节点的evbit信息
	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		//根据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;
}

1.3.3、查询方式

02_input_read.c:使用./02_input_read /dev/input/event0 [noblock]阻塞(查询方式)或非阻塞方式(休眠-唤醒方式)读取event、type、code、value

/* ./02_input_read /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  [noblock]\n", argv[0]);
		return -1;
	}

	if (argc == 3 && !strcmp(argv[2], "noblock"))//判断是否传入参数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));	//如果使用阻塞方式未读取到信息内核将处于休眠态
												//如果使用非阻塞方式且未读取到信息将进入else分支
		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;
}

1.3.4、POLL/SELECT方式

使用poll函数监测多个输入设备
03_input_read_poll .c使用./03_input_read_poll /dev/input/event0 /dev/input/event1监测多个输入设备

/* ./03_input_read_poll /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;
	
	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 ",
		};
	/*输入参数解析和设备ID信息获取输出*/
	if (argc < 2)
	{
		printf("Usage: %s \n", argv[0]);
		return -1;
	}
	int num_devices = argc - 1;
	int fd[num_devices];
	struct pollfd fds[num_devices];
	nfds_t nfds = num_devices;//poll设备数量
	
	//遍历输出各节点的input_id和ev_bit信息
	for(int i = 0; i < num_devices; i ++)
	{
		fd[i] = open(argv[i+1], O_RDWR | O_NONBLOCK);	
		if (fd[i] < 0)
		{
			printf("open %s err\n", argv[i]);
			return -1;
		}
		err = ioctl(fd[i], EVIOCGID, &id);
		if (err == 0)
		{
			printf("dev:%s\n", argv[i+1] );		
			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[i], EVIOCGBIT(0, sizeof(evbit)), &evbit);
		if (len > 0 && len <= sizeof(evbit))
		{
			printf("support ev type: ");
			for (int j = 0; j < len; j++)
			{
				byte = ((unsigned char *)evbit)[j];
				for (bit = 0; bit < 8; bit++)
				{
					if (byte & (1<<bit)) {
						printf("%s ", ev_names[j*8 + bit]);
					}
				}
			}
			printf("\n");
		}
		printf("\n");
	}

	/*使用poll函数完成多个设备节点信息实时读取*/
	while (1)
	{
		//poll所有的设备
		for(int i = 0; i < num_devices; i ++)
		{
			fds[i].fd = fd[i];
			fds[i].events  = POLLIN;
			fds[i].revents = 0;
			ret = poll(fds, nfds, 100);
		}

		if (ret > 0)
		{
			//输出revents == POLLIN的设备的input_event
			for(int i = 0; i < num_devices; i++)
			{
				if (fds[i].revents == POLLIN)
				{
					while (read(fd[i], &event, sizeof(event)) == sizeof(event))
					{
						printf("get event:dev:%s type = 0x%x, code = 0x%x, value = 0x%x\n", argv[i+1], event.type, event.code, event.value);
					}
				}
			}
		}
		else if (ret == 0)
		{
			//printf("time out\n");
		}
		else
		{
			printf("poll err\n");
		}
		
	}

	return 0;
}

使用select函数监测多个输入设备

/* ./04_input_read /dev/input/event0 */
int main(int argc, char **argv)
{
	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 timeval tv;
	int nfds;
	fd_set readfds;
	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 ",
		};
	
	/*输入参数解析和设备ID信息获取输出*/
	if (argc < 2)
	{
		printf("Usage: %s \n", argv[0]);
		return -1;
	}
	int num_devices = argc - 1;
	int fd[num_devices];
	
	//遍历输出各节点的input_id和ev_bit信息
	for(int i = 0; i < num_devices; i ++)
	{
		fd[i] = open(argv[i+1], O_RDWR | O_NONBLOCK);	
		if (fd[i] < 0)
		{
			printf("open %s err\n", argv[i]);
			return -1;
		}
		err = ioctl(fd[i], EVIOCGID, &id);
		if (err == 0)
		{
			printf("dev:%s\n", argv[i+1] );		
			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[i], EVIOCGBIT(0, sizeof(evbit)), &evbit);
		if (len > 0 && len <= sizeof(evbit))
		{
			printf("support ev type: ");
			for (int j = 0; j < len; j++)
			{
				byte = ((unsigned char *)evbit)[j];
				for (bit = 0; bit < 8; bit++)
				{
					if (byte & (1<<bit)) {
						printf("%s ", ev_names[j*8 + bit]);
					}
				}
			}
			printf("\n");
		}
		printf("\n");
	}
	

	while (1)
	{
		/* 设置超时时间 */
		tv.tv_sec  = 0;
		tv.tv_usec = 100;
		
		/* 想监测哪些文件? */
		FD_ZERO(&readfds);    /* 先全部清零 */	
		for(int i = 0; i < num_devices; i ++)
		{
			FD_SET(fd[i], &readfds); /* 想监测fd */
		}

		
		/* 函数原型为:
			int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
         * 我们为了"read"而监测, 所以只需要提供readfds
		 */
		int fd_max = 0;
		for(int i = 0; i < sizeof(fd)/sizeof(int); i ++)
		{
			if(fd[i] > fd_max){
				fd_max = fd[i];
			}
		}
		nfds = fd_max + 1; /* nfds 是最大的文件句柄+1, 注意: 不是文件个数, 这与poll不一样 */ 
		ret = select(nfds, &readfds, NULL, NULL, &tv);
		if (ret > 0)  /* 有文件可以提供数据了 */
		{
			
			for(int i = 0; i < num_devices; i ++)
			{
				/* 再次确认fd有数据 */
				if (FD_ISSET(fd[i], &readfds))
				{
					while (read(fd[i], &event, sizeof(event)) == sizeof(event))
					{
						printf("get event: dev:%s type = 0x%x, code = 0x%x, value = 0x%x\n", argv[i+1], event.type, event.code, event.value);
					}
				}
			}
			
		}
		else if (ret == 0)  /* 超时 */
		{
			//printf("time out\n");
		}
		else   /* -1: error */
		{
			printf("select err\n");
		}
	}

	return 0;
}

1.3.5、异步通知方式

05_input_read_fasync.c:使用05_input_read_fasync /dev/input/event0 实现异步接收event0节点的信息

int fd;
/* ./05_input_read_fasync /dev/input/event0 */
int main(int argc, char **argv)
{
	int err;
	int len;
	int ret;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	unsigned int flags;
	int count = 0;
	
	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 \n", argv[0]);
		return -1;
	}

	/* 注册信号处理函数 */
	signal(SIGIO, my_sig_handler);
	
	/* 打开驱动程序 */
	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");
	}

	/* 把APP的进程号告诉驱动程序 */
	fcntl(fd, F_SETOWN, getpid());
	
	/* 使能"异步通知" */
	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC);
	
	while (1)
	{
		printf("main loop count = %d\n", count++);
		sleep(2);
	}

	return 0;
}

void my_sig_handler(int sig)函数:信号处理函数(可以理解其作用为中断服务函数)

void my_sig_handler(int sig)
{
	struct input_event event;
	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);		
	}
}

函数fcntl
  定义:
  int fcntl(int fd, int cmd);
  int fcntl(int fd, int cmd, long arg);
  int fcntl(int fd, int cmd, struct flock * lock);
fcntl有5种功能:
  1.复制一个现有的描述符。(cmd = F_DUPFD)
  2.获取/设置文件描述符标记。(cmd = F_GETFD 或 F_GETFD)
  3.获取/设置文件状态标记。(cmd = F_GETFL 或 F_SETFL)
  4.获取/设置异步I/O所有权。(cmd = F_GETOWN 或 F_SETOWN)
  5.获取/设置记录锁。(cmd = F_GETLK,F_SETLK或F_SETLKW)
这里介绍使用到的3和4功能:
  设置异步I/O所有权:
  F_SETOWN:设置接受SIGIO和SIGURG信号的进程ID和进程组ID。正的arg指定一个进程ID,负的arg表示等于arg绝对值的一个进程组ID。
  获取/设置文件状态标记:
  F_GETFL:获取文件的状态标志作为函数的返回值。这边状态标志就是open函数中的状态。不幸的是,三个访问标志位(O_RDONLY,O_WRONLY和O_RDWR)并不各占一位(这3种标志的值分别是0,1,2,由于历史原因,这三种值是互斥),因此首先必须用屏蔽字O_ACCMODE获取访问模式位,然后将结果与这三种值中的任一种做比较。
  F_SETFL:将文件状态设置为第三个参数的值。可以更改的几个标志位是:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC,O_ASYNC(FASYNC)。

1.4、电容屏和电阻屏

【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程_第4张图片

▲屏幕&屏幕事件

注:导图中的例子不是使用开发板得到的数据,而是使用安卓手机得到的数据,这里仅仅作为举例使用
补充:
ABS_MT_TOUCH_MAJOR:触点大小
ABS_MT_WIDTH_MAJOR:手指大小

1.5、tslib

   tslib是一个触摸屏的开源库,可以使用它来访问触摸屏设备,可以给输入设备添加各种“filter”(过滤器,就是各种处理),地址是:http://www.tslib.org/。
   编译tslib后,可以得到libts库,还可以得到各种工具:较准工具、测试工具。

1.5.1、tslib框架分析

tslib的主要代码如下:

src/ 接口函数
	ts_setup.c
	ts_open.c
	ts_config.c
plugins/	插件/module
	linear.c
	dejitter.c
	pthres.c
	input-raw.c
tests/ 测试程序
	ts_test.c
	ts_test_mt.c
	ts_print.c
	ts_print_mt.c

  核心在于“plugins”目录里的“插件”,或称为“module”。这个目录下的每个文件都是一个module,每个module都提供2个函数read、read_mt,前者用于读取单点触摸屏的数据,后者用于读取多点触摸屏的数据。
  可以参考ts_test.c和ts_test_mt.c来分析tslib的框架,前者用于一般触摸屏(比如电阻屏、单点电容屏),后者用于多点触摸屏。
  下面是韦东山老师给出的流程图,用来介绍tslib的框架:
【嵌入式Linux】嵌入式Linux应用开发基础知识之输入系统应用编程_第5张图片

▲tslib的框架流程图

5、关于tsdev

struct tsdev {
	int fd;
	char *eventpath;
	struct tslib_module_info *									//这个指针指向linear模块,用来通过ts_read/ts_read_mt函数完成模块的层层调用,达到逐步处理数据的目的
	struct tslib_module_info *list_raw;				//这个指针指向读取原始数据的input模块
	unsigned int res_x;
	unsigned int res_y;
	int rotation;
};

  因为是递归调用,所以尽管指针最先指向linear模块但其实是最先使用input模块读取设备节点得到原始数据,再依次经过pthres模块、dejitter模块、linear模块处理后,才返回最终数据。

1.5.2、交叉编译、测试tslib

本节主要内容请直接参考韦东山老师的教程

//记录一些遇到的不熟悉的命令
echo 'main(){}'| arm-buildroot-linux-gnueabihf-gcc -E -v -
-v                       显示编译器调用的程序
-E                       仅作预处理,不进行编译、汇编或链接
使用内建 specs。
COLLECT_GCC=/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/arm-buildroot-linux-gnueabihf-gcc.br_real
目标:arm-buildroot-linux-gnueabihf
配置为:./configure --prefix=/home/book/stm32mp157/ST-Buildroot/output/host --sysconfdir=/home/book/stm32mp157/ST-Buildroot/output/host/etc --enable-static --target=arm-buildroot-linux-gnueabihf --with-sysroot=/home/book/stm32mp157/ST-Buildroot/output/host/arm-buildroot-linux-gnueabihf/sysroot --enable-__cxa_atexit --with-gnu-ld --disable-libssp --disable-multilib --disable-decimal-float --with-gmp=/home/book/stm32mp157/ST-Buildroot/output/host --with-mpc=/home/book/stm32mp157/ST-Buildroot/output/host --with-mpfr=/home/book/stm32mp157/ST-Buildroot/output/host --with-pkgversion='Buildroot 2020.02-g6515f51-dirty' --with-bugurl=http://bugs.buildroot.net/ --disable-libquadmath --enable-tls --enable-plugins --enable-lto --enable-threads --with-isl=/home/book/stm32mp157/ST-Buildroot/output/host --with-abi=aapcs-linux --with-cpu=cortex-a7 --with-fpu=vfpv4-d16 --with-float=hard --with-mode=arm --enable-languages=c,c++,fortran --with-build-time-tools=/home/book/stm32mp157/ST-Buildroot/output/host/arm-buildroot-linux-gnueabihf/bin --enable-shared --enable-libgomp
线程模型:posix
gcc 版本 8.4.0 (Buildroot 2020.02-g6515f51-dirty) 
COLLECT_GCC_OPTIONS='-E' '-v' '-mcpu=cortex-a7' '-mfloat-abi=hard' '-mfpu=vfpv4-d16' '-mabi=aapcs-linux' '-marm' '-mtls-dialect=gnu' '-march=armv7ve+fp'
 /home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../libexec/gcc/arm-buildroot-linux-gnueabihf/8.4.0/cc1 -E -quiet -v -iprefix /home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/ -isysroot /home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot - -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=vfpv4-d16 -mabi=aapcs-linux -marm -mtls-dialect=gnu -march=armv7ve+fp
忽略重复的目录“/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/../../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/include”
忽略不存在的目录“/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/local/include”
忽略重复的目录“/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/../../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/include-fixed”
忽略重复的目录“/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/../../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/../../../../arm-buildroot-linux-gnueabihf/include”
#include "..." 搜索从这里开始:
#include <...> 搜索从这里开始:
 /home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/include
 /home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/include-fixed
 /home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/../../../../arm-buildroot-linux-gnueabihf/include
 /home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include
搜索列表结束。
# 1 ""
# 1 ""
# 1 "<命令行>"
# 31 "<命令行>"
# 1 "/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include/stdc-predef.h" 1 3 4
# 32 "<命令行>" 2
# 1 ""
main(){}
COMPILER_PATH=/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../libexec/gcc/arm-buildroot-linux-gnueabihf/8.4.0/:/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../libexec/gcc/:/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/../../../../arm-buildroot-linux-gnueabihf/bin/
LIBRARY_PATH=/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/:/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/:/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin/../lib/gcc/arm-buildroot-linux-gnueabihf/8.4.0/../../../../arm-buildroot-linux-gnueabihf/lib/:/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/lib/:/home/mi/100ask/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/
COLLECT_GCC_OPTIONS='-E' '-v' '-mcpu=cortex-a7' '-mfloat-abi=hard' '-mfpu=vfpv4-d16' '-mabi=aapcs-linux' '-marm' '-mtls-dialect=gnu' '-march=armv7ve+fp'

1.5.3、 写一个测试程序

mt_cal_distance.c:当屏幕上存在两个触点时测量两个触点之间的距离

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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;
	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;					//当前屏幕上的触点数量

	//NULL:文件名 0:非阻塞方式 
	ts = ts_setup(NULL, 0);
	if (!ts)
	{
		printf("ts_setup err\n");
		return -1;
	}

	//从tsdev类型设备文件ts->fd中获得设备信息存放在slot中
	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[0]拥有地址,
		以便可以在这块地址指向的内存中保存struct ts_sample_mt类型大小内存的地址
	*/
	samp_mt = malloc(sizeof(struct ts_sample_mt *));
	if (!samp_mt) {
		ts_close(ts);
		return -ENOMEM;
	}

	/*
		申请max_slots数量的struct ts_sample_mt大小的内存给samp_mt[0],
		现在samp_mt[0]中保存的是指向一块内存的首地址,
		这块内存用来保存max_slots数量的struct ts_sample_mt
	*/
	samp_mt[0] = calloc(max_slots, sizeof(struct ts_sample_mt));
	if (!samp_mt[0]) {
		free(samp_mt);
		ts_close(ts);
		return -ENOMEM;
	}

	//操作同samp_mt
	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;
	}

	//初始化pre_samp_mt.valid
	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)//!=0 表示该触点数据已更新
			{
				//更新samp_mt到pre_samp_mt数据
				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;
}

参考资料

《UNIX环境高级编程》笔记–fcntl函数

你可能感兴趣的:(#,嵌入式Linux,linux,stm32mp157,嵌入式Linux应用开发)