触摸屏驱动
a.先来了解下基本知识
1.ft5x6触摸屏驱动通过I2C接口与CPU进行连接,服务于I2C总线,同时触摸屏属于又是输入设备,因此又隶属于输入子系统。
2.ft5x06通过I2C接口连接CPU,直接将数字信号放入内部寄存器,(一些触摸屏管脚发出电压值为模拟信号,需要接ADC转换至数字信号供CPU读取)
3.ft5x06触摸屏的各个寄存器如下图
由寄存器表可以知道,当前按键的个数在02h地址的第四位,每个触摸点的X值的高8位都是在该地址内容的低4位,低8位在下一个地址中(用于之后的原始数据拼凑成X,Y值和触摸点个数值)
b.直接上代码(代码中有很详细的注释)
1.触摸屏驱动程序(文件名:tiny_ts.c)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define TS_NAME "ft5x0x_ts"
#define FT_FT5X0X_PT_MAX 5 //多点触摸最大值
struct ts_info{
int x;
int y;
int id;
};
struct ts_desc{
int irq;
struct i2c_client* ts_cli;
struct input_dev* ts_input;
int x_max; //触摸屏最大的X值,用于设置input_dev
int y_max; //触摸屏最大的Y值, 用于设置input_dev
int pressure_max; //触摸屏最大的压力值
int ts_num; //当前触摸的点数
struct ts_info ts_info[FT_FT5X0X_PT_MAX]; //触摸点信息的数组,最大可产生5个触摸点
struct work_struct ts_work_queue;
};
static struct ts_desc *ts;
/*I2C读取函数
*client:i2c_lient结构体
*buf:需要读取的地址
*count:需要读取寄存器的个数
*/
static int ts_i2c_read(struct i2c_client* client, char *buf, int count)
{
/*分析:传参过来buf[0] == 0
*然后先写,当len = 1时,表示写入buf数组的第一个元素buf[0]
*写入的第一个元素为0值,意思是告诉寄存器我要从0x00地址开始读取数据了
*然后再读,读取长度为count,表示我要从我刚才写入进去的0x00地址开始读取
*读取count字节数据(等于读取了0x00地址到0x00偏移了count-1地址空间的数据)
*/
struct i2c_adapter *adapt;
struct i2c_msg ts_msg[2];
adapt = client->adapter;
ts_msg[0].addr = client->addr;
ts_msg[0].flags = 0; //写,告诉触摸屏我要读哪个地址的数据
ts_msg[0].buf = buf;
ts_msg[0].len = 1; //长度为1,表明写入第一个数组的元素buf[0] = 0;
//代表我要从0地址开始读取数据
ts_msg[1].addr = client->addr;
ts_msg[1].flags = I2C_M_RD;
ts_msg[1].buf = buf; //读取的数据存入的数组
ts_msg[1].len = 31; //读取从0地址最多偏移30字节的地址全部的数据,存入buf
int ret = i2c_transfer(adapt, ts_msg, sizeof(ts_msg) / sizeof(ts_msg[0]));
if(ret != 2)
printk("fail to i2c_transfer read data\n");
return ret;
}
static int ts_i2c_data_analysis(void)
{
char buf[32] = {0};
//这里31字节是读取0x到0x1E之间所有地址的数据
int ret = ts_i2c_read(ts->ts_cli, buf, 31);
if(ret != 2){
printk("fail to ts_i2c_data_analysis\n");
return -1;
}
ts->ts_num = buf[2] & 0xf; //获取当前触摸点数,触摸点数为0x2地址的低四位
/*根据触摸屏手册的数据位,进行移位操作,组成每个触摸点的X,Y值*/
switch(ts->ts_num){
case 5:
ts->ts_info[4].x = (short)buf[0x1c] | (((short)(buf[0x1b]) & 0x0f) << 8);
ts->ts_info[4].y = (short)buf[0x1e] | (((short)(buf[0x1d]) & 0x11) << 8);
ts->ts_info[4].id = buf[0x1d] >> 4;
case 4:
ts->ts_info[3].x = (short)buf[0x16] | (((short)(buf[0x15]) & 0x0f) << 8);
ts->ts_info[3].y = (short)buf[0x18] | (((short)(buf[0x17]) & 0x11) << 8);
ts->ts_info[3].id = buf[0x17] >> 4;
case 3:
printk("3 point\n");
ts->ts_info[2].x = (short)buf[0x10] | (((short)(buf[0x0f]) & 0x0f) << 8);
ts->ts_info[2].y = (short)buf[0x12] | (((short)(buf[0x0b]) & 0x11) << 8);
ts->ts_info[2].id = buf[0x11] >> 4;
case 2:
ts->ts_info[1].x = (short)buf[0x0a] | (((short)(buf[0x09]) & 0x0f) << 8);
ts->ts_info[1].y = (short)buf[0x0c] | (((short)(buf[0x0b]) & 0x0f) << 8);
ts->ts_info[1].id = buf[0xb] >> 4;
case 1:
ts->ts_info[0].x = (short)buf[0x04] | (((short)(buf[0x03]) & 0x0f) << 8);
ts->ts_info[0].y = (short)buf[0x06] | (((short)(buf[0x05]) & 0x0f) << 8 );
ts->ts_info[0].id = buf[0x5] >> 4;
break;
default:
return ret;
}
return ret;
}
/*中断底半部处理函数(工作队列机制实现)*/
static void work_read_ts_data(struct work_struct *work)
{
int i = 0;
if(ts_i2c_data_analysis() < 0){
printk("fail to work_read_ts_data\n");
return;
}
if(0 == ts->ts_num){ //表明现在没有触摸点,即抬起,中断处理完毕
input_mt_sync(ts->ts_input);
input_sync(ts->ts_input);
return;
}
for(i = 0; i < ts->ts_num; i++){ //上报多个数据
input_report_abs(ts->ts_input, ABS_MT_POSITION_X, ts->ts_info[i].x);
input_report_abs(ts->ts_input, ABS_MT_POSITION_Y, ts->ts_info[i].y);
input_report_abs(ts->ts_input, ABS_MT_TRACKING_ID, ts->ts_info[i].id);
}
input_mt_sync(ts->ts_input); //每个触摸点都需要上报多个,1个可以不需要
input_sync(ts->ts_input);
}
/*中断处理函数*/
static irqreturn_t ts_irq_handler(int irq, void *dev_id)
{
schedule_work(& (ts->ts_work_queue));//执行中断底半部函数
return IRQ_HANDLED;
}
static int ts_probe(struct i2c_client *cli, const struct i2c_device_id *idtable)
{
printk("-------%s--------\n", __FUNCTION__);
/*这个结构体的内容取自mach-tiny4412.c文件
static struct ft5x0x_i2c_platform_data ft5x0x_pdata = {
.gpio_irq = EXYNOS4_GPX1(6),
.irq_cfg = S3C_GPIO_SFN(0xf),
.screen_max_x = 800,
.screen_max_y = 1280,
.pressure_max = 255,
};*/
/*1.申请input_event对象,从i2c子系统中获取平台数据用于初始化该对象
* (也可以不获得平台数据,直接定义x,y等信息也行)
* 2.设置input_dev, 设置哪种事件,哪种数据类型,并给数据类型设置范围
* 3.初始化工作队列(用于触摸时产生中断时的底半部处理)
* 4.申请中断,注册中断处理顶半部函数
*/
//mach-tiny4412.c文件中ft5x6的数据类型在其中定义
static struct ft5x0x_i2c_platform_data *pdata;
ts = kzalloc(sizeof(struct ts_desc), GFP_KERNEL);
if(!ts){
return -1;
}
/*记录一下i2c_client指针,用于I2C收发数据*/
ts->ts_cli = cli;
/*从i2c_clien结构体中拿取平台数据*/
/*在mach-tiny4412.c文件中厂商已经定义了需要的数据*/
/*也可以不从i2c总线中拿数据,自己设定数据也可以,现在主要是为了移植性好一点*/
/*采用从i2c总线中拿数据*/
pdata = cli->dev.platform_data; //数据会放在i2c_client.dev.platfrom_data中
if(pdata == NULL){
return -1;
}
printk("success platfrom_data\n");
printk("pdata = %p\n", pdata);
ts->irq = gpio_to_irq(pdata->gpio_irq);
ts->x_max = pdata->screen_max_x;
ts->y_max = pdata->screen_max_y;
if(ts->x_max != 800 || ts->y_max != 480)
{
ts->x_max = 800;
ts->y_max = 480;
}
ts->pressure_max = pdata->pressure_max;
ts->ts_input = input_allocate_device();
if(NULL == ts->ts_input){
printk("fail to input_allocate_device\n");
goto err_input_alloc;
}
printk("success platform data\n");
set_bit(EV_SYN, ts->ts_input->evbit);
set_bit(EV_ABS, ts->ts_input->evbit);
set_bit(ABS_MT_PRESSURE, ts->ts_input->absbit);
set_bit(ABS_MT_POSITION_X, ts->ts_input->absbit);
set_bit(ABS_MT_POSITION_Y, ts->ts_input->absbit);
set_bit(ABS_MT_TRACKING_ID, ts->ts_input->absbit); //最大支持几点触摸
/*设置数据类型的范围*/
input_set_abs_params(ts->ts_input, ABS_MT_PRESSURE, 0, ts->pressure_max , 0, 0);
input_set_abs_params(ts->ts_input, ABS_MT_POSITION_X, 0, ts->x_max , 0, 0);
input_set_abs_params(ts->ts_input, ABS_MT_POSITION_Y, 0, ts->y_max, 0, 0);
input_set_abs_params(ts->ts_input, ABS_MT_TRACKING_ID, 0, FT_FT5X0X_PT_MAX, 0, 0);
set_bit(INPUT_PROP_DIRECT, ts->ts_input->propbit); //表示上面设置的X,Y值与屏幕实际像素一一对应
/*下面设置的内容会在sys/class/input...下看到,主要用于给应用程序提示*/
ts->ts_input->name = "input_ft5x0x_ts"; //该名字会在sys/class/input...下看到输入设备的名字
ts->ts_input->id.bustype = BUS_I2C; //表明该输入设备隶属于I2C总线
ts->ts_input->id.product = 0x14;
ts->ts_input->id.vendor = 0x12;
ts->ts_input->id.version = 3;
int ret = input_register_device(ts->ts_input); //设置完成之后然后注册进输入子系统
if(0 != ret){
printk("fail to input_register_device\n");
goto err_input_register;
}
/*初始化工作队列,注册中断底半部处理函数*/
INIT_WORK(& (ts->ts_work_queue), work_read_ts_data);
/*硬件操作:申请中断*/
ret = request_irq(ts->irq, ts_irq_handler, IRQ_TYPE_EDGE_BOTH, "ts_irq", NULL);
if(ret < 0){
printk("fail to request_irq\n");
goto err_irq_request;
}
return 0; //正常情况返回0
err_irq_request:
cancel_work_sync(&(ts->ts_work_queue));
input_unregister_device(ts->ts_input);
err_input_register:
input_free_device(ts->ts_input);
err_input_alloc:
kfree(ts);
return -1; //异常情况返回-1
}
static int ts_remove(struct i2c_client *cil)
{
free_irq(ts->irq, NULL); //销毁中断
cancel_work_sync(&(ts->ts_work_queue)); //释放工作队列
input_unregister_device(ts->ts_input); //从input子系统移出input_device对象
input_free_device(ts->ts_input); //释放资源
kfree(ts); //释放资源
return 0;
}
struct i2c_device_id id[] = { //i2c_driver匹配的信息
[0] = { //注意:有限匹配id_table中的名字
.name = TS_NAME, //其次才会匹配i2c_driver中driver中的名字
.driver_data = 0,
},
[1] = {
.name = "ft510x_ts",
.driver_data = 0,
},
};
struct i2c_driver ts_driver = { //定义一个i2c_driver对象
.probe = ts_probe,
.remove = ts_remove,
.id_table = id,
.driver = {
.name = TS_NAME,
.owner = THIS_MODULE,
},
};
static int __init tiny4412_ft_init(void) //模块入口
{
printk("-------%s--------\n", __FUNCTION__);
return i2c_add_driver(&ts_driver); //向i2c总线中添加该设备
}
static void __exit tiny4412_ft_exit(void) //模块出口
{
i2c_del_driver(&ts_driver); //退出时将该设备移出总线
}
module_init(tiny4412_ft_init);
module_exit(tiny4412_ft_exit);
MODULE_LICENSE("GPL"); //声明协议
2.测试程序(文件名ts_test.c)
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd;
int ret = 0;
struct input_event myevent;
//设备节点文件根据不同的文件系统会有差异,有的文件系统是/dev/event*
fd = open("/dev/input/event1", O_RDWR);
if(fd < 0){
printf("fail to open!\n");
}
while(1)
{
ret = read(fd, &myevent, sizeof(myevent));
if(ret < 0){
printf("fail to read\n");
return -1;
}
if(myevent.type == EV_ABS){
if(myevent.code == ABS_MT_POSITION_X){
printf("x = %d ", myevent.value);
}
if(myevent.code == ABS_MT_POSITION_Y){
printf("y = %d\n", myevent.value);
}
if(myevent.code == ABS_MT_TRACKING_ID){
printf("id = %d\n", myevent.value);
}
}
}
close(fd);
return 0;
}
3.Makefile文件
obj-m +=tiny_ts.o
TAPP=ts_test
LMAKE=/home/linux/nfsroot/yingjian/neiheyuan/linux-3.5
module:
make -C $(LMAKE) M=$(shell pwd) modules
arm-linux-gcc $(TAPP).c -o $(TAPP)
.PHYON:
clean:
rm *.mod.c *.o *.ko *.order *.symvers
install:
cp ./*.ko /home/linux/nfsroot/nfs/home/neoway/ts/
cp ./$(TAPP) /home/linux/nfsroot/nfs/home/neoway/ts/
我在实现的时候,发现从0x02地址处的第四位读取触摸点的个数时,最大个数为2,并不是5,这一点我也不知道为什么。附上触摸屏触摸点个数寄存器的图, 希望读者解决之后指教
手册上已经说的很清楚最大支持5点触摸,我也不知道为什么我从这个低四位里面读取的最大数是2,应该是5才对啊。