本博客参考了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 = ®_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");