Linux 多点电容触摸屏实验

这篇具有很好参考价值的文章主要介绍了Linux 多点电容触摸屏实验。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、 Linux 下电容触摸屏驱动框架简介

1、多点触摸(MT)协议详解

电容触摸屏驱动其实就是以下几种 linux 驱动框架的组合:
①、IIC 设备驱动,因为电容触摸 IC 基本都是 IIC 接口的,因此大框架就是 IIC 设备驱动。
②、通过中断引脚(INT)向 linux 内核上报触摸信息,因此需要用到 linux 中断驱动框架。坐标的上报在中断服务函数中完成。
③、触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux 的 input 子系统,因此向 linux 内核上报触摸屏坐标信息就得使用 input 子系统。只是,我们得按照 linux 内核规定的规则来上报坐标信息。

MT(Multi-touch,简称 MT) 协议被分为两种类型,TypeA 和 TypeB,这两种类型的区别如下:
TypeA:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据(此类型在实际使用中非常少!)。
Type B:适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot 更新某一个触摸点的信息,FT5426 就属于此类型,一般的多点电容触摸屏 IC 都有此能力。

ABS_MT 事件定义在文件 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 、ABS_MT_POSITION_X 、 ABS_MT_POSITION_Y 和 ABS_MT_TRACKING_ID 。其中ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 用 来 上报 触 摸点 的 (X,Y) 坐 标 信息 ,ABS_MT_SLOT 用 来 上 报 触 摸 点 ID ,对于 Type B 类 型 的 设 备 , 需 要 用 到ABS_MT_TRACKING_ID 事件来区分触摸点。

对于 TypeA 类型的设备,通过 input_mt_sync()函数来隔离不同的触摸点数据信息,此函数原型如下所示:

void input_mt_sync(struct input_dev *dev)

此函数只要一个参数,类型为 input_dev,用于指定具体的 input_dev 设备。input_mt_sync()函数会触发 SYN_MT_REPORT 事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据。

对于 Type B 类型的设备,上报触摸点信息的时候需要通过 input_mt_slot()函数区分是哪一个触摸点,input_mt_slot()函数原型如下所示:

void input_mt_slot(struct input_dev *dev, int slot)

此函数有两个参数,第一个参数是 input_dev 设备,第二个参数 slot 用于指定当前上报的是哪个触摸点信息。input_mt_slot()函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据。

TypeA 设备,内核驱动需要一次性将触摸屏上所有的触摸点信息全部上报,每个触摸点的信息在本次上报事件流中的顺序不重要,因为事件的过滤和手指(触摸点)跟踪是在内核空间处理的。

Type B 设备驱动需要给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息。可以通过 slot 的 ABS_MT_TRACKING_ID 来新增、替换或删除触摸点。一个非负数的 ID 表示一个有效的触摸点,-1 这个 ID 表示未使用 slot。一个以前不存在的 ID 表示这是一个新加的触摸点,一个 ID 如果再也不存在了就表示删除了。

Type B 和 Type A 相比最大的区别就是 Type B 可以区分出触摸点, 因此可以减少发送到用户空间的数据。

2、Type A 触摸点信息上报时序

对于 Type A 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:

ABS_MT_POSITION_X x[0]	//上报第一个触摸点的 X 坐标数据,通过input_report_abs 函数实现
ABS_MT_POSITION_Y y[0]	//上报第一个触摸点的 Y 坐标数据
SYN_MT_REPORT			//上报 SYN_MT_REPORT 事件,通过调用 input_mt_sync 函数来实现
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_MT_REPORT
SYN_REPORT

具体实现代码如下:

static irqreturn_t st1232_ts_irq_handler(int irq, void *dev_id)
{
	......
	ret = st1232_ts_read_data(ts);//获取所有触摸点信息
	if (ret < 0)
		goto end;

	/* multi touch protocol */
	for (i = 0; i < MAX_FINGERS; i++) 
	{
		if (!finger[i].is_valid)
		continue;
	
		input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, finger[i].t);
		input_report_abs(input_dev, ABS_MT_POSITION_X, finger[i].x);
		input_report_abs(input_dev, ABS_MT_POSITION_Y, finger[i].y);
		input_mt_sync(input_dev);
		count++;
	}
	......
	
	/* SYN_REPORT */
	input_sync(input_dev);	//发送一个SYN_REPORT 事件
	end:
	return IRQ_HANDLED;
}

3、Type B 触摸点信息上报时序
对于 Type B 类型的设备,发送触摸点信息的时序如下所示,这里以 2 个触摸点为例:

ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 45
ABS_MT_POSITION_X x[0]
ABS_MT_POSITION_Y y[0]
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 46
ABS_MT_POSITION_X x[1]
ABS_MT_POSITION_Y y[1]
SYN_REPORT

第 1 行,上报 ABS_MT_SLOT 事件,也就是触摸点对应的 SLOT。每次上报一个触摸点坐标之前要先使用input_mt_slot函数上报当前触摸点SLOT,触摸点的SLOT其实就是触摸点ID,需要由触摸 IC 提供。
第 2 行,根据 Type B 的要求,每个 SLOT 必须关联一个 ABS_MT_TRACKING_ID,通过修改 SLOT 关联的 ABS_MT_TRACKING_ID 来完成对触摸点的添加、替换或删除。具体用到的函数就是 input_mt_report_slot_state,如果是添加一个新的触摸点,那么此函数的第三个参数active 要设置为 true,linux 内核会自动分配一个 ABS_MT_TRACKING_ID 值,不需要用户去指定具体的 ABS_MT_TRACKING_ID 值。
第 3 行,上报触摸点 0 的 X 轴坐标,使用函数 input_report_abs 来完成。
第 4 行,上报触摸点 0 的 Y 轴坐标,使用函数 input_report_abs 来完成。
第 5 - 8行,和第 1~4 行类似,只是换成了上报触摸点 0 的(X,Y)坐标信息
第 9 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件,使用 input_sync函数来完成。

当一个触摸点移除以后,同样需要通过 SLOT 关联的 ABS_MT_TRACKING_ID 来处理,时序如下所示:

ABS_MT_TRACKING_ID -1
SYN_REPORT

第 1 行,当一个触摸点(SLOT)移除以后,需要通过 ABS_MT_TRACKING_ID 事件发送一个-1 给内核。方法很简单,同样使用 input_mt_report_slot_state 函数来完成,只需要将此函数的第三个参数 active 设置为 false 即可,不需要用户手动去设置-1。
第 2 行,当所有的触摸点坐标都上传完毕以后就得发送 SYN_REPORT 事件。

用于上报 ili210x触摸坐标信息的,函数内容如下所示:

static void ili210x_report_events(struct input_dev *input,const struct touchdata *touchdata)
{
	int i;
	bool touch;
	unsigned int x, y;
	const struct finger *finger;

	for (i = 0; i < MAX_TOUCHES; i++) 		//实现所有触摸点上报
	{
		input_mt_slot(input, i);			//上 报 ABS_MT_SLOT 事件

		finger = &touchdata->finger[i];
	 
		touch = touchdata->status & (1 << i);
		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);//上报ABS_MT_TRACKING_ID 事件,也就是给 SLOT 关联一个 ABS_MT_TRACKING_ID
		if (touch) 
		{
			x = finger->x_low | (finger->x_high << 8);
			y = finger->y_low | (finger->y_high << 8);
	
			input_report_abs(input, ABS_MT_POSITION_X, x);
			input_report_abs(input, ABS_MT_POSITION_Y, y);
		}
	}

	input_mt_report_pointer_emulation(input, false);
	input_sync(input);	//上报 SYN_REPORT 事件
}

4、多点触摸所使用到的 API 函数

4.1、input_mt_init_slots 函数

input_mt_init_slots 函数用于初始化 MT 的输入 slots,编写 MT 驱动的时候必须先调用此函数初始化 slots,此函数定义在文件 drivers/input/input-mt.c 中,函数原型如下所示:

int input_mt_init_slots( struct input_dev *dev, unsigned int num_slots,unsigned int flags)

函数参数和返回值含义如下:
dev: MT 设备对应的 input_dev,因为 MT 设备隶属于 input_dev。
num_slots:设备要使用的 SLOT 数量,也就是触摸点的数量。
flags:其他一些 flags 信息,可设置的 flags 如下所示:

#define INPUT_MT_POINTER 			0x0001 /* pointer device, e.g. trackpad */
#define INPUT_MT_DIRECT 			0x0002 /* direct device, e.g. touchscreen */
#define INPUT_MT_DROP_UNUSED		0x0004 /* drop contacts not seen in frame */
#define INPUT_MT_TRACK 				0x0008 /* use in-kernel tracking */
#define INPUT_MT_SEMI_MT 			0x0010 /* semi-mt device, finger count handled manually */

可以采用‘|’运算来同时设置多个 flags 标识。
返回值:0,成功;负值,失败。

4.2、input_mt_slot 函数

此函数用于 Type B 类型,此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据,此函数定义在文件 include/linux/input/mt.h 中,函数原型如下所示:

void input_mt_slot(struct input_dev *dev, int slot)

函数参数和返回值含义如下:
dev: MT 设备对应的 input_dev。
slot:当前发送的是哪个 slot 的坐标信息,也就是哪个触摸点。
返回值:无。

4.3、input_mt_report_slot_state 函数

此函数用于 Type B 类型,用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事件, ABS_MT_TRACKING_ID 事 件 给 slot 关联一个 ABS_MT_TRACKING_ID ,ABS_MT_TOOL_TYPE 事 件 指 定 触 摸 类 型 ( 是 笔 还 是 手 指 等 )。 此 函 数 定 义 在 文 件
drivers/input/input-mt.c 中,此函数原型如下所示:

void input_mt_report_slot_state( struct input_dev *dev,unsigned int tool_type, bool active)

函数参数和返回值含义如下:
dev: MT 设备对应的 input_dev。
tool_type:触摸类型,可以选择 MT_TOOL_FINGER(手指)、MT_TOOL_PEN(笔)或MT_TOOL_PALM(手掌),对于多点电容触摸屏来说一般都是手指。
active:true,连续触摸,input 子系统内核会自动分配一个 ABS_MT_TRACKING_ID 给 slot。
false,触摸点抬起,表示某个触摸点无效了,input 子系统内核会分配一个-1 给 slot,表示触摸点溢出。
返回值:无。

4.4、input_report_abs 函数

TypeA 和 Type B 类型都使用此函数上报触摸点坐标信息,通过 ABS_MT_POSITION_X 和ABS_MT_POSITION_Y 事 件 实 现 X 和 Y 轴 坐 标 信 息 上 报 。 此 函 数 定 义 在 文 件include/linux/input.h 中,函数原型如下所示:

void input_report_abs( struct input_dev *dev, unsigned int code, int value)

函数参数和返回值含义如下:
dev: MT 设备对应的 input_dev。
code:要上报的是什么数据,可以设置为 ABS_MT_POSITION_X 或 ABS_MT_POSITION_Y,
也就是 X 轴或者 Y 轴坐标数据。
value:具体的 X 轴或 Y 轴坐标数据值。
返回值:无。

4.5、input_mt_report_pointer_emulation 函数

如果追踪到的触摸点数量多于当前上报的数量,驱动程序使用 BTN_TOOL_TAP 事件来通知用户空间当前追踪到的触摸点总数量,然后调用 input_mt_report_pointer_emulation 函数将use_count 参数设置为 false。否则的话将 use_count 参数设置为 true,表示当前的触摸点数量(此函数会获取到具体的触摸点数量,不需要用户给出),此函数定义在文件 drivers/input/input-mt.c中,函数原型如下:

void input_mt_report_pointer_emulation(struct input_dev *dev, bool use_count)

函数参数和返回值含义如下:
dev: MT 设备对应的 input_dev。
use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量。
返回值:无。

5、多点电容触摸驱动框架

我们在编写驱动的时候需要注意一下几点:
①、多点电容触摸芯片的接口,一般都为 I2C 接口,因此驱动主框架肯定是 I2C。
②、linux 里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架。
③、多点电容触摸属于 input 子系统,因此还要用到 input 子系统框架。
④、在中断处理程序中按照 linux 的 MT 协议上报坐标信息。
根据上面的分析,多点电容触摸驱动编写框架以及步骤如下:

5.1、I2C 驱动框架

/* 设备匹配表 */
static const struct i2c_device_id xxx_ts_id[] = {
	{ "xxx", 0, },
	{ /* sentinel */ }
};

/* 设备树匹配表 */
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "xxx", },
	{ /* sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver ft5x06_ts_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "edt_ft5x06",
		.of_match_table = of_match_ptr(xxx_of_match),
	},
	.id_table = xxx_ts_id,
	.probe = xxx_ts_probe,
	.remove = xxx_ts_remove,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init xxx_init(void)
{
	int ret = 0;
	
	ret = i2c_add_driver(&xxx_ts_driver);
	
	return ret;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit xxx_exit(void)
{
	i2c_del_driver(&ft5x06_ts_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");

5.2、初始化触摸 IC、中断和 input 子系统

static int xxx_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct input_dev *input;

	/* 1、初始化 I2C */
	......

	/* 2,申请中断, */
	devm_request_threaded_irq(&client->dev, client->irq, NULL,xxx_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,client->name, &xxx);		//中断线程化,防止程序一直进入中断占用CPU,这里单独给他个线程,使用“devm_”前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理。
	......

	/* 3,input 设备申请与初始化 */
	input = devm_input_allocate_device(&client->dev);	//申请 input_dev,因为多点电容触摸属于 input 子系统。

	input->name = client->name;
	input->id.bustype = BUS_I2C;
	input->dev.parent = &client->dev;
	......

	/* 4,初始化 input 和 MT */
	__set_bit(EV_ABS, input->evbit);	//设置 input_dev 需要上报的事件为 EV_ABS 和 BTN_TOUCH
	__set_bit(BTN_TOUCH, input->keybit);

	input_set_abs_params(input, ABS_X, 0, width, 0, 0);
	input_set_abs_params(input, ABS_Y, 0, height, 0, 0);
	input_set_abs_params(input, ABS_MT_POSITION_X,0, width, 0, 0);
	input_set_abs_params(input, ABS_MT_POSITION_Y,0, height, 0, 0); 
	input_mt_init_slots(input, MAX_SUPPORT_POINTS, 0);		//初始化多点电容触摸的 slots
	......
 
	/* 5,注册 input_dev */
	input_register_device(input);				//数系统注册前面申请到的 input_dev
	......
}

5.3、上报坐标信息

static irqreturn_t xxx_handler(int irq, void *dev_id)
{

	int num; /* 触摸点数量 */
	int x[n], y[n]; /* 保存坐标值 */
	
	/* 1、从触摸芯片获取各个触摸点坐标值 */
	......
	
	/* 2、上报每一个触摸点坐标 */
	for (i = 0; i < num; i++) {
		input_mt_slot(input, id);
		input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
		input_report_abs(input, ABS_MT_POSITION_X, x[i]);
		input_report_abs(input, ABS_MT_POSITION_Y, y[i]);
	}
	......
	 
	input_sync(input);
	......
	 
	return IRQ_HANDLED;
}

二、试验程序编写

1、修改设备树

1.1、添加 FT5426 所使用的 IO
FT5426 触摸芯片用到了 4 个 IO,一个复位 IO、一个中断 IO、I2C2 的 SCL 和 SDA,所以我们需要先在设备树中添加 IO 相关的信息。

触摸屏的中断引脚信息,修改以后的“pinctrl_tsc”节点内容如下所示:

pinctrl_tsc: tscgrp {
	fsl,pins = <
		MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0xF080 /* TSC_INT */
	>;
};

复位引脚配置信息即可,如下所示:

pinctrl_tsc_reset: tsc_reset {
	fsl,pins = <
		MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0
	>;
};

找到“pinctrl_i2c2”节点,此节点就是用于描述 I2C2 的 IO 信息,节点内容如下所示:

pinctrl_i2c2: i2c2grp {
	fsl,pins = <
		MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
		MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
	>;
};

确保触摸屏所使用的 IO 没有被其他的外设使用,如果有的话就需要将其屏蔽掉,保证只有触摸屏用到了这四个 IO。

2、添加 FT5426 节点

&i2c2 {
	clock_frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";
	/****************************/
	/* 省略掉其他的设备节点 */
	/****************************/
	 
	/* zuozhongkai FT5406/FT5426 */
	ft5426: ft5426@38 {			//触摸屏所使用的 FT5426 芯片节点,挂载 I2C2 节点下,FT5426 的器件地址为0X38。
		compatible = "edt,edt-ft5426";
		reg = <0x38>;			//reg 属性描述 FT5426 的器件地址为 0x38。
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_tsc
				&pinctrl_tsc_reset >;
		interrupt-parent = <&gpio1>;	//中断 IO 对应的 GPIO 组为 GPIO1
		interrupts = <9 0>;				//述中断 IO 对应的是 GPIO1 组的 IOI09
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; 		//复位 IO 对应的 GPIO 为 GPIO5_IO09
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;	//中断 IO 对应的 GPIO 为 GPIO1_IO09
	};
};

3、编写多点电容触摸驱动

#include <linux/module.h>
#include <linux/ratelimit.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input/mt.h>
#include <linux/input/touchscreen.h>
#include <linux/input/edt-ft5x06.h>
#include <linux/i2c.h>

#define MAX_SUPPORT_POINTS		5			/* 5点触摸 	*/
#define TOUCH_EVENT_DOWN		0x00		/* 按下 	*/
#define TOUCH_EVENT_UP			0x01		/* 抬起 	*/
#define TOUCH_EVENT_ON			0x02		/* 接触 	*/
#define TOUCH_EVENT_RESERVED	0x03		/* 保留 	*/

/* FT5X06寄存器相关宏定义 */
#define FT5X06_TD_STATUS_REG	0X02		/*	状态寄存器地址 		*/
#define FT5x06_DEVICE_MODE_REG	0X00 		/* 模式寄存器 			*/
#define FT5426_IDG_MODE_REG		0XA4		/* 中断模式				*/
#define FT5X06_READLEN			29			/* 要读取的寄存器个数 	*/

struct ft5x06_dev {
	struct device_node	*nd; 				/* 设备节点 		*/
	int irq_pin,reset_pin;					/* 中断和复位IO		*/
	int irqnum;								/* 中断号    		*/
	void *private_data;						/* 私有数据 		*/
	struct input_dev *input;				/* input结构体 		*/
	struct i2c_client *client;				/* I2C客户端 		*/
};

static struct ft5x06_dev ft5x06;

/*
 * @description     : 复位FT5X06
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_reset(struct i2c_client *client, struct ft5x06_dev *dev)
{
	int ret = 0;

	if (gpio_is_valid(dev->reset_pin)) {  		/* 检查IO是否有效 */
		/* 申请复位IO,并且默认输出低电平 */
		ret = devm_gpio_request_one(&client->dev,	
					dev->reset_pin, GPIOF_OUT_INIT_LOW,
					"edt-ft5x06 reset");
		if (ret) {
			return ret;
		}

		msleep(5);
		gpio_set_value(dev->reset_pin, 1);	/* 输出高电平,停止复位 */
		msleep(300);
	}

	return 0;
}

/*
 * @description	: 从FT5X06读取多个寄存器数据
 * @param - dev:  ft5x06设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int ft5x06_read_regs(struct ft5x06_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->client;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ft5x06地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ft5x06地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向ft5x06多个寄存器写入数据
 * @param - dev:  ft5x06设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 ft5x06_write_regs(struct ft5x06_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->client;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ft5x06地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/*
 * @description	: 向ft5x06指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ft5x06设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 */
static void ft5x06_write_reg(struct ft5x06_dev *dev, u8 reg, u8 data)
{
	u8 buf = 0;
	buf = data;
	ft5x06_write_regs(dev, reg, &buf, 1);
}

/*
 * @description     : FT5X06中断服务函数
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t ft5x06_handler(int irq, void *dev_id)
{
	struct ft5x06_dev *multidata = dev_id;

	u8 rdbuf[29];
	int i, type, x, y, id;
	int offset, tplen;
	int ret;
	bool down;

	offset = 1; 	/* 偏移1,也就是0X02+1=0x03,从0X03开始是触摸值 */
	tplen = 6;		/* 一个触摸点有6个寄存器来保存触摸值 */

	memset(rdbuf, 0, sizeof(rdbuf));		/* 清除 */

	/* 读取FT5X06触摸点坐标从0X02寄存器开始,连续读取29个寄存器 */
	ret = ft5x06_read_regs(multidata, FT5X06_TD_STATUS_REG, rdbuf, FT5X06_READLEN);
	if (ret) {
		goto fail;
	}

	/* 上报每一个触摸点坐标 */
	for (i = 0; i < MAX_SUPPORT_POINTS; i++) {
		u8 *buf = &rdbuf[i * tplen + offset];

		/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
		 * bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件
		 * bit5:4  保留
		 * bit3:0  X轴触摸点的11~8位。
		 */
		type = buf[0] >> 6;     /* 获取触摸类型 */
		if (type == TOUCH_EVENT_RESERVED)
			continue;
 
		/* 我们所使用的触摸屏和FT5X06是反过来的 */
		x = ((buf[2] << 8) | buf[3]) & 0x0fff;
		y = ((buf[0] << 8) | buf[1]) & 0x0fff;
		
		/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
		 * bit7:4  Touch ID  触摸ID,表示是哪个触摸点
		 * bit3:0  Y轴触摸点的11~8位。
		 */
		id = (buf[2] >> 4) & 0x0f;
		down = type != TOUCH_EVENT_UP;

		input_mt_slot(multidata->input, id);
		input_mt_report_slot_state(multidata->input, MT_TOOL_FINGER, down);

		if (!down)
			continue;

		input_report_abs(multidata->input, ABS_MT_POSITION_X, x);
		input_report_abs(multidata->input, ABS_MT_POSITION_Y, y);
	}

	input_mt_report_pointer_emulation(multidata->input, true);
	input_sync(multidata->input);

fail:
	return IRQ_HANDLED;

}

/*
 * @description     : FT5x06中断初始化
 * @param - client 	: 要操作的i2c
 * @param - multidev: 自定义的multitouch设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_irq(struct i2c_client *client, struct ft5x06_dev *dev)
{
	int ret = 0;

	/* 1,申请中断GPIO */
	if (gpio_is_valid(dev->irq_pin)) {
		ret = devm_gpio_request_one(&client->dev, dev->irq_pin,
					GPIOF_IN, "edt-ft5x06 irq");
		if (ret) {
			dev_err(&client->dev,
				"Failed to request GPIO %d, error %d\n",
				dev->irq_pin, ret);
			return ret;
		}
	}

	/* 2,申请中断,client->irq就是IO中断, */
	ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
					ft5x06_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
					client->name, &ft5x06);
	if (ret) {
		dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
		return ret;
	}

	return 0;
}

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败
  */
static int ft5x06_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	int ret = 0;

	ft5x06.client = client;

	/* 1,获取设备树中的中断和复位引脚 */
	ft5x06.irq_pin = of_get_named_gpio(client->dev.of_node, "interrupt-gpios", 0);
	ft5x06.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

	/* 2,复位FT5x06 */
	ret = ft5x06_ts_reset(client, &ft5x06);
	if(ret < 0) {
		goto fail;
	}

	/* 3,初始化中断 */
	ret = ft5x06_ts_irq(client, &ft5x06);
	if(ret < 0) {
		goto fail;
	}

	/* 4,初始化FT5X06 */
	ft5x06_write_reg(&ft5x06, FT5x06_DEVICE_MODE_REG, 0); 	/* 进入正常模式 	*/
	ft5x06_write_reg(&ft5x06, FT5426_IDG_MODE_REG, 1); 		/* FT5426中断模式	*/

	/* 5,input设备注册 */
	ft5x06.input = devm_input_allocate_device(&client->dev);
	if (!ft5x06.input) {
		ret = -ENOMEM;
		goto fail;
	}
	ft5x06.input->name = client->name;
	ft5x06.input->id.bustype = BUS_I2C;
	ft5x06.input->dev.parent = &client->dev;

	__set_bit(EV_KEY, ft5x06.input->evbit);
	__set_bit(EV_ABS, ft5x06.input->evbit);
	__set_bit(BTN_TOUCH, ft5x06.input->keybit);

	input_set_abs_params(ft5x06.input, ABS_X, 0, 1024, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_Y, 0, 600, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_MT_POSITION_X,0, 1024, 0, 0);
	input_set_abs_params(ft5x06.input, ABS_MT_POSITION_Y,0, 600, 0, 0);	     
	ret = input_mt_init_slots(ft5x06.input, MAX_SUPPORT_POINTS, 0);
	if (ret) {
		goto fail;
	}

	ret = input_register_device(ft5x06.input);
	if (ret)
		goto fail;

	return 0;

fail:
	return ret;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ft5x06_ts_remove(struct i2c_client *client)
{	
	/* 释放input_dev */
	input_unregister_device(ft5x06.input);
	return 0;
}


/*
 *  传统驱动匹配表
 */ 
static const struct i2c_device_id ft5x06_ts_id[] = {
	{ "edt-ft5206", 0, },
	{ "edt-ft5426", 0, },
	{ /* sentinel */ }
};

/*
 * 设备树匹配表 
 */
static const struct of_device_id ft5x06_of_match[] = {
	{ .compatible = "edt,edt-ft5206", },
	{ .compatible = "edt,edt-ft5426", },
	{ /* sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver ft5x06_ts_driver = {
	.driver = {
		.owner = THIS_MODULE,
		.name = "edt_ft5x06",
		.of_match_table = of_match_ptr(ft5x06_of_match),
	},
	.id_table = ft5x06_ts_id,
	.probe    = ft5x06_ts_probe,
	.remove   = ft5x06_ts_remove,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ft5x06_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ft5x06_ts_driver);

	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ft5x06_exit(void)
{
	i2c_del_driver(&ft5x06_ts_driver);
}

module_init(ft5x06_init);
module_exit(ft5x06_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");

运行测试

depmod //第一次加载驱动的时候需要运行此命令
modprobe ft5x06.ko //加载驱动模块

驱动加载成功以后就会生成/dev/input/eventX(X=1,2,3…),比如本实验的多点电容触摸驱动就会在我所使用的 ALPHA 开发板平台下就会生成/dev/input/event2 这个文件

Linux 多点电容触摸屏实验
上报数据的格式
Linux 多点电容触摸屏实验
多点电容触摸信息含义

/* 编号 */ /* tv_sec */	 	/* tv_usec */ /* type */ /* code */ /* value */
0000000 	02bb 0000 		9459 0007 		0003 	002f 		0000 0000
0000010 	02bb 0000 		9459 0007 		0003 	0039 		0005 0000
0000020 	02bb 0000 		9459 0007 		0003 	0035 		03ec 0000
0000030	 	02bb 0000 		9459 0007 		0003 	0036 		0017 0000
0000040 	02bb 0000 		9459 0007 		0001 	014a 		0001 0000
0000050 	02bb 0000 		9459 0007 		0003 	0000 		03ec 0000
0000060 	02bb 0000 		9459 0007 		0003 	0001 		0017 0000
0000070 	02bb 0000 		9459 0007 		0000 	0000 		0000 0000
0000080 	02bb 0000 		e5f8 0008 		0003 	0039 		ffff ffff
0000090 	02bb 0000 		e5f8 0008 		0001 	014a 		0000 0000
00000a0 	02bb 0000 		e5f8 0008 		0000 	0000 		0000 0000

第 1 行,type 为 0x3,说明是一个 EV_ABS 事件,code 为 0x2f,为 ABS_MT_SLOT,因此这一行就是 input_mt_slot 函数上报的 ABS_MT_SLOT 事件。value=0,说明接下来上报的是第一个触摸点坐标。

第 2 行 , type 为 0x3 , 说 明 是 一 个 EV_ABS 事 件 , code 为 0x39 , 也 就 是ABS_MT_TRACKING_ID ,这一行就是 input_mt_report_slot_state 函 数 上 报ABS_MT_TRACKING_ID 事件。value=5 说明给 SLOT0 分配的 ID 为 5。

第 3 行,type 为 0x3,是一个 EV_ABS 事件,code 为 0x35,为 ABS_MT_POSITION_X,这一行就是 input_report_abs 函数上报的 ABS_MT_POSITION_X 事件,也就是触摸点的 X 轴坐标。value=0x03ec=1004,说明触摸点 X 轴坐标为 1004,属于屏幕右上角区域。

第 4 行,type 为 0x3,是一个 EV_ABS 事件,code 为 0x36,为 ABS_MT_POSITION_Y,这一行就是 input_mt_report_slot_state 函数上报的 ABS_MT_POSITION_Y 事件,也就是触摸点的 Y 轴坐标。value=0x17=23,说明 Y 轴坐标为 23,由此可以看出本次触摸的坐标为(1004,23),处于屏幕右上角区域。

第 5 行,type 为 0x1,是一个 EV_KEY 事件,code=0x14a,为 BTN_TOUCH,value=0x1 表示触摸屏被按下。

三、tslib 移植与使用(测试触摸屏的软件)

文章来源地址https://www.toymoban.com/news/detail-458041.html

到了这里,关于Linux 多点电容触摸屏实验的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux下安装qt、qt触摸屏校准tslib

    选择安装选项:在安装程序启动后,按照提示进行操作。你可以选择安装路径、安装组件、个性化设置等。 开始安装:在确认安装选项后,点击 “安装” 按钮开始安装 Qt。这个过程可能需要一些时间,取决于你选择的组件和系统性能。 配置 Qt:安装完成后,打开终端并进入

    2024年02月09日
    浏览(44)
  • 【ChatGpt】ChatGpt解答了 “我一下午都没解决的“ Linux触摸屏驱动的问题

    现实问题: 有一个基于Linux4.19内核开发了,在海思SS528芯片运行的系统,用鼠标可以正常使用。 现在要求使用一块公司开发的 多点触控屏 连接这个系统,能正常使用。 分析问题: 要在LInux系统使用触控屏,可能需要移植 tslib 的库,这个有以前做过,可以自己解决。 移植后

    2024年02月07日
    浏览(42)
  • 【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

    1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html 现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制

    2023年04月23日
    浏览(115)
  • 应用在游戏机触摸屏中的触摸感应芯片

    触屏游戏机的屏幕是由液晶屏和触控层组成的。触控层分为电容式触屏和电阻式触屏两种。电容式触屏是将悬空电极和屏幕玻璃上的电极组成静电场,当人体接近屏幕时,就会改变静电场分布,从而实现触摸的位置探测。而电阻式触屏则是利用玻璃上的两层电极之间通电形成一个

    2024年01月18日
    浏览(44)
  • 基于GEC6818的触摸屏

    连接操作系统的输入设备,可不止一种,也许是一个标准PS/2键盘,也许是一个USB鼠标,或者是一块触摸屏,甚至是一个游戏机摇杆,Linux在处理这些纷繁各异的输入设备的时候,采用的办法还是找中间层来屏蔽各种细节,请看下图: 在Linux的内核中,对输入设备的使用,实际

    2024年02月05日
    浏览(59)
  • ESP32开发---驱动触摸屏

    采用 I2C 驱动触摸屏。 I2C 多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。 它有两条线,一条是 SCL (串行时钟总线),另外一条是SDA(串行数据线),这两条数据需要接上拉电阻,总线空闲的时候SCL和SDA处于高电

    2024年02月08日
    浏览(56)
  • WPF --- 触摸屏下的两个问题

    本片文章分享一下之前遇到的WPF应用在触摸屏下使用时的两个问题。 具体场景就是一个配置界面, ScrollViewer 中包含一个 StackPanel 然后纵向堆叠,已滚动的方式查看,然后包含多个 TextBlock 、 TextBox 以及 DataGrid ,期间遇到了两个问题: WPF在触摸屏下,如果有滚动条( ScrollVie

    2024年03月09日
    浏览(58)
  • 威纶通触摸屏报错问题汇总

    一旦出现这样提示以后,不管是离线模拟还是在线模拟都无法顺利往下执行,那么,怎么来解决这个问题呢?解决这个问题,通常分以下三类情况: 情况一 常规的解决方法 四个步骤,逐步进行,步骤一解决不了再进行步骤二这样类推。 1、 软件编辑-系统参数设置-HMI属性-端

    2024年02月05日
    浏览(53)
  • 【威纶通触摸屏 按键保护(元件安全设置)】

    提示:在触摸屏的设计中,需要对系统按键设置保护,防止误操作和非法操作,需要特定的用户权限才可以操作。 提示:这里使用的威纶通触摸屏软件及版本如下: 1.窗口布局如下,本页面展示设备运行时长,在设备保养或更换主要零部件后,按下重置按钮重新设备计算运行

    2024年02月05日
    浏览(63)
  • 【Liux下6818开发板(ARM)】触摸屏

    (꒪ꇴ꒪ ),hello我是 祐言 博客主页:C语言基础,Linux基础,软件配置领域博主🌍 快上🚘,一起学习! 送给读者的一句鸡汤🤔: 集中起来的意志可以击穿顽石! 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏         在正式学习触摸屏之前,我们先来了解一下事件

    2024年02月14日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包