1 电阻触摸屏介绍
如下图所示,该开发板使用的是4线触摸屏,方框标记的引脚是专门用来接收模拟输入信号。
引脚说明:
YM: (Y Minus)触摸屏的Y坐标的负线,也可以用Y -表示
YP : (Y Power)触摸屏的Y坐标的正线, 也可以用Y+表示
XM: (Y Minus)触摸屏的Y坐标的负线, 也可以用X-表示
XP : (Y Power)触摸屏的Y坐标的正线, 也可以用X+表示
1.1 基本原理
电阻触摸屏包含了两个阻性层(电阻阻值均匀),如下图所示
当没有触摸按下时,X层和Y层是分离的,此时就测不到电压。
测量x方向的坐标时:如下图, 把XP接3.3V , XM接0V, YP和YM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时YP就会输出当前X坐标值的1.66V给CPU。
测量y坐标时:如下图, 把YP接3.3V , YM接0V, XP和XM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时XP就会输出当前X坐标值的1.66V给CPU 。
2 触摸屏驱动需要使用的结构体与函数
input_dev结构体分析:
struct input_dev {
void *private;
const char *name; //设备名字
const char *phys; //文件路径,比如 input/buttons
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)]; //表示支持哪类事件,常用有以下几种事件(可以多选)
//EV_SYN 同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
//EV_KEY 键盘事件
//EV_REL (relative)相对坐标事件,比如鼠标
//EV_ABS (absolute)绝对坐标事件,比如摇杆、触摸屏感应
//EV_MSC 其他事件,功能
//EV_LED LED灯事件
//EV_SND (sound)声音事件
//EV_REP 重复键盘按键事件
//(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)
//EV_FF 受力事件
//EV_PWR 电源事件
//EV_FF_STATUS 受力状态事件
unsigned long keybit[NBITS(KEY_MAX)]; //存放支持的键盘按键值
//键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)、BTN_TOUCH(触摸屏的按键)
unsigned long relbit[NBITS(REL_MAX)]; //存放支持的相对坐标值
unsigned long absbit[NBITS(ABS_MAX)]; //存放支持的绝对坐标值,存放下面4个absxxx[]
unsigned long mscbit[NBITS(MSC_MAX)]; //存放支持的其它事件,也就是功能
unsigned long ledbit[NBITS(LED_MAX)]; //存放支持的各种状态LED
unsigned long sndbit[NBITS(SND_MAX)]; //存放支持的各种声音
unsigned long ffbit[NBITS(FF_MAX)]; //存放支持的受力设备
unsigned long swbit[NBITS(SW_MAX)]; //存放支持的开关功能
... ...
/*以下4个数组都会保存在上面成员absbit[]里,数组号为:ABS_xx ,位于include/linux/input.h */
/*比如数组0,标志就是ABS_X,以下4个的absXXX[0]就是表示绝对位移X方向的最大值、最小值... */
/*对于触摸屏常用的标志有:
ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向,比如绘图,越用力线就越粗)* /
int absmax[ABS_MAX + 1]; //绝对坐标的最大值
int absmin[ABS_MAX + 1]; //绝对坐标的最小值
int absfuzz[ABS_MAX + 1]; //绝对坐标的干扰值,默认为0,
int absflat[ABS_MAX + 1]; //绝对坐标的平焊位置,默认为0
... ...
相关函数介绍
struct input_dev *input_allocate_device(void); //向内存中分配input_dev结构体
input_free_device(struct input_dev *dev); //释放内存中的input_dev结构体
input_register_device(struct input_dev *dev); //注册一个input_dev,若有对应的驱动事件,
则在/sys/class/input下创建这个类设备
input_unregister_device(struct input_dev *dev); //卸载/sys/class/input目录下的
input_dev这个类设备
set_bit(nr,p); //设置某个结构体成员p里面的某位等于nr,支持这个功能
/* 比如:
set_bit(EV_KEY,buttons_dev->evbit); //设置input_dev结构体buttons_dev->evbit支持EV_KEY
set_bit(KEY_S,buttons_dev->keybit); //设置input_dev结构体buttons_dev->keybit支持按键”S”
*/
input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);
//设置绝对位移的支持参数
//dev: 需要设置的input_dev结构体
//axis : 需要设置的数组号,常用的有: ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向)//min: axis方向的最小值, max:axis方向的最大值, fuzz: axis方向的干扰值, flat:axis方向的平焊位置
input_report_abs(struct input_dev *dev, unsigned int code, int value);
//上报EV_ABS事件
//该函数实际就是调用的input_event(dev, EV_ABS, code, value);
//*dev :要上报哪个input_dev驱动设备的事件
// code: EV_ABS事件里支持的哪个方向,比如X坐标方向则填入: ABS_X
//value:对应的方向的值,比如X坐标126
input_report_key(struct input_dev *dev, unsigned int code, int value);
//上报EV_KEY事件
input_sync(struct input_dev *dev); //同步事件通知,通知系统有事件上报
struct clk *clk_get(struct device *dev, const char *id);
//获得*id模块的时钟,返回一个clk结构体
//*dev:填0即可, *id:模块名字, 比如"adc","i2c"等,名字定义在clock.c中
clk_enable(struct clk *clk);
//开启clk_get()到的模块时钟,就是使能CLKCON寄存器的某个模块的位
3 具体实现代码
步骤如下:
3.1 在init入口函数中:
1)分配一个input_dev结构体
2)设置input_dev的成员
-> 2.1)设置input_dev->evbit支持按键事件,绝对位移事件
(触摸屏:通过按键BTN_TOUCH获取按下/松开,通过绝对位移获取坐标)
-> 2.2)设置input_dev-> keybit支持BTN_TOUCH触摸屏笔尖按下
-> 2.3)设置input_dev-> absbit 支持ABS_X、ABS_Y、 ABS_PRESSURE
input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0);
input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0); // 0x3FF:最大值为10位ADC,
input_set_abs_params(ts.dev, ABS_PRESSURE, 0, 1, 0, 0); //压力最多就是1
3)注册input_dev 驱动设备到内核中
4)设置触摸屏相关的硬件
-> 4.1)开启ADC时钟,使用clk_get ()和clk_enable()函数
-> 4.2) ioremap获取寄存器地址,设置寄存器ADCCON =(1<<14)|(49<<6),分频
->4.3)设置寄存器ADCDLY=0xffff,ADC启动延时时间设为最大值,使触摸按压更加稳定
->4.4)开启IRQ_TC笔尖中断、开启IRQ_ADC中断获取XY坐标
-> 4.5)初始化定时器,增加触摸滑动功能
->4.6)最后设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断
3.2 在出口函数中:
1)注销内核里的input_dev、
2)释放中断、删除定时器、iounmap注销地址、
3)释放input_dev、
3.3 在IRQ_TC中断函数中:
1)若判断笔尖为松开,设置寄存器ADCTSC =0XD3(按下中断)
2)若判断笔尖按下,设置为XY自动转换模式,启动一次ADC转换,ADC转换成功,会进入ADC中断
3.4 在IRQ_ADC中断函数中:
1)获取ADCDAT0的位[9:0],来算出XY方向坐标值
2)测量n次值保存在数组中,然后再次设置为XY自动转换模式,启动ADC
(PS:要启动ADC转换之前必须设置一次XY为自动转换模式,不然获取的数据会不准)
3)采集完毕,使用快速排序将n次值排序后,以最小值为基准,如有误差非常大的数,则舍弃,如果没有则打印数组的中间值,实现中值滤波。
(PS: 使用快速排序,比冒泡更快,详解:http://www.cnblogs.com/lifexy/p/7597276.html )
4)打印数据后,必须设置寄存器ADCTSC =0X1D3(松开中断IRQ_TC)
(PS:在ADC采样模式下是判断不到ADCDAT0的bit15位的,因为ADCDAT0已被自动设置为X坐标的采样值)
5)设置定时器10ms超时时间
3.5 在定时器超时函数中:
1)判断ADCDAT0的bit15位,若还在按下再次启动ADC转换(实现触摸滑动功能)
2)若松开,设置寄存器ADCTSC =0XD3(按下中断)
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/plat-s3c24xx/ts.h>
#include <asm/arch/regs-adc.h>
#include <asm/arch/regs-gpio.h>
struct s3c_ts_regs {
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcpdn;
};
static volatile struct s3c_ts_regs *s3c_ts_regs;
static struct input_dev *s3c_ts_dev;
static struct timer_list ts_timer;
//等待触摸笔按下模式
static void enter_wait_pen_down_mode(void)
{
s3c_ts_regs->adctsc = 0xd3;
}
//等待触摸笔松开模式
static void enter_wait_pen_up_mode(void)
{
s3c_ts_regs->adctsc = 0x1d3;
}
//自动测量xy坐标模式
static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<2) | (1<<3);
}
//启动adc
static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
//adc处理数据需要一定的时间,完成之后会产生中断
}
//触摸屏中断处理函数
static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
{
if(s3c_ts_regs->adcdat0 & (1<<15)) //看芯片手册,如果bit15为1表示松开
{
printk("pen up\n");
//松开时进入等待按下模式,这样才可以继续处理下一次
enter_wait_pen_down_mode();
}
else
{
/*
printk("pen down\n");
//按下时等待松开模式,这样才可以处理下一次的动作
enter_wait_pen_up_mode();
*/
//按下之后,测量xy坐标模式,然后转换为坐标
enter_measure_xy_mode();
start_adc(); //adc转换成功后产生中断,调用adc_irq,
//adc_irq中断函数完成之后,进入等待松开模式,准备处理下一次动作,如果没有这个函数,就只能处理一次动作
//enter_wait_pen_up_mode();
}
return IRQ_HANDLED;
}
//adc中断处理函数
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
static int x[4],y[4];
int adcdata0,adcdata1;
adcdata0 = s3c_ts_regs->adcdat0;
adcdata1 = s3c_ts_regs->adcdat1;
if(s3c_ts_regs->adcdat0 & (1<<15))
{
cnt = 0;
//如果已经松开,丢弃结果
//松开时进入等待按下模式,这样才可以继续处理下一次
enter_wait_pen_down_mode();
}
else
{
//优化措施2:如果ADC完成时,发现触摸笔已经松开,则丢弃此次结果。
//printk("adc_irq cnt = %d,x = %d, y = %d\n",++cnt,s3c_ts_regs->adcdat0 & 0x3ff,s3c_ts_regs->adcdat1 & 0x3ff ); //只有前十位表示电压值
//优化措施3:多次测量求平均值
x[cnt] = s3c_ts_regs->adcdat0 & 0x3ff;
y[cnt] = s3c_ts_regs->adcdat1 & 0x3ff;
++cnt;
if(cnt == 4)
{
printk("adc_irq x = %d, y = %d\n",(x[0] + x[1] + x[2] +x[3]) /4,(y[0] + y[1] + y[2] +y[3]) /4 );
cnt = 0;
enter_wait_pen_up_mode();
//启动定时器,处理长按或者滑动的情况
mod_timer(&ts_timer,jiffies + HZ / 100); //等待10ms
}
else
{
//否则在测量一次
enter_measure_xy_mode();
start_adc();
}
return IRQ_HANDLED;
}
}
static void s3c_ts_timer_function(unsigned long data)
{
if(s3c_ts_regs->adcdat0 & (1<<15))
{
//已经松开
enter_wait_pen_down_mode();
}
else
{
//测量 x y坐标
enter_measure_xy_mode();
start_adc();
}
}
static int s3c_ts_init(void)
{
struct clk *clk;
//1、分配一个input_dev结构体
s3c_ts_dev = input_allocate_device();
//2、设置
//2.1能产生哪一类事件
set_bit(EV_KEY,s3c_ts_dev->evbit);
set_bit(EV_ABS,s3c_ts_dev->evbit);
//2.2能产生这类事件的那些事件
set_bit(BTN_TOUCH,s3c_ts_dev->evbit);
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0); //0x3ff的原因:芯片手册adc最大为10位
input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
//3、注册
input_register_device(s3c_ts_dev);
//4、硬件相关的配置
//4.1、使能时钟(CLKCON(15),为了省电,启动时系统禁止外接硬件,使用时需要使能)
clk = clk_get(NULL, "adc");
clk_enable(clk); //使能时钟
//4.2、映射相关寄存器的地址
s3c_ts_regs= ioremap(0x58000000,sizeof(struct s3c_ts_regs));
//4.3、设置寄存器ADC/TS(操作之前需要映射相关寄存器,ioremap)
/* 系统默认的plk为50MHZ
bit[14] : 1-A/D converter prescaler enable
* bit[13:6]: A/D converter prescaler value,
* 49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
* bit[0]: A/D conversion starts by enable. 先设为0
*/
s3c_ts_regs->adccon = (1<<14) | (49<<6);
//5、注册中断
request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM,"ts_pen", NULL);
//adc处理数据需要一定的时间,完成转换之后,会产生一个中断,我们使用中断,提高效率
request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM,"adc", NULL);
/*优化措施1:优化电压值可能不准确(按下之后立刻产生中断,可能电压还没有达到稳定)
使用适当延时,使触摸笔按下稳定之后,才产生中断
设置adcdly为最大值,这使得电压稳定后在发出IRQ_TC中断
*/
s3c_ts_regs->adcdly = 0xffff;
/*
优化3:使用定时器处理长按,滑动的情况
*/
init_timer(&ts_timer);
ts_timer.function = s3c_ts_timer_function;
add_timer(&ts_timer);
enter_wait_pen_down_mode();//等待触摸笔按下模式
return 0;
}
static void s3c_ts_exit(void)
{
//释放中断
free_irq(IRQ_TC,NULL);
free_irq(IRQ_ADC,NULL);
//释放物理地址映射
iounmap(s3c_ts_regs);
//释放设备
input_unregister_device(s3c_ts_dev);
//释放内存
input_free_device(s3c_ts_dev);
del_timer(&ts_timer);
}
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");