电阻触摸屏驱动

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触摸屏笔尖按下

扫描二维码关注公众号,回复: 9737188 查看本文章

-> 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");
发布了29 篇原创文章 · 获赞 1 · 访问量 543

猜你喜欢

转载自blog.csdn.net/qq_45173769/article/details/103915425