欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

kernel(十九)触摸屏FT5x06

程序员文章站 2022-03-22 17:09:09
...

FT5x06原厂芯片资料下载https://download.csdn.net/download/jerrygou/10653027

FT5x06驱动完整代码下载https://download.csdn.net/download/jerrygou/10653022

一、电容触控芯片FT5x06

TQ210的电容触摸屏控制芯片是FT5x06。
           FT5x06引出了6根脚,分别是VCC、GND、I2CSDA、I2CSCL、INT和RESET,虽然INT脚不是必须的,但是开发高效省资源的触屏驱动程序往往都采用中断方式,下面是GT811的引脚图:

kernel(十九)触摸屏FT5x06

 

使用万能表实际测量了一下触控模块的各个引脚,实际线序是GND、SDA、SDL、INT、RESET和VDD。

kernel(十九)触摸屏FT5x06

LXpwmTout3连接到了FT5x06的RESET脚上,然后与GPIO0_3链接,XEINT14连接到FT5x06的INT脚上,与GPIO1_6链接,因此,需要将GPIO0_3配置为终端输入引脚,GPIO1_6配置为输出引脚。

二、 I2C驱动编写

I2C驱动也是基于总线结构的,不过分为两种,一种是Legacy方式,另一种是New Style方式,其中,Legacy方式在新内核中已经不支持了,不过韦东山老师的视频中还是分析的Legacy方式,New Style方式你可以自己用Source Insight追踪分析一下,具体的可以参考下面的代码。

ft5x06_touch.c

/*
 * driver/input/touchscreen/ft5x06_touch.c
 */
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/input.h>
#include <linux/device.h>
#include <linux/hrtimer.h>


#define FT5X06_I2C_BUS		(2)
#define FT5X06_I2C_ADDRESS	(0x38)

#define SCREEN_MAX_WIDTH	(800)
#define SCREEN_MAX_HEIGHT	(480)

#define TOUCH_MAX_WIDTH	    (800)
#define TOUCH_MAX_HEIGHT    (480)

#define K_BACK				(KEY_BACK & KEY_MAX)
#define K_MENU				(KEY_MENU & KEY_MAX)
#define K_HOME				(KEY_HOME & KEY_MAX)
#define K_SEARCH			(KEY_SEARCH & KEY_MAX)

struct point_node
{
	unsigned int x;
	unsigned int y;
};

struct ft5x06_ts_data {
	struct workqueue_struct * wq;
	struct work_struct work;
	struct i2c_client * client;
	struct input_dev * input_dev;

	struct point_node node[5];
};

typedef enum
{
	ERR_OK,
	ERR_MODE,
    ERR_READID,
    ERR_ERASE,
    ERR_STATUS,
    ERR_ECC,
    ERR_DL_ERASE_FAIL,
    ERR_DL_PROGRAM_FAIL,
    ERR_DL_VERIFY_FAIL
} E_UPGRADE_ERR_TYPE;

typedef unsigned char					FTS_BYTE;
typedef unsigned short					FTS_WORD;
typedef unsigned int					FTS_DWRD;
typedef unsigned char					FTS_BOOL;

#define FTS_NULL						0x0
#define FTS_TRUE						0x1
#define FTS_FALSE						0x0

#define FTS_PACKET_LENGTH				128

static int ft5x06_read_coordinates(struct i2c_client * client, uint8_t * buf, uint8_t len)
{
	struct i2c_msg msgs[2];
	uint8_t msgbuf[1] = { 0x0 };
	int ret;

	msgs[0].flags = !I2C_M_RD;
	msgs[0].addr = client->addr;
	msgs[0].len = 1;
	msgs[0].buf = &msgbuf[0];

	msgs[1].flags = I2C_M_RD;
	msgs[1].addr = client->addr;
	msgs[1].len = len;
	msgs[1].buf = &buf[0];

	ret = i2c_transfer(client->adapter, msgs, 2);
	return ret;
}

static void ft5x06_ts_work_func(struct work_struct * work)
{
	struct ft5x06_ts_data * ts = container_of(work, struct ft5x06_ts_data, work);
	uint8_t buf[32] = { 0 };
	unsigned int X, Y;
	unsigned int x, y, event, id;
	int i;

	ft5x06_read_coordinates(ts->client, buf, 32);

	for(i = 0; i < 5; i++)
	{
		X = (buf[i*6 + 3])<<8 | buf[i*6 + 4];
		Y = (buf[i*6 + 5])<<8 | buf[i*6 + 6];

		x = X & 0xfff;
		y = Y & 0xfff;
	
		event = (X >> 14) & 0x3;    //触摸点所发生的事件
		id = (Y >> 12) & 0xf;       //触摸点ID

		/* Remove Muti finger */
         if(id >= 0 && id <= 4) //五点触摸
		//if(id == 0)   //一点触摸
		{
			ts->node[id].x = x;
			ts->node[id].y = y;
            printk("[%d]event: event = %4d\n", id, event);

			if((event == 0) || (event == 0x02)) //按下或接触
			{
                //上报触屏事件
				input_report_abs(ts->input_dev, ABS_X, ts->node[id].x); //X坐标
				input_report_abs(ts->input_dev, ABS_Y, ts->node[id].y); //Y坐标
				input_report_abs(ts->input_dev, ABS_Z, 0);
				input_report_abs(ts->input_dev, ABS_PRESSURE, 1);
				input_sync(ts->input_dev);

				//printk("[%d]down: x = %4d, y = %4d\n", id, ts->node[id].x, ts->node[id].y);
			}
			else if(event == 0x01)  //抬起
			{
				//input_report_abs(ts->input_dev, ABS_X, ts->node[id].x);
				//input_report_abs(ts->input_dev, ABS_Y, ts->node[id].y);
				input_report_abs(ts->input_dev, ABS_Z, 0);
				//input_report_key(ts->input_dev, BTN_TOUCH, 0);
				input_report_abs(ts->input_dev, ABS_PRESSURE, 0);
				input_sync(ts->input_dev);

				//printk("[%d]up: x = %4d, y = %4d\n", id, ts->node[id].x, ts->node[id].y);
			}
		}
	}

	input_sync(ts->input_dev);  //同步用于告诉input core子系统报告结束
	enable_irq(ts->client->irq);
}

static irqreturn_t ft5x06_ts_irq_handler(int irq, void * dev_id)
{
	struct ft5x06_ts_data * ts = dev_id;

	disable_irq_nosync(ts->client->irq);
	queue_work(ts->wq, &ts->work);

	return IRQ_HANDLED;
}

static int ft5x06_ts_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
	int ret;
	struct ft5x06_ts_data * ts;

	//检测适配器是否支持I2C_FUNC_I2C通讯方式
	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA))
		return -ENODEV;

	ts = kzalloc(sizeof(*ts), GFP_KERNEL);	//(注:GFP_KERNEL是内核内存分配时最常用的,无内存可用时可引起休眠)
	if(!ts)
		return -ENOMEM;

	ts->wq = create_workqueue("ft5x06_wq");	//创建工作queue
	if(!ts->wq)
	{
		kfree(ts);
		return -ENOMEM;
	}

	INIT_WORK(&ts->work, ft5x06_ts_work_func);	//创建工作,关联工作函数
	ts->client = client;
	i2c_set_clientdata(client, ts);

	ts->input_dev = input_allocate_device();	//分配一个设备结构体,并初始化
	if (ts->input_dev == NULL)
	{
		destroy_workqueue(ts->wq);	//释放工作queue
		kfree(ts);
		return -ENOMEM;
	}

	ts->input_dev->name = client->name;
	ts->input_dev->phys = "input/ts";
	ts->input_dev->id.bustype = BUS_I2C;
	ts->input_dev->id.vendor = 0xDEAD;
	ts->input_dev->id.product = 0xBEEF;
	ts->input_dev->id.version = 10427;

    set_bit(EV_SYN, ts->input_dev->evbit);
    set_bit(EV_KEY, ts->input_dev->evbit);
    set_bit(EV_ABS, ts->input_dev->evbit);
    set_bit(ABS_X,  ts->input_dev->absbit);
    set_bit(ABS_Y,  ts->input_dev->absbit);
    set_bit(ABS_Z,  ts->input_dev->absbit);
    set_bit(BTN_TOUCH, ts->input_dev->keybit);
	set_bit(BTN_2, ts->input_dev->keybit);

    set_bit(K_BACK, ts->input_dev->keybit);
    set_bit(K_MENU, ts->input_dev->keybit);
    set_bit(K_HOME, ts->input_dev->keybit);
    set_bit(K_SEARCH, ts->input_dev->keybit);

	//设置范围参数
    input_set_abs_params(ts->input_dev, ABS_MT_WIDTH_MAJOR, 0, 255, 0, 0);
    input_set_abs_params(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
	//表示:X轴范围是0到SCREEN_MAX_WIDTH,数据误差是-4到+4,中心平滑位置是0 .
    input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, SCREEN_MAX_WIDTH, 0, 0);
    input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, SCREEN_MAX_HEIGHT, 0, 0);
    input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 1, 0, 0);

    ret = input_register_device(ts->input_dev);	//注册到输入型子系统
	if(ret)
	{
		destroy_workqueue(ts->wq);
		kfree(ts);
		return ret;
	}	

	irq_set_irq_type(client->irq, IRQ_TYPE_EDGE_FALLING);	//下降沿触发
	ret = request_irq(client->irq, ft5x06_ts_irq_handler, IRQ_TYPE_EDGE_FALLING, client->name, ts);
	if(ret)
	{
		destroy_workqueue(ts->wq);
		kfree(ts);
		return ret;
	}
    
	return 0;
}

static int ft5x06_ts_remove(struct i2c_client *client)
{
	struct ft5x06_ts_data * ts = i2c_get_clientdata(client);
	printk("==ft5x06_ts_remove=\n");
	
	free_irq(client->irq, ts);
	i2c_set_clientdata(client, NULL);
	input_unregister_device(ts->input_dev);
	if(ts->input_dev)
		kfree(ts->input_dev);
	kfree(ts);

	return 0;
}

static const struct i2c_device_id ft5x06_ts_id[] = {
	{ "Louis210-ts", 0},
	{ }
};

MODULE_DEVICE_TABLE(i2c, ft5x06_ts_id);

static struct i2c_driver ft5x06_ts_driver = {
	.driver        = {
		.name    = "Louis210_ts",
		.owner = THIS_MODULE,
	},
	.probe		= ft5x06_ts_probe,
	.remove		= ft5x06_ts_remove,
	.id_table	= ft5x06_ts_id,
};

module_i2c_driver(ft5x06_ts_driver);

MODULE_AUTHOR("LouisGou");
MODULE_DESCRIPTION("Louis210 ft5x06 Touchscreen Driver");
MODULE_LICENSE("GPL");

这并不是完整的代码,一方面是没有做异常处理,另一方面是没有上报消息,只是简单的驱动了TQ210的触摸屏部分,如果您需要拿去自己略作修改即可使用。

三、 注册TS的I2C模块设备

注册TS的I2C模块很简单,在Linux内核文件arch/arm/mach-s5pv210/mach-Louis210.c文件的I2C通道2结构体中加入TS的I2C地址,也就是0x53,添加后如下

void Louis210_ft5x06_cfg_gpio(struct platform_device *dev)
{
	int err;

	s3c_gpio_cfgall_range(S5PV210_GPD1(0), 2,
			      S3C_GPIO_SFN(2), S3C_GPIO_PULL_UP);

	err = gpio_request(S5PV210_GPH1(6), "GPH1");
	if (err)
		printk(KERN_ERR "#### failed to GPH1(6) for gt801 interrupt\n");

	s3c_gpio_setpull(S5PV210_GPH1(6), S3C_GPIO_PULL_NONE);
	s3c_gpio_cfgpin(S5PV210_GPH1(6),S3C_GPIO_SFN(0xf));
	gpio_free(S5PV210_GPH1(6));
	
	err = gpio_request(S5PV210_GPD0(3), "GPD0");
	if (err)
		printk(KERN_ERR "#### failed to GPH1(6) for gt801 interrupt\n");

	s3c_gpio_setpull(S5PV210_GPD0(3), S3C_GPIO_PULL_NONE);
	s3c_gpio_cfgpin(S5PV210_GPD0(3),S3C_GPIO_SFN(0x1));
	gpio_direction_output(S5PV210_GPD0(3), 0);
	gpio_free(S5PV210_GPD0(3));
}

static struct s3c2410_platform_i2c Louis_ft5x06_pdata __initdata = {
   .flags        = 0,
   .slave_addr    = 0xaa>>1,
   .frequency    = 1000*100,
   .sda_delay    =  10,
   .cfg_gpio = Louis210_ft5x06_cfg_gpio,
};

static struct i2c_board_info smdkv210_i2c_devs2[] __initdata = {
	/* To Be Updated */
	{ I2C_BOARD_INFO("Louis210-ts", 0x38),
	  .irq = IRQ_EINT(14),
	  .platform_data  = &Louis_ft5x06_pdata,	
	},
};

运行结果:

kernel(十九)触摸屏FT5x06

kernel(十九)触摸屏FT5x06

四、tslib测试教程(ubuntu)

编译好的tslib文件https://download.csdn.net/download/jerrygou/10640507

1. 安装git

sudo apt-get install git

2. 下载最新的tslib

git clone https://github.com/kergoth/tslib

3. 安装auto

sudo apt-get install autoconf  automake  libtool

4. 编译tslib(配置成make.sh脚本

#!/bin/sh
./autogen.sh 
mkdir tmp
echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache
./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
make
make install

如果报错:

../src/.libs/libts.so: undefined reference to `EVIOCGPROP'
collect2: ld returned 1 exit status
Makefile:579: recipe for target 'ts_test' failed

修改tslib.h文件,添加

#define EVIOCGPROP(len)        _IOC(_IOC_READ, 'E', 0x09, len)        /* get device properties */

5. 安装tslib

cd tmp
cp * /nfsroot/rootfs -rfd

6. 配置tslib

修改 /etc/ts.conf
将行
# module_raw input
改为:
module_raw input
(实际上就是去掉高行的#号和第一个空格)

7. 配置tslib运行环境变量(添加至/rootfs/etc/profile文件中

export TSLIB_TSDEVICE=/dev/input/event0  //这里需要根据自己的event位置进行修改,新内核在/dev/input/event*
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0

8. 校正(电容屏实际上不需要校正,仅为了测试触屏驱动

运行ts_calibrate,并根据提示进行校正
如果报错 ts_setup: No such file or directory等类似错误,这是因为上一步环境变量配置失败,需要重新设置

kernel(十九)触摸屏FT5x06

9. *画图

运行ts_test,点击draw按钮,可以*画图,效果如下图。

kernel(十九)触摸屏FT5x06

五、小结

本文中列举的代码是简单的实现了触摸坐标获取,没有实现触摸消息上报等操作,这些操作需要自己来实现。

kernel(十九)触摸屏FT5x06

六、触摸屏驱动测试

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <signal.h>
#include <linux/fb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/input.h>

int main(int argc, char** argv) 
{
    int tsfd;
    int key_value, count;
    struct input_event ev_key;
    
    printf("hello tq210\n");
    
    tsfd = open("/dev/input/event0", O_RDWR);
    if (tsfd == -1) {
        perror("open device error");
        exit(-1);
    }
    printf("open device %d\n", tsfd);
    
    for(;;){
        count = read(tsfd, &ev_key, sizeof(struct input_event));
        
        if(EV_KEY == ev_key.type)
            printf("type:EV_KEY, code:0x%x, value:0x%x\n", ev_key.type, ev_key.code, ev_key.value);
        else if(EV_ABS == ev_key.type)
            printf("type:EV_ABS, code:0x%x, value:0x%x\n", ev_key.type, ev_key.code, ev_key.value);
        else if(EV_SYN == ev_key.type)
            printf("[ ... syn event]\n");
            
    }

    close(tsfd);
    return 0;
}

七、报错

运行报错:tslib: Selected device is not a touchscreen (must support ABS and KEY event types)

两种情况:一种内核未加入触摸屏的支持,另一种情况是将TSLIB_TSDEVICE=/dev/input/event0 
改为 TSLIB_TSDEVICE=/dev/input/event1
 
解决办法:
1)内核加入触摸屏的支持
内核和文件系统编译时加入触摸屏支持:
一、内核配置支持触摸屏
 
Device Drivers  --->
    Input device support  --->
        (800) Horizontal screen resolution
        (480) Vertical screen resolution
        <*> Event interface
        [*] Touchscreens --->
            <*> Louis210 FT5216 touchscreen driver 

 

相关标签: LCD kernel