linux i2c设备驱动之触控芯片驱动简述

本博客参考了https://blog.csdn.net/jklinux/article/details/74561352 ,在之前基础上扩展

现实中lcd的显示部分与触摸部分实际是分开的。

我们可以形象的理解为:电容屏的触摸相当于在lcd屏上覆盖一个透明的矩阵键盘, 当用户按下时,可以获取用按下的坐标位置.
通常情况不管是电阻还是电容屏,现都使用一个触控芯片,用于处理用户的操作坐标及实现与SOC的坐标数据的交互.

电阻的触控芯片有: tsc2046, 还有国内的: xx2046

电容的触控芯片有: ft5x06(9寸屏以下使用), gt9271(9寸屏以上用)

============

电容屏说明:主要围绕上面我们使用较多的芯片的来说

这里写图片描述
注意: 屏越大, tx/rx线就越多. tx线相当于矩阵键盘里的列线, rx线相当于矩阵键盘里的行线.

这里写图片描述
TP表示触摸屏上的触摸膜
Host表示我们的板
整个图的意思:表示ft5x06触控芯片, 会自动处理按下坐标的事件,会通过int脚(低电平工作)通知我们的SOC有坐标事件发生,然后我们通过i2c接口把坐标数据读取回去。也就是对我们来说, 触控芯片就是一个i2c设备.

i2c设备得有设备地址, 我用过的ft5306的地址有: 0x2b(这个在个人理解上,是很老的一批芯片使用的地址), 0x38(??), 设备地址可以用i2c_new_probed_device函数探测出来
==================
1>      ft5x06芯片内部也有很多寄存器, 寄存器上的值需要通过i2c按口来访问:
这里写图片描述

===========
2>       ft5x06的i2c读写时序:

这里写图片描述
其中Data Address表示芯片内部的寄存器地址
///////
这里写图片描述
从触控芯片读取寄存器的值的时序. 但这里没有指定从哪个寄存器开始读。有些ft5306默认是从地址为0的寄存器开始读, 有些必须用下面时序来指定要开始读寄存器的地址才可以

这里写图片描述

//  这个用于指定开始读数据的寄存器的地址

/linux-3.4.112/drivers/input/touchscreen  可以在内核源当中找到这几种常见的触摸芯片的驱动

//电容的触控芯片有: ft5x06(9寸屏以下使用), gt9271(9寸屏以上用) ,在编程上 两者的主要使用区别在于:后者需要每下一次操作之前把寄存器的值清0(后续补贴上gt9271代码及差异之处

====================

电阻屏的说明:

电阻的触控芯片有: tsc2046, 还有国内的: xx2046基本都是最后兼容到tsc2046芯片上。

当然还有很大一部分的芯片是自带有这部分的触摸功能的。如s3c2440 ,6818等

我们用一部分的hdmi的触摸屏都是使用的电阻屏,对应的触控芯片会有不一样(比如,树梅派的屏大致的原理是把24位色的转化成16位色,显示在屏幕上。)

使用的电阻屏,以四线电阻屏比较多。

.......

具体原理(待补上)

.......

我们能够知道,四线屏的原理是最后采集到的ADC值,最后我们需要使用移植tslib第三方库 来实现我们获得的电压值到坐标值的转换(这里的内容参考的s3c2440的lcd屏驱动的实现来描述的)。而电容屏是不需要这个步骤,直接能从触控芯片输出中得到坐标值。

====================

芯片的操作:

读取ft5306芯片内部地址为0xa3的寄存器的值:


#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

int myprobe(struct i2c_client *cli, const struct i2c_device_id *id)
{
    struct i2c_msg msgs[2];
    char reg_addr = 0xa3; //读取ft5306芯片内部地址为xa3寄存器的值 
    char data;

    printk("in myprobe ...%s, %x\n", cli->name, cli->addr);
    // set data address
    msgs[0].addr = cli->addr;
    msgs[0].flags = 0; //write
    msgs[0].len = 1; //要发出的数据长度,多少字节
    msgs[0].buf = &reg_addr;    
    if (i2c_transfer(cli->adapter,  &msgs[0], 1) < 0)
    {
        printk("i2c_transfer0 failed ...\n");
        return -ENODEV;
    }
    // read reg_addr 0xa3
    msgs[1].addr = cli->addr;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = 1;
    msgs[1].buf = &data; 
    i2c_transfer(cli->adapter,  &msgs[1], 1);

    printk("data = %x\n", data);
    return 0;
}

int myremove(struct i2c_client *cli)
{
    printk("in myremove ...\n");
    return 0;
}


struct i2c_device_id ids[] = {
    {"ft5x0x_ts"},
    {},
};

struct i2c_driver mydrv = {
    .probe = myprobe,
    .remove = myremove,

    .id_table = ids,

    .driver = {
        .name = "myi2c",
    },  
};

module_i2c_driver(mydrv);
MODULE_LICENSE("GPL");

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
当触屏有按下或松手事件发生时, 触控芯片会通过int脚发出通知信号. 设备驱动里在中断处理函数里读取坐标数据,但调用i2c_transfer函数会堵塞而中断处函数里不可以堵塞的, 所以用工作队列作中断的底半部,在中断的底半部里读取坐标.

实现标准的输入设备驱动,还需要在驱动加入输入设备的驱动模型.
为了方便QT程序的应用,实现单点触摸输入设备:
主要驱动模型:

    input_dev初始化
mydev->name = "my ts";
mydev->evbit[0] = BIT_MASK(EV_KEY)|BIT_MASK(EV_ABS); //支持事件类型, 触摸屏的坐标是绝对坐标
mydev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);//触摸按键支持

    set_bit(ABS_X, mydev->absbit); // 设置此输入设备的绝对坐标数据里有x, y, pressure数据
    set_bit(ABS_Y, mydev->absbit);
    set_bit(ABS_PRESSURE, mydev->absbit);

input_set_abs_params(mydev, ABS_X, 最小值, 最大值, 物理误差, 误差); //x轴的最小/大值
input_set_abs_params(mydev, ABS_Y, 0, 0x3ff, 0, 0);
input_set_abs_params(mydev, ABS_PRESSURE, 0, 1, 0, 0);

    采到坐标数据后:
        input_report_abs(mydev, ABS_X, x); //汇报坐标值
        input_report_abs(mydev, ABS_Y, y);
        input_report_key(mydev, BTN_TOUCH, 1); //汇报按下
        input_report_abs(mydev, ABS_PRESSURE, 1);
        input_sync(mydev);
    注意汇报坐标值时得要汇报按下

    收到up的中断后
    input_report_key(mydev, BTN_TOUCH, 0); //汇报松手
    input_report_abs(mydev, ABS_PRESSURE, 0);
    input_sync(mydev);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

///////////////////////
test.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/input.h>

typedef struct {
    struct workqueue_struct *queue; //  工作队列,用于调度工作任务
    struct work_struct work; // 工作任务,用于中断底半部
    struct i2c_client *cli; // i2c设备
    struct input_dev *dev; //输入设备
}mywork_t;

irqreturn_t ts_irq(int irqno, void *arg)
{
    mywork_t *mywork = (mywork_t *)arg;

    disable_irq_nosync(irqno); //关闭中断,等中断底半部接收坐标后再重新打开中断
    queue_work(mywork->queue, &mywork->work); //底半部的工作安排
    return IRQ_HANDLED;
}

void ts_work(struct work_struct *work) //中断底半部处理函数
{
    mywork_t *mywork = container_of(work, mywork_t, work);
    struct i2c_msg msg;
    struct i2c_client *cli = mywork->cli;
    unsigned char data[32];
    int x, y;


    msg.addr = cli->addr;
    msg.flags = I2C_M_RD;
    msg.len = 32;  //从地址为0的寄存器,连续读32个寄存器的值
    msg.buf = data;

    if (i2c_transfer(cli->adapter, &msg, 1) < 0)
    {
        printk("i2c transfer failed ...\n");
        return;
    }

    if ((data[3]>>6) == 1) //up
    {
        input_report_abs(mywork->dev, ABS_PRESSURE, 0);
        input_report_key(mywork->dev, BTN_TOUCH, 0);
        input_sync(mywork->dev);
    }
    else  //按下
    {
        x = ((data[3]&0xf)<<8) | data[4];
        y = ((data[5]&0xf)<<8) | data[6];


        input_report_abs(mywork->dev, ABS_X, x);
        input_report_abs(mywork->dev, ABS_Y, y);
        input_report_abs(mywork->dev, ABS_PRESSURE, 1);
        input_report_key(mywork->dev, BTN_TOUCH, 1);
        input_sync(mywork->dev);
    }   
    enable_irq(cli->irq); //重新打开中断
}

int myprobe(struct i2c_client *cli, const struct i2c_device_id *id)
{
    int ret;
    mywork_t *mywork;
    struct input_dev *dev;

    mywork = kzalloc(sizeof(*mywork), GFP_KERNEL); //准备i2c设备的数据

    mywork->cli = cli;
    mywork->queue = create_singlethread_workqueue("myts"); //创建工作队列
    INIT_WORK(&mywork->work, ts_work); //初始化工作任务

    dev = input_allocate_device(); //输入设备分配空间

    //初始化输入设备
    dev->name = "myts", 
    dev->evbit[0] = BIT_MASK(EV_KEY)|BIT_MASK(EV_ABS);
    dev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);

    input_set_abs_params(dev, ABS_X, 0, 800, 0, 0);         
    input_set_abs_params(dev, ABS_Y, 0, 480, 0, 0);     
    input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);

    dev->absbit[BIT_WORD(ABS_X)] |= BIT_MASK(ABS_X);
    dev->absbit[BIT_WORD(ABS_Y)] |= BIT_MASK(ABS_Y);
    dev->absbit[BIT_WORD(ABS_PRESSURE)] |= BIT_MASK(ABS_PRESSURE);

    mywork->dev = dev;

    input_register_device(dev); //注册输入设备
    dev_set_drvdata(&cli->dev, mywork);
    ret = request_irq(cli->irq, ts_irq, IRQF_TRIGGER_FALLING, "myts", mywork); //申请中断
    return ret;
}

int myremove(struct i2c_client *cli)
{
    mywork_t *mywork = dev_get_drvdata(&cli->dev);

    free_irq(cli->irq, mywork);
    cancel_work_sync(&mywork->work);
    destroy_workqueue(mywork->queue);
    input_unregister_device(mywork->dev);
    printk("in myremove ...\n");


    return 0;
}


struct i2c_device_id ids[] = {
    {"ft5x0x_ts"},
    {},
};

struct i2c_driver mydrv = {
    .probe = myprobe,
    .remove = myremove,

    .id_table = ids,

    .driver = {
        .name = "myi2c",
    },  
};

moudle_i2c_driver(mydrv);
MODULE_LICENSE("GPL");



猜你喜欢

转载自blog.csdn.net/lailaiquququ11/article/details/80935186