linux驱动程序之触摸屏(涵盖了常用的事件编码)

作为一个输入子系统,步骤依然是:

1、分配

2、设置

3、注册

4、硬件相关的操作


触摸屏的使用过程:

1、按下,产生中断

2、在中断处理程序里面启动ADC转换XY坐标

3、ADC结束,产生中断。

4、在ADC处理函数里面上报input_event,启动定时器

5、定时器时间到,再次启动ADC(处理长按,滑动)

6、等待松开

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

具体步骤:

1、分配

分配一个 input_dev 结构体。

static struct input_dev *s3c_ts_dev;

s3c_ts_dev = input_allocate_device();

2、设置

	/*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->keybit);

	input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
	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);

首先设置的是能产生哪类事件,

下面给出了这里给出一些常用输入设备的事件类型。

input_set_abs_params函数原型为:

static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)

对于上面代码的最大值0x3ff的解释是这样的,s3c2440的AD是10位的AD所以最大值就是0x3ff。

 Event types:

types对应于一个相同逻辑输入结构的一组Codes。每个type都有一组可用的codes用于产生输入事件。


EV_SYN0x00同步事件
EV_KEY0x01按键事件
EV_REL0x02相对坐标
EV_ABS0x03绝对坐标
EV_MSC0x04其它
EV_LED0x11LED
EV_SND0x12声音
EV_REP0x14Repeat
EV_FF0x15力反馈
EV_PWR 电源
EV_FF_STATUS 状态

Event codes:

Event codes 用于对事件的type进行更精确的定义:

EV_SYN:

SYN_REPORT:当多个输入数据在同一时间发生变化时,SYN_REPORT用于把这些数据进行打包和包同步。例如,一次鼠标的移动可以上报REL_X和REL_Y两个数值,然后发出一个SYN_REPORT。下一次鼠标移动可以再次发出REL_X和REL_Y两个数值,然后经跟这另一个SYN_REPORT
SYN_CONFIG用于同步和分离触摸事件
SYN_MT_REPORT 
SYN_DROPPED用来指出evdev客户的事件队列的的缓冲区溢出。客户端顶盖忽略所有的事件,包括下一个SYN_REPORT事件,并且要查询设备来获得它的状态(使用EVIOCG* ioctls)。

 EV_KEY:

BTN_TOOL_<name>这些codes用于配合触控板,平板和触摸屏这些设备的输入,这些设备可以使用手指,笔或者其它工具。当一个事件发生并且检测到某种工具在使用时,相应的BTN_TOOL_<name> code事件应该把value设为1,当该工具不再和输入设备进行交互时,value应该复位为0。所有的触控板,当事件发生时,平板和触摸屏映泰至少使用一种BTN_TOOL_<name> code
BTN_TOUCHBTN_TOUCH用于触摸接触事件。当一个输入工具被判定为有意义的物理接触时,这一特性的value值应该设为1。所谓有意义的物理接触可以是任何的接触,又或者是满足某种定义条件的接触。例如,触摸板可以当触摸的压力达到某一个值以上时才把value设为1,一个用笔的平板当笔划过但没有接触到平板的表面时,把BTN_TOOL_PEN的value设为1,而把BTN_TOUCH的value设为0.

 注意:为了配合一些老的传统mousedev模拟驱动程序可以工作,BTN_TOUCH必须作为一个同步帧的第一个evdevcode发出。

 注意:出于历史的原因,用户空间会把带有BTN_TOOL_FINGER和 BTN_TOUCH的触摸设备解释为触摸板,而类似的不带BTN_TOOL_FINGER的触摸设备则被解释为触摸屏。为了与目前的用户空间应用向后兼容,建议遵循这一区分原则。以后,这一区分方法将会失效,而会使用设备属性ioctl EVIOCGPROP(定义在linux/input.h)来传送设备的类型。

BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP,BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP

-这些codes表明一个,两个,三个和四个手指参与触摸板和触摸屏的操作。例如,如果用户使用两只手指在触摸板上试图滚动屏幕上的内容,在运动期间,应该发送value为1的BTN_TOOL_DOUBLETAP。注意的是所有的BTN_TOOL_<name>codes 和 BTN_TOUCH code是用于正交目的的,一个触摸板由手指碰触时,应该在这两组中各生成一个事件code,至少在一个同步帧中带有一个value为1的BTN_TOOL_<name>code。

 注意:出于历史原因,一些驱动会在同一个同步帧内发送多个value为1的上报手指数的codes,但是这一方法现在已经过时了(不再使用)。

 注意:在多手指触摸驱动中,应该使用input_mt_report_finger_count()函数来发出以上这些codes,详情请参看内核文档:multi-touch-protocol.txt。

 EV_REL:

REL_WHEEL, REL_HWHEEL这两个codes用于对应的垂直方向和水平方向的滚轮。

EV_ABS:

ABS_DISTANCE:来描述触摸工具离触摸表面的距离。这一事件应该只有当触摸工具在表面悬空滑过时发出,也就是说,在靠经触摸表面,但是BTN_TOUCH的value是0的时候。如果输入设备可以工作在3维坐标时,应该考虑使用ABS_Z会更好。
ABS_MT_<name>用于描述多手指触摸输入设备

3、注册:

input_register_device(s3c_ts_dev);

4、硬件相关的操作

1、硬件上主要是设置AD的寄存器。s3c2440在默认情况下没使用的模块的时钟是关闭的,因此首先要开启AD的时钟:

struct clk* clk;

clk = clk_get(NULL,"adc");
	if(!clk)
	{
		printk(KERN_ERR"falied to find adc clock source\n");
		return -ENOENT;
	}
clk_enable(clk);

首先申请时钟,然后使能。

2、既然要配置AD的寄存器,那么在配置之前要先映射寄存器的物理地址:

s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_ts_regs));
注意:手册上给的ADCCON寄存器的物理地址是:0x5800000,我没有仔细查有几个零,直接复制过来了,后来怎么调试都有问题,原来是少了0造成的

3、根据手册配置ADCCON,寄存器的配置在代码的注释上已经描述的很详尽:

	/*ADCCON
	 *p_clock  50MHz
	 *bit[14]=1:A/D converter prescaler enable
	 *bit[13:6] =49 A/D converter prescaler value
	 *A/D converter freq. = 50MHz/(49+1) = 1MHz
	 *bit[0] A/D conversion starts by enable.先设为0
	 */
	s3c_ts_regs->ADCCON = (1<<14)|(49<<6);

关于P_clock的获取方法:可以使用 命令:dmesg察看内核信息,找到P -clock的大小

4、配置ADC为等待按下模式:

enter_wait_pen_down_mode();
static void enter_wait_pen_down_mode(void)
{
	s3c_ts_regs->ADCTSC = 0xd3;
}
进入等待按下模式后,只要按键按下,就会触发中断。

5、因为要使用异步的方式获取触摸屏的信息,所以注册两个中断,一个是,触摸屏只要被按下或者松开就会出发的中断,另一个是ADC转换完成的中断:

	request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM,"ts_pen",NULL);

	request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM,"adc",NULL);
当触摸屏被按下或者松开后触发中断,进入中断服务程序:

static irqreturn_t pen_down_up_irq(int irq,void *dev_id)
{
	if(s3c_ts_regs->ADCDAT0 & (1<<15) )//松开了
	{
		printk("pen up\n");
		enter_wait_pen_down_mode();
	}
	else//按下了
	{
		enter_measure_xy_mode();
		start_ADC();
	}
	return IRQ_HANDLED;
}

在这个中断服务程序里面,可以进一步检测是按下了触摸屏还是松开了,如果按下
 ADCDAT0[15]会变成0,反之松开变成1,因此通过检查ADCDAT0的第15位就可以确定是按下还是松开。

如果松开,就再次进入按下等待模式,否则,进入测量模式,开启ADC转换。

由于还要处理常按和滑动,所以可以开启一个定时器来完成这个功能,这里不再详细描述细节。


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

测试:

make menuconfig

│     -> Device Drivers                                                                                                                 
│       -> Input device support                                                                                                                       
│         -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])                                                                           
│           -> Touchscreens (INPUT_TOUCHSCREEN [=y])  


                EmbedSky TQ2440 TouchScreen input driver     去掉


  │     -> Device Drivers                                                                                                                                   
  │       -> Character devices 

   |             -> EmbedSky SKY2440/TQ2440 Board ADC Driver   去掉


make

安装新内核    


使用hexdump测试。

hexdump /dev/event0


完整代码如下:

#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 <plat/regs-adc.h>
#include <mach/regs-gpio.h>

static struct input_dev *s3c_ts_dev;

static struct timer_list ts_timer;


struct s3c_ts_regs{
	unsigned long ADCCON;
	unsigned long ADCTSC;
	unsigned long ADCDLY;
	unsigned long ADCDAT0;
	unsigned long ADCDAT1;
	unsigned long ADCUPDN;
};

static volatile struct s3c_ts_regs* s3c_ts_regs;

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;
}
static void enter_measure_xy_mode(void)
{
	/*ADCTSC
	 *bit[2]=1 1=Auto Sequential measurement of X-position, Y-position.
	 *bit[3]=1 1=XP Pull-up Disable.
	 */
	s3c_ts_regs->ADCTSC = (1<<3)|(1<<2);
}

static void start_ADC(void)
{
	/*ADCCON
	 * bit[0] = 1= A/D conversion starts and this bit is cleared after the start- up.
	 */
	s3c_ts_regs->ADCCON |= (1<<0);
}
static irqreturn_t pen_down_up_irq(int irq,void *dev_id)
{
	if(s3c_ts_regs->ADCDAT0 & (1<<15) )//松开了
	{
		printk("pen up\n");
		enter_wait_pen_down_mode();
	}
	else//按下了
	{
		enter_measure_xy_mode();
		start_ADC();
	}
	return IRQ_HANDLED;
}

/*过滤*/
static int soft_filter_ts(int x[],int y[])
{
#define ERR_LIMIT 10
	int avr_x,avr_y;
	int det_x,det_y;

	avr_x =(x[0] + x[1])/2;
	avr_y =(y[0] + y[1])/2;

	det_x = (x[2]>avr_x) ? (x[2] - avr_x):(avr_x - x[2]);
	det_y = (y[2]>avr_y) ? (y[2] - avr_y):(avr_y - y[2]);

	if((det_x > ERR_LIMIT)||(det_y > ERR_LIMIT))
		return 0;

	avr_x =(x[1] + x[2])/2;
	avr_y =(y[1] + y[2])/2;

	det_x = (x[3]>avr_x) ? (x[3] - avr_x):(avr_x - x[3]);
	det_y = (y[3]>avr_y) ? (y[3] - avr_y):(avr_y - y[3]);

	if((det_x > ERR_LIMIT)||(det_y > ERR_LIMIT))
		return 0;

	return 1;

}

static void s3c_ts_timer_function(unsigned long data)
{
	if(s3c_ts_regs->ADCDAT0 & (1<<15) ) //已经松开
	{
		input_report_abs(s3c_ts_dev,ABS_PRESSURE,0);
		input_report_key(s3c_ts_dev,BTN_TOUCH,0);
		input_sync(s3c_ts_dev);
		enter_wait_pen_down_mode();
	}
	else
	{
		/*测量x,y坐标*/
		enter_measure_xy_mode();
		start_ADC();
	}
}


static irqreturn_t adc_irq(int irq,void *dev_id)
{
	static int cnt = 0;
	static int x[4],y[4];
	/*优化2:
	 * 如果adc完成时,发现触摸笔已经松开,则丢弃此次结果
	 * */
	if(s3c_ts_regs->ADCDAT0 & (1<<15) )//松开了
	{
		cnt = 0;
		input_report_abs(s3c_ts_dev,ABS_PRESSURE,0);
		input_report_key(s3c_ts_dev,BTN_TOUCH,0);
		input_sync(s3c_ts_dev);
		enter_wait_pen_down_mode();
	}
	else
	{
		/*优化3:多次测量,求平均值
		 *
		 */
		++cnt;
		x[cnt] =s3c_ts_regs->ADCDAT0 & 0x3ff;
		y[cnt] =s3c_ts_regs->ADCDAT1 & 0x3ff;
		if(cnt == 4)
		{
			/*优化4
			 * 软件过滤
			 */
			if(soft_filter_ts)
			{
				int avaragex = (x[0]+x[1]+x[2]+x[3])/4;
				int avaragey = (y[0]+y[1]+y[2]+y[3])/4;
				//printk("adc_irq cnt = %d,x= %d,y= %d \n",cnt,avaragex,avaragey);
				input_report_abs(s3c_ts_dev,ABS_X,avaragex);
				input_report_abs(s3c_ts_dev,ABS_Y,avaragey);
				input_report_abs(s3c_ts_dev,ABS_PRESSURE,1);
				input_report_key(s3c_ts_dev,BTN_TOUCH,1);
				input_sync(s3c_ts_dev);
			}
			cnt = 0;
			enter_wait_pen_up_mode();
			/*启动定时器,处理常按 滑动的情况*/
			mod_timer(&ts_timer,jiffies +HZ/100);

		}
		else
		{
			enter_measure_xy_mode();
			start_ADC();
		}
	}
	return IRQ_HANDLED;
}

static int drv_ts_init(void)
{
	struct clk* clk;

	/*1、分配一个input ——dev结构体*/
	s3c_ts_dev = input_allocate_device();
	if (!s3c_ts_dev)
	{
		printk(KERN_ERR "Unable to allocate the input device !!\n");
		return -ENOMEM;
	}

	/*2、设置*/
	/*2、1能产生哪类事件 Event types:*/
	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->keybit);

	input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
	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使能ADC时钟clkcon[15]*/
	clk = clk_get(NULL,"adc");
	if(!clk)
	{
		printk(KERN_ERR"falied to find adc clock source\n");
		return -ENOENT;
	}
	clk_enable(clk);

	/*设置s3c2440的ADC/ts寄存器*/
	s3c_ts_regs = ioremap(0x58000000,sizeof(struct s3c_ts_regs));
	/*ADCCON
	 *p_clock  50MHz
	 *bit[14]=1:A/D converter prescaler enable
	 *bit[13:6] =49 A/D converter prescaler value
	 *A/D converter freq. = 50MHz/(49+1) = 1MHz
	 *bit[0] A/D conversion starts by enable.先设为0
	 */
	s3c_ts_regs->ADCCON = (1<<14)|(49<<6);

	request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM,"ts_pen",NULL);

	request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM,"adc",NULL);

	/*优化1
	 *设置adc——delay为最大值
	 *电压稳定后,再出发 IRQ_TC中断。
	 */
	s3c_ts_regs->ADCDLY = 0xffff;

	/*优化5
	 *使用定时器处理常按,滑动的情况
	 */
	init_timer(&ts_timer);
	ts_timer.function = s3c_ts_timer_function;
	add_timer(&ts_timer);

	/*进入按键等待模式*/
	enter_wait_pen_down_mode();

	return 0;
}

static void drv_ts_exit(void)
{
	free_irq(IRQ_ADC,NULL);
	free_irq(IRQ_TC,NULL);
	iounmap(s3c_ts_regs);
	input_unregister_device(s3c_ts_dev);
	input_free_device(s3c_ts_dev);
	del_timer(&ts_timer);
}

module_init(drv_ts_init);
module_exit(drv_ts_exit);
MODULE_LICENSE("GPL");



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值