资讯详情

Linux驱动开发|电容触摸屏

电容触摸屏

一、Linux电容式触摸屏驱动框架

可参考电容触摸驱动的基本原理Linux裸机开发|电容式触摸屏实验一文。电容触摸 IC 基本都是 I2C 接口,所以大框架是 I2C 设备驱动;中断引脚(INT)向内核报告触摸信息,需要中断驱动框架;触摸屏的坐标信息、屏幕按下和提升信息属于 input 因此,需要使用子系统 input 子系统

1.1 多点触摸协议

I2C 驱动,中断驱动, input 子系统已在前一篇文章中介绍,以下主要介绍 input 多点电容触摸协议在子系统下(Multi-touch,简称 MT)

通过触摸点的信息 ABS_MT 事件上报给内核,ABS_MT 在文件中定义事件 include/uapi/linux/input.h 中,

#define ABS_MT_SLOT 0x2f /* 最常用:用于上报触摸点ID */ #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 /* X坐标信息用于报告触摸点 */ #define ABS_MT_POSITION_Y 0x36 /* Y坐标信息用于报告触摸点 */ #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 /* 用于区分触摸点 */ #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 */

MT 协议被分为两种类型:Type AType B

  • Type A:适用于触摸点不能被区分或者追踪,此类型设备上报原始数据(较少用)

通过 input_mt_sync(struct input_dev *dev)函数来隔离不同的触摸点数据信息,该函数会触发 SYN_MT_REPORT 事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据

input_mt_sync(struct input_dev *dev)
//input_dev:用于指定具体的 input_dev 设备

:编写Type A类型的多点触摸驱动时,需要按照下面的时序上报坐标信息

//以两点触摸为例
ABS_MT_POSITION_X x[0]	//上报第一个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[0]	//上报第一个触摸点的Y坐标信息,通过input_report_abs函数实现
SYN_MT_REPORT			//上报SYN_MT_REPORT事件,通过调用input_mt_sync函数实现
ABS_MT_POSITION_X x[1]	//上报第二个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[1]	//上报第二个触摸点的Y坐标信息,通过input_report_abs函数实现
SYN_MT_REPORT			//上报SYN_MT_REPORT事件,通过调用input_mt_sync函数实现
SYN_REPORT				//上报SYN_REPORT事件,通过调用input_sync函数实现

Type A 类型触摸点信息上报实例

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

	/* 按照Type A类型轮流上报所有的触摸点信息 */
	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);
		//每上报完一个触摸点坐标,都要调用以下函数上报一个 SYN_MT_REPOR
		input_mt_sync(input_dev);	
		count++;
	}
	......
	/* 每上报完一轮触摸点信息就调用一次input_sync函数,发送SYN_REPORT事件 */
	input_sync(input_dev);
end:
	return IRQ_HANDLED;
}
  • Type B:适用于有硬件追踪并能区分触摸点的触摸设备,通过 slot 更新某个触摸点的信息

通过 input_mt_slot(struct input_dev *dev, int slot)函数区分是哪一个触摸点,函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据

void input_mt_slot(struct input_dev *dev, int slot)
//input_dev:用于指定具体的 input_dev 设备
//slot:用于指定当前上报的是哪个触摸点信息

:编写Type B类型的多点触摸驱动时,需要按照下面的时序上报坐标信息

//以两点触摸为例
ABS_MT_SLOT 0			//上报触摸点对应的SLOT(即触摸点ID),通过input_mt_slot函数实现
ABS_MT_TRACKING_ID 45	//每个触摸点关联一个ID,修改该ID可进行触摸点的添加、替换或删除,通过input_mt_report_slot_state函数实现
ABS_MT_POSITION_X x[0]	//上报第一个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[0]	//上报第一个触摸点的Y坐标信息,通过input_report_abs函数实现
ABS_MT_SLOT 1			//上报触摸点对应的SLOT(即触摸点ID),通过input_mt_slot函数实现
ABS_MT_TRACKING_ID 46	//每个触摸点关联一个ID,修改该ID可进行触摸点的添加、替换或删除,通过input_mt_report_slot_state函数实现
ABS_MT_POSITION_X x[1]	//上报第二个触摸点的X坐标信息,通过input_report_abs函数实现
ABS_MT_POSITION_Y y[1]	//上报第二个触摸点的Y坐标信息,通过input_report_abs函数实现
SYN_REPORT				//上报SYN_REPORT事件,通过调用input_sync函数实现

Type B 类型触摸点信息上报实例

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);
		//上报ABS_MT_TRACKING_ID事件,即给SLOT关联一个ABS_MT_TRACKING_ID
		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
		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函数,发送SYN_REPORT事件 */
	input_sync(input);
}
1.2 多点触摸相关API函数

Linux 下的多点触摸协议其实就是通过不同的事件来上报触摸点坐标信息,这些事件都是通过内核提供的对应 API 函数实现的,下面介绍一些常见的 API 函数

  • input_mt_init_slots 函数:用于初始化 MT 的输入 slots,编写 MT 驱动时须先调用此函数初始化 slots
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_UNUSED0x0004 /* 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 */
//返回值: 0,成功;负值,失败
  • input_mt_slot 函数:用于 Type B 类型,此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据
void input_mt_slot(struct input_dev *dev, int slot)
//dev:MT设备对应的input_dev
//slot:当前发送的是哪个slot的坐标信息,也就是哪个触摸点
  • 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 事 件 指 定 触 摸 类 型 ( 是 笔 还 是 手 指 等 )
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
  • input_report_abs 函数:Type A 和 Type B 类型都使用此函数上报触摸点坐标信息
void input_report_abs(struct input_dev *dev,
					  unsigned int code,
					  int value)
//dev:MT设备对应的input_dev
//code:要上报的是什么数据,如设置为ABS_MT_POSITION_X,就表示上报X轴坐标数据
//value:具体的X轴或Y轴坐标数据值
  • input_mt_report_pointer_emulation 函数:若追踪到的触摸点数量多于当前上报的数量,驱动程序使用 BTN_TOOL_TAP 事件来通知用户空间当前追踪到的触摸点总数量,然后调用此函数将use_count 参数设置为 false;否则的话将 use_count 参数设置为 true,表示当前的触摸点数量
void input_mt_report_pointer_emulation( struct input_dev *dev,
										bool use_count)
//dev:MT设备对应的input_dev
//use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量
1.3 多点触摸驱动框架

多点电容触摸驱动编写框架以及步骤如下:

  • I2C 驱动框架:驱动总体采用 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,
};
/* 驱动入口函数 */
static int __init xxx_init(void) { 
        
	int ret = 0;
	ret = i2c_add_driver(&xxx_ts_driver);
	return ret;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void) { 
        
	i2c_del_driver(&ft5x06_ts_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
  • 初始化触摸 IC、中断和 input 子系统:在xxx_ts_probe 函数中完成
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);
	......
	/* 3, input 设备申请与初始化 */
	input = devm_input_allocate_device(&client->dev);
	input->name = client->name;
	input->id.bustype = BUS_I2C;
	input->dev.parent = &client->dev;
	......
	/* 4,初始化 input 和 MT */
	__set_bit(EV_ABS, input->evbit);
	__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);
	......
	/* 5,注册 input_dev */
	input_register_device(input);
	......
}

使用“ devm_”前缀的函数申请到的资源可以由系统自动释放,不需要我们手动处理

  • 上报坐标信息:在中断服务程序中上报读取到的坐标信息
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;
}

二、电容触摸屏实验

本实验以ATK7016(7 寸 1024*600 分辨率)屏幕所使用的 FT5426 触摸芯片为例,讲解如何编写多点电容触摸驱动,硬件原理图参考Linux裸机开发|电容触摸屏实验一文

2.1 修改设备树
  • 修改或添加pinctrl节点:FT5426 触摸芯片用到了复位 IO、中断 IO、I2C2 的 SCL 和 SDA共4个 IO

如有默认的 pinctrl_tsc 子节点,则直接添加如下中断引脚信息;若没有则在iomuxc节点下自行创建

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

复位引脚是SNVS_TAMPER9,因此在iomuxc_snvs节点下添加复位引脚信息

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

I2C2 的 SCL 和 SDA 属于 I2C2,一般默认已经添加,无需修改

pinctrl_i2c2: i2c2grp { 
        
	fsl,pins = <
		MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0
		MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0
	>;
};
  • 添加子节点:FT5426 触摸 IC 挂载 I2C2 下,因此需要向 I2C2 节点下添加一个子节点,用于描述 FT5426
&i2c2 { 
        
	clock_frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c2>;
	status = "okay";
	/****************************/
	/* 省略掉其他的设备节点 */
	/****************************/
	ft5426: ft5426@38 { 
        		//器件地址为0x38
		compatible = "edt,edt-ft5426";
		reg = <0x38>;	//描述器件地址为0x38
		pinctrl-names = "default";
		pinctrl-0 = < &pinctrl_tsc
					  &pinctrl_tsc_reset >;
		interrupt-parent = <&gpio1>;
		interrupts = <9 0>;
		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;
		interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
	};
};
  • 检查PIN是否冲突:检查pinctrl中设置以及设备节点中指定的引脚有没有被别的外设使用

保存修改后,在kernel主目录下使用“make dtbs”命令编译设备树,使用新的设备树文件启动Llinux系统

2.2 驱动程序编写

新建 ft5x06.c 驱动文件

#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;
/* 复位FT5X06 */
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;
}
/* 从FT5X06读取多个寄存器数据 */
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 

标签: 电容笔变接触笔6410电容屏驱动8合1电容笔电容的if20008电容触摸屏

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台