• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

电容触摸屏驱动Linux驱动开发篇

武飞扬头像
栋哥爱做饭
帮助1

1.电容触摸屏简介

  • 电容屏只需要手指轻触即可,而电阻屏是需要手指
    给予一定的压力才有反应,而且电容屏不需要校准。
  • 如果要做人机交互设备的开发,多点电容触摸屏基本是不可能绕过去的。

2. 驱动器件

  • 正点原子ATK-7016 这款屏幕其实是由 TFT LCD 触摸屏组合起来的。底下是 LCD 面板,上面是触摸面板,将两个封装到一起就成了带有触摸屏的 LCD 屏幕
  • 电容触摸屏也是需要一个驱动 IC的,驱动 IC 一般会提供一个 I2C 接口给主控制器主控制器可以通过 I2C 接口来读取驱动 IC里面的触摸坐标数据
  • ATK-7016、ATK-7084这两款屏幕使用的触摸控制 IC 是 FT5426。
    1.FT5426 这款驱动 IC 采用 15*28 的驱动结构,也就是 15 个感应通道,28 个驱动通道,
    2.最多支持5点电容触摸。
  • ATK-7016的电容触摸屏部分有4个 IO用于连接主控制器: SCL、 SDA、RST 和 INT
    1.SCL 和 SDA 是 I2C 引脚,
    2.RST 是复位引脚,INT 是中断引脚。
    一般通过 INT 引脚来通知主控制器有触摸点按下,然后在INT 中断服务函数中读取触摸数据。也可以不使用中断功能,采用轮询的方式不断查询是否有触摸点按下。
  • 和所有的 I2C 器件一样,FT5426 也是通过读写寄存器来完成初始化和触摸坐标数据读取的主要工作就是读写FT5426 的寄存器
  • FT5426 的 I2C 设备地址为 0X38,FT5426 的寄存器有很多,本章我们只用到了其中的一部分:
    学新通学新通
  • 触摸屏与单片机接触引脚如下:
    1.触摸屏连接着I.MX6U的I2C2,
    2.INT引脚连接着I.MX6U的GPIO1_IO9,
    3.RST 引脚连接着 I.MX6U 的 SNVS_TAMPER9。

裸机驱动

  • 同样用的是和前面I2C驱动的一样的是,主机驱动设备驱动
  • 主机驱动就是配置SOC的代码

主机驱动bsp_i2c.h

#ifndef _BSP_I2C_H
#define _BSP_I2C_H
#include "imx6ul.h"

/* 相关宏定义 */
#define I2C_STATUS_OK				(0)
#define I2C_STATUS_BUSY				(1)
#define I2C_STATUS_IDLE				(2)
#define I2C_STATUS_NAK				(3)
#define I2C_STATUS_ARBITRATIONLOST	(4)
#define I2C_STATUS_TIMEOUT			(5)
#define I2C_STATUS_ADDRNAK			(6)

/*
 * I2C方向枚举类型
 */
enum i2c_direction
{
    kI2C_Write = 0x0, 		/* 主机向从机写数据 */
    kI2C_Read = 0x1,  		/* 主机从从机读数据 */
} ;

/*
 * 主机传输结构体
 */
struct i2c_transfer
{
    unsigned char slaveAddress;      	/* 7位从机地址 			*/
    enum i2c_direction direction; 		/* 传输方向 			*/
    unsigned int subaddress;       		/* 寄存器地址			*/
    unsigned char subaddressSize;    	/* 寄存器地址长度 			*/
    unsigned char *volatile data;    	/* 数据缓冲区 			*/
    volatile unsigned int dataSize;  	/* 数据缓冲区长度 			*/
};

/*
 *函数声明
 */
void i2c_init(I2C_Type *base);
unsigned char i2c_master_start(I2C_Type *base, unsigned char address, enum i2c_direction direction);
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address,  enum i2c_direction direction);
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status);
unsigned char i2c_master_stop(I2C_Type *base);
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size);
void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size);
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer);

#endif

主机驱动bsp_i2c.c

#include "bsp_i2c.h"
#include "bsp_delay.h"
#include "stdio.h"

/*
 * @description		: 初始化I2C,波特率100KHZ
 * @param - base 	: 要初始化的IIC设置
 * @return 			: 无
 */
void i2c_init(I2C_Type *base)
{
	/* 1、配置I2C */
	base->I2CR &= ~(1 << 7); /* 要访问I2C的寄存器,首先需要先关闭I2C */

    /* 设置波特率为100K
     * I2C的时钟源来源于IPG_CLK_ROOT=66Mhz
 	 * IC2 时钟 = PERCLK_ROOT/dividison(IFDR寄存器)
	 * 设置寄存器IFDR,IFDR寄存器参考IMX6UL参考手册P1260页,表29-3,
	 * 根据表29-3里面的值,挑选出一个还是的分频数,比如本例程我们
	 * 设置I2C的波特率为100K, 因此当分频值=66000000/100000=660.
	 * 在表29-3里面查找,没有660这个值,但是有640,因此就用640,
	 * 即寄存器IFDR的IC位设置为0X15
	 */
	base->IFDR = 0X15 << 0;

	/*
     * 设置寄存器I2CR,开启I2C
     * bit[7] : 1 使能I2C,I2CR寄存器其他位其作用之前,此位必须最先置1
	 */
	base->I2CR |= (1<<7);
}

/*
 * @description			: 发送重新开始信号
 * @param - base 		: 要使用的IIC
 * @param - addrss		: 设备地址
 * @param - direction	: 方向
 * @return 				: 0 正常 其他值 出错
 */
unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address,  enum i2c_direction direction)
{
	/* I2C忙并且工作在从模式,跳出 */
	if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))		
		return 1;

	/*
     * 设置寄存器I2CR
     * bit[4]: 1 发送
     * bit[2]: 1 产生重新开始信号
	 */
	base->I2CR |=  (1 << 4) | (1 << 2);

	/*
     * 设置寄存器I2DR
     * bit[7:0] : 要发送的数据,这里写入从设备地址
     *            参考资料:IMX6UL参考手册P1249
	 */ 
	base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
	
	return 0;
}

/*
 * @description			: 发送开始信号
 * @param - base 		: 要使用的IIC
 * @param - addrss		: 设备地址
 * @param - direction	: 方向
 * @return 				: 0 正常 其他值 出错
 */
unsigned char i2c_master_start(I2C_Type *base, unsigned char address,  enum i2c_direction direction)
{
	if(base->I2SR & (1 << 5))			/* I2C忙 */
		return 1;

	/*
     * 设置寄存器I2CR
     * bit[5]: 1 主模式
     * bit[4]: 1 发送
	 */
	base->I2CR |=  (1 << 5) | (1 << 4);

	/*
     * 设置寄存器I2DR
     * bit[7:0] : 要发送的数据,这里写入从设备地址
     *            参考资料:IMX6UL参考手册P1249
	 */ 
	base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);
	return 0;
}

/*
 * @description		: 检查并清除错误
 * @param - base 	: 要使用的IIC
 * @param - status	: 状态
 * @return 			: 状态结果
 */
unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status)
{
	/* 检查是否发生仲裁丢失错误 */
	if(status & (1<<4))
	{
		base->I2SR &= ~(1<<4);		/* 清除仲裁丢失错误位 			*/

		base->I2CR &= ~(1 << 7);	/* 先关闭I2C 				*/
		base->I2CR |= (1 << 7);		/* 重新打开I2C 				*/
		return I2C_STATUS_ARBITRATIONLOST;
	} 
	else if(status & (1 << 0))     	/* 没有接收到从机的应答信号 */
	{
		return I2C_STATUS_NAK;		/* 返回NAK(No acknowledge) */
	}
	return I2C_STATUS_OK;
}

/*
 * @description		: 停止信号
 * @param - base	: 要使用的IIC
 * @param			: 无
 * @return 			: 状态结果
 */
unsigned char i2c_master_stop(I2C_Type *base)
{
	unsigned short timeout = 0xffff;

	/*
	 * 清除I2CR的bit[5:3]这三位
	 */
	base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));

	/* 等待忙结束 */
	while((base->I2SR & (1 << 5)))
	{
		timeout--;
		if(timeout == 0)	/* 超时跳出 */
			return I2C_STATUS_TIMEOUT;
	}
	return I2C_STATUS_OK;
}

/*
 * @description		: 发送数据
 * @param - base 	: 要使用的IIC
 * @param - buf		: 要发送的数据
 * @param - size	: 要发送的数据大小
 * @param - flags	: 标志
 * @return 			: 无
 */
void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size)
{
	/* 等待传输完成 */
	while(!(base->I2SR & (1 << 7))); 
	
	base->I2SR &= ~(1 << 1); 	/* 清除标志位 */
	base->I2CR |= 1 << 4;		/* 发送数据 */
	
	while(size--)
	{
		base->I2DR = *buf  ; 	/* 将buf中的数据写入到I2DR寄存器 */
		
		while(!(base->I2SR & (1 << 1))); 	/* 等待传输完成 */	
		base->I2SR &= ~(1 << 1);			/* 清除标志位 */

		/* 检查ACK */
		if(i2c_check_and_clear_error(base, base->I2SR))
			break;
	}
	
	base->I2SR &= ~(1 << 1);
	i2c_master_stop(base); 	/* 发送停止信号 */
}

/*
 * @description		: 读取数据
 * @param - base 	: 要使用的IIC
 * @param - buf		: 读取到数据
 * @param - size	: 要读取的数据大小
 * @return 			: 无
 */
void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size)
{
	volatile uint8_t dummy = 0;

	dummy  ; 	/* 防止编译报错 */
	
	/* 等待传输完成 */
	while(!(base->I2SR & (1 << 7))); 
	
	base->I2SR &= ~(1 << 1); 				/* 清除中断挂起位 */
	base->I2CR &= ~((1 << 4) | (1 << 3));	/* 接收数据 */
	
	/* 如果只接收一个字节数据的话发送NACK信号 */
	if(size == 1)
        base->I2CR |= (1 << 3);

	dummy = base->I2DR; /* 假读 */
	
	while(size--)
	{
		while(!(base->I2SR & (1 << 1))); 	/* 等待传输完成 */	
		base->I2SR &= ~(1 << 1);			/* 清除标志位 */

	 	if(size == 0)
        {
        	i2c_master_stop(base); 			/* 发送停止信号 */
        }

        if(size == 1)
        {
            base->I2CR |= (1 << 3);
        }
		*buf   = base->I2DR;
	}
}

/*
 * @description	: I2C数据传输,包括读和写
 * @param - base: 要使用的IIC
 * @param - xfer: 传输结构体
 * @return 		: 传输结果,0 成功,其他值 失败;
 */
unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
{
	unsigned char ret = 0;
	 enum i2c_direction direction = xfer->direction;	

	base->I2SR &= ~((1 << 1) | (1 << 4));			/* 清除标志位 */

	/* 等待传输完成 */
	while(!((base->I2SR >> 7) & 0X1)){}; 

	/* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */
    if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read))
    {
        direction = kI2C_Write;
    }

	ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 发送开始信号 */
    if(ret)
    {	
		return ret;
	}

	while(!(base->I2SR & (1 << 1))){};			/* 等待传输完成 */

    ret = i2c_check_and_clear_error(base, base->I2SR);	/* 检查是否出现传输错误 */
    if(ret)
    {
      	i2c_master_stop(base); 						/* 发送出错,发送停止信号 */
        return ret;
    }
	
    /* 发送寄存器地址 */
    if(xfer->subaddressSize)
    {
        do
        {
			base->I2SR &= ~(1 << 1);			/* 清除标志位 */
            xfer->subaddressSize--;				/* 地址长度减一 */
			
            base->I2DR =  ((xfer->subaddress) >> (8 * xfer->subaddressSize)); //向I2DR寄存器写入子地址
  
			while(!(base->I2SR & (1 << 1)));  	/* 等待传输完成 */

            /* 检查是否有错误发生 */
            ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
             	i2c_master_stop(base); 				/* 发送停止信号 */
             	return ret;
            }  
        } while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));

        if(xfer->direction == kI2C_Read) 		/* 读取数据 */
        {
            base->I2SR &= ~(1 << 1);			/* 清除中断挂起位 */
            i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read); /* 发送重复开始信号和从机地址 */
    		while(!(base->I2SR & (1 << 1))){};/* 等待传输完成 */

            /* 检查是否有错误发生 */
			ret = i2c_check_and_clear_error(base, base->I2SR);
            if(ret)
            {
             	ret = I2C_STATUS_ADDRNAK;
                i2c_master_stop(base); 		/* 发送停止信号 */
                return ret;  
            }
           	          
        }
    }	

    /* 发送数据 */
    if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0))
    {
    	i2c_master_write(base, xfer->data, xfer->dataSize);
	}

    /* 读取数据 */
    if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0))
    {
       	i2c_master_read(base, xfer->data, xfer->dataSize);
	}
	return 0;	
}

这就是驱动的分离和分层思想,主机驱动具有通用性,对于我们只要篇编写设备驱动就行,这里主要就是传感器FT5426的编写

设备驱动bsp_ft5xx5.h

此文件里面描写都是设备的信息,和寄存器打交道的读写函数

#ifndef _FT5XX6_H
#define _FT5XX6_H

#include "imx6ul.h"
#include "bsp_gpio.h"

/* 宏定义 */
#define FT5426_ADDR				0X38	/* FT5426设备地址 		*/

#define FT5426_DEVICE_MODE		0X00 	/* 模式寄存器 			*/
#define FT5426_IDGLIB_VERSION	0XA1 	/* 固件版本寄存器 			*/
#define FT5426_IDG_MODE			0XA4	/* 中断模式				*/
#define FT5426_TD_STATUS		0X02	/* 触摸状态寄存器 			*/
#define FT5426_TOUCH1_XH		0X03	/* 触摸点坐标寄存器,
										 * 一个触摸点用4个寄存器存储坐标数据*/
										 
#define FT5426_XYCOORDREG_NUM	30		/* 触摸点坐标寄存器数量 */
#define FT5426_INIT_FINISHED	1		/* 触摸屏初始化完成 			*/
#define FT5426_INIT_NOTFINISHED	0		/* 触摸屏初始化未完成 			*/

#define FT5426_TOUCH_EVENT_DOWN			0x00	/* 按下 		*/
#define FT5426_TOUCH_EVENT_UP			0x01	/* 释放 		*/
#define FT5426_TOUCH_EVENT_ON			0x02	/* 接触 		*/
#define FT5426_TOUCH_EVENT_RESERVED		0x03	/* 没有事件 */

/* 触摸屏结构体 */
struct ft5426_dev_struc
{	
	unsigned char initfalg;		/* 触摸屏初始化状态 */
	unsigned char intflag;		/* 标记中断有没有发生 */
	unsigned char point_num;	/* 触摸点 		*/
	unsigned short x[5];		/* X轴坐标 	*/
	unsigned short y[5];		/* Y轴坐标 	*/

};

extern struct ft5426_dev_struc ft5426_dev;

/* 函数声明 */
void ft5426_init(void);
void gpio1_io9_irqhandler(void);
unsigned char ft5426_write_byte(unsigned char addr,unsigned char reg, unsigned char data);
unsigned char ft5426_read_byte(unsigned char addr,unsigned char reg);
void ft5426_read_len(unsigned char addr,unsigned char reg,unsigned char len,unsigned char *buf);
void ft5426_read_tpnum(void);
void ft5426_read_tpcoord(void);

#endif

设备驱动bsp_ft5xx5.c

学新通
这里用的是I2C2

#include "bsp_ft5xx6.h"
#include "bsp_i2c.h"
#include "bsp_int.h"
#include "bsp_delay.h"
#include "stdio.h"

struct ft5426_dev_struc ft5426_dev;

/*
 * @description	: 初始化触摸屏,其实就是初始化FT5426
 * @param		: 无
 * @return 		: 无
 */
void ft5426_init(void)
{
	unsigned char reg_value[2];

	ft5426_dev.initfalg = FT5426_INIT_NOTFINISHED;
	int i;
	for( i = 0; i < 5; i   )
	{	/* 避免编译器自动赋值 */
		ft5426_dev.x[i] = 0;
		ft5426_dev.y[i] = 0;
	}
	ft5426_dev.point_num = 0;

	/* 1、初始化IIC2 IO
     * I2C2_SCL -> UART5_TXD
     * I2C2_SDA -> UART5_RXD
     */
	IOMUXC_SetPinMux(IOMUXC_UART5_TX_DATA_I2C2_SCL,1);
	IOMUXC_SetPinMux(IOMUXC_UART5_RX_DATA_I2C2_SDA,1);

	/* 配置I2C2 IO属性	
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 1 默认47K上拉
	 *bit [13]: 1 pull功能
	 *bit [12]: 1 pull/keeper使能 
	 *bit [11]: 0 关闭开路输出
	 *bit [7:6]: 10 速度100Mhz
	 *bit [5:3]: 110 驱动能力为R0/6
	 *bit [0]: 1 高转换率
	 */
	IOMUXC_SetPinConfig(IOMUXC_UART5_TX_DATA_I2C2_SCL,0x70B0);
	IOMUXC_SetPinConfig(IOMUXC_UART5_RX_DATA_I2C2_SDA,0X70B0);
	
	/* 2、初始化触摸屏中断IO和复位IO */
	gpio_pin_config_t ctintpin_config;

	IOMUXC_SetPinMux(IOMUXC_GPIO1_IO09_GPIO1_IO09,0);		/* 复用为GPIO1_IO9 */
	IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0);/* 复用为GPIO5_IO9 */
	
	IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO09_GPIO1_IO09,0xF080);
	IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER9_GPIO5_IO09,0X10B0);

	/* 中断IO初始化 */
	ctintpin_config.direction = kGPIO_DigitalInput;
	ctintpin_config.interruptMode = kGPIO_IntRisingOrFallingEdge;
	gpio_init(GPIO1, 9, &ctintpin_config);

	GIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);				/* 使能GIC中对应的中断 */
	system_register_irqhandler(GPIO1_Combined_0_15_IRQn, (system_irq_handler_t)gpio1_io9_irqhandler, NULL);	/* 注册中断服务函数 */
	gpio_enableint(GPIO1, 9);								/* 使能GPIO1_IO18的中断功能 */

	/* 复位IO初始化 */
    ctintpin_config.direction=kGPIO_DigitalOutput;	
    ctintpin_config.interruptMode=kGPIO_NoIntmode;	
    ctintpin_config.outputLogic=1;					   
    gpio_init(GPIO5, 9, &ctintpin_config); 

	/* 3、初始化I2C */
	i2c_init(I2C2);	

	/* 4、初始化FT5426 */
	gpio_pinwrite(GPIO5, 9, 0);	/* 复位FT5426 */
	delayms(20);
	gpio_pinwrite(GPIO5, 9, 1); /* 停止复位FT5426 */
	delayms(20);

	ft5426_write_byte(FT5426_ADDR, FT5426_DEVICE_MODE, 0); 	/* 进入正常模式 				*/
	ft5426_write_byte(FT5426_ADDR, FT5426_IDG_MODE, 1); 	/* FT5426中断模式 			*/

	
	ft5426_read_len(FT5426_ADDR, FT5426_IDGLIB_VERSION, 2, reg_value);
	printf("Touch Frimware Version:%#X\r\n", ((unsigned short)reg_value[0] << 8)   reg_value[1]);
	
	ft5426_dev.initfalg = FT5426_INIT_FINISHED;	/* 标记FT5426初始化完成 */
	ft5426_dev.intflag = 0;
}

/*
 * @description			: GPIO1_IO9最终的中断处理函数
 * @param				: 无
 * @return 				: 无
 */
void gpio1_io9_irqhandler(void)
{ 
	if(ft5426_dev.initfalg == FT5426_INIT_FINISHED)
	{
		//ft5426_dev.intflag = 1;
		ft5426_read_tpcoord();
	}
	gpio_clearintflags(GPIO1, 9); /* 清除中断标志位 */
}


/*
 * @description	: 向FT5429写入数据
 * @param - addr: 设备地址
 * @param - reg : 要写入的寄存器
 * @param - data: 要写入的数据
 * @return 		: 操作结果
 */
unsigned char ft5426_write_byte(unsigned char addr,unsigned char reg, unsigned char data)
{
    unsigned char status=0;
    unsigned char writedata=data;
    struct i2c_transfer masterXfer;
	
    /* 配置I2C xfer结构体 */
   	masterXfer.slaveAddress = addr; 			/* 设备地址 				*/
    masterXfer.direction = kI2C_Write;			/* 写入数据 				*/
    masterXfer.subaddress = reg;				/* 要写入的寄存器地址 			*/
    masterXfer.subaddressSize = 1;				/* 地址长度一个字节 			*/
    masterXfer.data = &writedata;				/* 要写入的数据 				*/
    masterXfer.dataSize = 1;  					/* 写入数据长度1个字节			*/

    if(i2c_master_transfer(I2C2, &masterXfer))
        status=1;
        
    return status;
}

/*
 * @description	: 从FT5426读取一个字节的数据
 * @param - addr: 设备地址
 * @param - reg : 要读取的寄存器
 * @return 		: 读取到的数据。
 */
unsigned char ft5426_read_byte(unsigned char addr,unsigned char reg)
{
	unsigned char val=0;
	
	struct i2c_transfer masterXfer;	
	masterXfer.slaveAddress = addr;				/* 设备地址 				*/
    masterXfer.direction = kI2C_Read;			/* 读取数据 				*/
    masterXfer.subaddress = reg;				/* 要读取的寄存器地址 			*/
    masterXfer.subaddressSize = 1;				/* 地址长度一个字节 			*/
    masterXfer.data = &val;						/* 接收数据缓冲区 				*/
    masterXfer.dataSize = 1;					/* 读取数据长度1个字节			*/
	i2c_master_transfer(I2C2, &masterXfer);

	return val;
}

/*
 * @description	: 从FT5429读取多个字节的数据
 * @param - addr: 设备地址
 * @param - reg : 要读取的开始寄存器地址
 * @param - len : 要读取的数据长度.
 * @param - buf : 读取到的数据缓冲区
 * @return 		: 无
 */
void ft5426_read_len(unsigned char addr,unsigned char reg,unsigned char len,unsigned char *buf)
{	
	struct i2c_transfer masterXfer;	
	
	masterXfer.slaveAddress = addr;				/* 设备地址 				*/
    masterXfer.direction = kI2C_Read;			/* 读取数据 				*/
    masterXfer.subaddress = reg;				/* 要读取的寄存器地址 			*/
    masterXfer.subaddressSize = 1;				/* 地址长度一个字节 			*/
    masterXfer.data = buf;						/* 接收数据缓冲区 				*/
    masterXfer.dataSize = len;					/* 读取数据长度1个字节			*/
	i2c_master_transfer(I2C2, &masterXfer);
} 

/*
 * @description	: 读取当前触摸点个数
 * @param 		: 无
 * @return 		: 无
 */
void ft5426_read_tpnum(void)
{
	ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);
}

/*
 * @description	: 读取当前所有触摸点的坐标
 * @param 		: 无
 * @return 		: 无
 */
void ft5426_read_tpcoord(void)
{
	unsigned char i = 0;
	unsigned char type = 0;
	//unsigned char id = 0;
	unsigned char pointbuf[FT5426_XYCOORDREG_NUM];
	
	ft5426_dev.point_num = ft5426_read_byte(FT5426_ADDR, FT5426_TD_STATUS);

	/*
  	 * 从寄存器FT5426_TOUCH1_XH开始,连续读取30个寄存器的值,这30个寄存器
  	 * 保存着5个点的触摸值,每个点占用6个寄存器。
	 */
	ft5426_read_len(FT5426_ADDR, FT5426_TOUCH1_XH, FT5426_XYCOORDREG_NUM, pointbuf);
		
	for(i = 0; i < ft5426_dev.point_num ; i  )
	{
		unsigned char *buf = &pointbuf[i * 6];
		/* 以第一个触摸点为例,寄存器TOUCH1_XH(地址0X03),各位描述如下:
		 * bit7:6  Event flag  0:按下 1:释放 2:接触 3:没有事件
		 * bit5:4  保留
		 * bit3:0  X轴触摸点的11~8位。
		 */
		ft5426_dev.x[i] = ((buf[2] << 8) | buf[3]) & 0x0fff;
		ft5426_dev.y[i] = ((buf[0] << 8) | buf[1]) & 0x0fff;
		
		type = buf[0] >> 6;	/* 获取触摸类型 */
		

		/* 以第一个触摸点为例,寄存器TOUCH1_YH(地址0X05),各位描述如下:
		 * bit7:4  Touch ID  触摸ID,表示是哪个触摸点
		 * bit3:0  Y轴触摸点的11~8位。
		 */
		//id = (buf[2] >> 4) & 0x0f;
		
		if(type == FT5426_TOUCH_EVENT_DOWN || type == FT5426_TOUCH_EVENT_ON )/* 按下 	*/
		{
		} 
		else  {	/* 释放 */		
		}
	}	
}

linux驱动

1.介绍

  • 看到这里,我们可以得出电容触摸屏驱动其实大框架就是 IIC设备驱动
  • 在此基础上,介绍了触摸屏的四个引脚,除去IIC的SCL和SDA,还有INT引脚和RST引脚.
  • INT(中断引脚))向linux内核上报触摸信息,因此需要用到linux中断驱动框架。坐标的上报在中断服务函数中完成。
  • 触摸屏的坐标信息、屏幕按下和抬起信息都属于 linux的 input子系统,因此向 linux内核上报触摸屏坐标信息就得使用 input子系统。这篇文章介绍了input子系统简述
  • 在input系统框架上我们知道(按键、鼠标、键盘、触摸屏等都属于输入设备,linux内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了input框架,用户只需要负责上报输入事件,比如按键值、坐标等信息)但是对于多点触摸的上报事件我们需要引入input 子系统下的多点电容触摸协议(MT)

1.MT协议。 MT 协议被分为两种类型, Type A和TypeB。
1)Type A:适用于触摸点不能被区分或者追踪,此类型的设备上报原始数据
2)适用于有硬件追踪并能区分触摸点的触摸设备,此类型设备通过 slot更新某一个触摸点的信息,FT5426就属于此类型,一般的多点电容触摸屏 IC都有此能力。

2.我们这里使用TypeB。我们用MT协议的根本目的就是为了上报事件给内核(这里适用于多点的触摸的ABS_MT事件)

/*
@	ABS_MT 事件 
*/
 #define ABS_MT_SLOT        0x2f /* MT slot being modified ==================上 报 触 摸 点 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 /* 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 ==对于 Type  B 类 型 的 设 备 ==来区分触摸点*/ 
 #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 */ 



/*
@	对于Type A 类型的设备,函数来隔离不同的触摸点数据信息
@	dev:用于指定具体的 input_dev设备
@ 	功能: 函数会触发 SYN_MT_REPORT 事件,此事件会通知接收者获取当前触摸数据,并且准备接收下一个触摸点数据
*/
void input_mt_sync(struct input_dev *dev) 
/*
@	对于Type B 类型的设备,上报触摸点信息的时候需要通过 input_mt_slot()函数区分是哪一个触摸点
@	dev:  input_dev设备
@	slot: 指定当前上报的是哪个触摸点信息 	
@	功能: 函数会触发 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点(slot)的数据
*/
void input_mt_slot(struct input_dev *dev, int slot) 

3.原理:Type B 设备驱动需要给每个识别出来的触摸点分配一个 slot,后面使用这个 slot 来上报触摸点信息。(有点像信号和槽机制)。但是你既然多点触摸,肯定每个点都有个先后顺序,下面针对TypeB的来介绍

/*
@	Type B 触摸点数据上报时序 
*/
 ABS_MT_SLOT 0 /*上报ABS_MT_SLOT事件,也就是触摸点对应的 SLOT。每次上报一个触摸点坐标之前要先使用input_mt_slot函数上报当前触摸点SLOT,触摸点的SLOT其实就是触摸点ID,需要由触摸IC提供*/
 ABS_MT_TRACKING_ID 45 /*根据 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值*/
 ABS_MT_POSITION_X x[0] /*上报触摸点 0的X 轴坐标,使用函数 input_report_abs来完成*/
 ABS_MT_POSITION_Y y[0] /*上报触摸点 0的Y 轴坐标,使用函数 input_report_abs来完成*/
 ABS_MT_SLOT 1 
 ABS_MT_TRACKING_ID 46 
 ABS_MT_POSITION_X x[1] 
 ABS_MT_POSITION_Y y[1] 
 SYN_REPORT /*当所有的触摸点坐标都上传完毕以后就得发送SYN_REPORT事件,使用input_sync函数来完成*/

当一个触摸点移除以后,同样需要通过 SLOT 关联的ABS_MT_TRACKING_ID 来处理

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

4.总结一下, MT协议隶属于linux的 input 子系统,驱动通过大量的 ABS_MT 事件向 linux 内核上报多点触摸坐标数据。根据触摸 IC的不同,分为Type A和Type B两种类型,不同的类型其上报时序不同,目前使用最多的是 Type B 类型

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

/*
@	input_mt_init_slots函数用于初始化MT的输入slots
@	dev:  MT 设备对应的 input_dev,因为MT设备隶属于input_dev。 
@	num_slots:设备要使用的SLOT数量,也就是触摸点的数量
@	flags:其他一些 flags信息,可以采用‘|’运算来同时设置多个 flags标识
@	返回值:0,成功;负值,失败
*/
int input_mt_init_slots(  struct input_dev    *dev,  unsigned int      num_slots, unsigned int  flags) 

/*
@	定义在文件include/linux/input/mt.h中
@	此函数用于Type B 类型,此函数用于产生 ABS_MT_SLOT 事件,告诉内核当前上报的是哪个触摸点的坐标数据
@	dev:  MT 设备对应的 input_dev
@ 	slot:当前发送的是哪个 slot的坐标信息,也就是哪个触摸点
@	返回值:无。 
*/
void input_mt_slot(struct input_dev *dev,   int  slot) 

/*
@	定义在文件drivers/input/input-mt.c 中
@	此函数用于 Type B 类型,用于产生 ABS_MT_TRACKING_ID 和 ABS_MT_TOOL_TYPE事件
@	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,表示触摸点溢出。
@	返回值:无。 
*/
void input_mt_report_slot_state(  struct input_dev *dev, unsigned int tool_type,bool  active) 

/*
@	定义在 文件include/linux/input.h中
@	Type A和Type B 类型都使用此函数上报触摸点坐标信息
@	dev:  MT 设备对应的 input_dev。
@	code:要上报的是什么数据,可以设置为ABS_MT_POSITION_X或ABS_MT_POSITION_Y,也就是X 轴或者Y轴坐标数据
@	value:具体的X 轴或Y轴坐标数据值
*/
void input_report_abs(  struct input_dev *dev,unsigned int      code,   int  value) 

/*
@	定义在文件drivers/input/input-mt.c中
@	函数会获取到具体的触摸点数量,不需要用户给出
@	dev:  MT 设备对应的 input_dev
@	use_count:true,有效的触摸点数量;false,追踪到的触摸点数量多于当前上报的数量
@	返回值:无
*/
void input_mt_report_pointer_emulation(struct input_dev   *dev,   bool   use_count)

面的那么多都是整个框架中所涉及到的未知知识
多点电容触摸驱动的编写框架用到的知识点和框架:
1.多点电容触摸芯片的接口,一般都为I2C接口,因此驱动主框架肯定是 I2C。
2.linux里面一般都是通过中断来上报触摸点坐标信息,因此需要用到中断框架
3.多点电容触摸属于 input子系统,因此还要用到 input子系统框架。
4.在中断处理程序中按照 linux的MT协议上报坐标信息(此协议是多点触摸专用)

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

  • 1.I2C驱动框架(中间的probe函数里面初始化触摸 IC,中断和input子系统)
    当设备树中触摸 IC的设备节点和驱动匹配以后probe函数会执行
  /* 设备树匹配表 */  
 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("zuozhongkai"); 
  • 2.初始化触摸IC、中断和 input子系统
static int xxx_ts_probe(struct i2c_client *client, const struct 
i2c_device_id *id) 
{ 
 	struct input_dev *input; 
    /* 1、初始化 I2C               */ 
    ...... 
    /*包括芯片的相关 IO,比如复位、中断等 IO 引脚,然后就是芯片本身的初始化,也就是配置触摸芯片的相关寄存器*/
   
    /* 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; 
    ...... 
    /*因为多点电容触摸属于 input子系统,申请到 input_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); /*初始化多点电容触摸的 slots*/
    ...... 
   
    /* 5,注册 input_dev */ 
    input_register_device(input); /*系统注册前面申请到的input_dev。*/
    ...... 
 } 
  • 3.上报坐标信息(Type B类型)
    最后就是在中断服务程序中上报读取到的坐标信息
 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; 
 } 

2. 实例应用

1.修改设备树

修改IO
4个 IO,一个复位 IO、一个中断IO、I2C2的 SCL 和SDA。复位 IO 和中断 IO是普通的GPIO,因此这两个IO可以放到同一个节点下去描述,I2C2的SCL和 SDA 属于 I2C2,因此这两个要放到同一个节点下去描述

  • 首先是复位 IO 和中断 IO,imx6ull-alientek-emmc.dts 文件里面默认有个名为“pinctrl_tsc”的节点,如果被删除了的话就自行创建,在此节点下添加触摸屏的中断引脚信息
/*
@	
*/
 pinctrl_tsc: tscgrp { 
   fsl,pins = < 
       MX6UL_PAD_GPIO1_IO09__GPIO1_IO09     0xF080   /* TSC_INT */ 
   >; 
 }; 

触摸屏复位引脚使用的是 SNVS_TAMPER9,因此复位引脚信息要添加到 iomuxc_snvs 节点下,在 iomuxc_snvs 节点新建一个名为 pinctrl_tsc_reset 的子节点

 pinctrl_tsc_reset: tsc_reset { 
     fsl,pins = < 
       MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09 0x10B0 
       >; 
 }; 
  • 继续添加 I2C2 的 SCL 和 SDA 这两个 IO 信息.x6ull-alientek-emmc.dts 里面默认就已经添加了 I2C2 的 IO 信息,这是 NXP 官方添加的,所以不需要我们去修改.
 pinctrl_i2c2: i2c2grp { 
   fsl,pins = < 
       MX6UL_PAD_UART5_TX_DATA__I2C2_SCL 0x4001b8b0 
       MX6UL_PAD_UART5_RX_DATA__I2C2_SDA 0x4001b8b0 
   >; 
 }; 

注意:要防止引脚冲突

添加设备节点
需要向 I2C2 节点下添加一个子节点,此子节点用于描述 FT5426,添加完成以后的 I2C2 节点

&i2c2 { 
   clock_frequency = <100000>; 
   pinctrl-names = "default"; 
   pinctrl-0 = <&pinctrl_i2c2>; 
   status = "okay"; 

    /* zuozhongkai FT5406/FT5426 */ 
    ft5426: ft5426@38 { 
        compatible = "edt,edt-ft5426"; 
        reg = <0x38>; /*器件地址为 0x38*/
        pinctrl-names = "default"; 
        pinctrl-0 = <&pinctrl_tsc /*复位 IO 和中断 IO 所使用的节点为 pinctrl_tsc和 pinctrl_tsc_reset*/
                    &pinctrl_tsc_reset >;  
        interrupt-parent = <&gpio1>;  
        interrupts = <9 0>;  
        reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;/*复位IO 对应的GPIO为GPIO5_IO09*/   
        interrupt-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;  /*中断IO对应的GPIO为 GPIO1_IO09。*/
    }; 
 }; 

2.编写设备驱动

总线驱动官方已经写好过了,我们只需要写设备驱动。

#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("zuozhongkai");

3. 驱动测试

1.手动加载加载驱动

  • 将上一小节编译出来 ft5x06.ko 拷贝到
    rootfs/lib/modules/4.1.15目录中,启动开发板,进入到目录 lib/modules/4.1.15中
depmod    /*第一次加载驱动的时候需要运行此命令*/

modprobe ft5x06.ko  /*加载驱动模块*/

hexdump /dev/input/event2    /*开始进行接触测试*/

2.将驱动添加到内核

  • 前面我们一直将触摸驱动编译为模块,每次系统启动以后在手动加载驱动模块,这样很不方便。当我们把驱动调试成功以后一般都会将其编译到内核中,这样内核启动以后就会自动加载驱动,不需要我们再手动 modprobe 了
    1、将驱动文件放到合适的位置,在内核源码中找个合适的位置将 ft5x06.c放进去,ft5x06.c是个触摸屏驱动,因此我们需要查找一下 linux 内核里面触摸屏驱动放到了哪个目录下。linux 内核里面将触摸屏驱动放到了 drivers/input/touchscreen 目录下,因此我们要将 ft5x06.c拷贝到此目录下。

2.修改对应的Makefile
学新通
修改完成以后重新编译 linux 内核,然后用新的 zImage 启动开发板。

/*将触摸屏驱动添加到 linux 内核里面以后触摸屏对应的是 event1,而不是前面编译为模块对应的event2,这一点一定要注意*/

hexdump  /dev/input/event1   //查看触摸屏原始数据上报信息==开始测试

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgjahia
系列文章
更多 icon
同类精品
更多 icon
继续加载