10-S3C2440驱动学习(六)嵌入式linux-触摸屏设备驱动

触摸屏子系统是经过input子系统来实现,对应设备节点 /dev/input/eventn,熟悉套路后重点放在硬件程序的编写。linux

1、内核自带触摸屏驱动S3c2410_ts的简单分析

S3c2410_ts.c (drivers\input\touchscreen) 内核自带三星的触摸屏驱动框架

(1)入口函数:函数

注册一个平台driver测试

static int __init s3c2410ts_init(void)优化

{spa

//     init_MUTEX(&gADClock);插件

       returnplatform_driver_register(&s3c2410ts_driver);3d

}指针

(2)platform_driver结构体code

static struct platform_drivers  3c2410ts_driver = {

      .driver         = {

              .name  = "s3c2410-ts",

              .owner = THIS_MODULE,

      },

      .probe         = s3c2410ts_probe,

      .remove         = s3c2410ts_remove,

};

内核中有同名设备的时候, probe函数s3c2410ts_probe会被调用。

(3)static int __init     s3c2410ts_probe(structplatform_device *pdev)作什么了呢?

       //分配一个 input_dev结构体

input_dev =input_allocate_device();

//设置    能产生哪些事件 按键类事件,绝对位置类事件

ts.dev =input_dev;

        ts.dev->evbit[0]= BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);

        ts.dev->keybit[LONG(BTN_TOUCH)]= BIT(BTN_TOUCH);

//设置绝对位置的参数

       input_set_abs_params(ts.dev,ABS_X, 0, 0x3FF, 0, 0);//绝对位移方向

       input_set_abs_params(ts.dev,ABS_Y, 0, 0x3FF, 0, 0);

       input_set_abs_params(ts.dev,ABS_PRESSURE, 0, 1, 0, 0);


       ts.dev->private= &ts;

       ts.dev->name= s3c2410ts_name;

       ts.dev->id.bustype= BUS_RS232;

       ts.dev->id.vendor= 0xDEAD;

       ts.dev->id.product= 0xBEEF;

       ts.dev->id.version= S3C2410TSVERSION;

//注册

       input_register_device(ts.dev);

(4)touch_timer_fire 当事件发生的时候,上报事件

上报事件:input_report_abs--》input_event


2、参考S3c2410_ts从零写S3C2440触摸屏驱动

(1)先写出基本框架


(2)分配一个input_dev结构体

s3c_ts_dev = input_allocate_device();

(3)设置 
1 能产生哪类事件
set_bit(EV_KEY, s3c_ts_dev->evbit);   //可以产生按键类事件
set_bit(EV_ABS, s3c_ts_dev->evbit);  //可以产生触摸屏事件

2 能产生这类事件里的哪些事件 
set_bit(BTN_TOUCH, s3c_ts_dev->keybit);   //可以产生按键类事件里的触摸屏事件
//x y 压力方向
input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);//最小值0 最大值3ff(参考2440手册触摸屏10位ADC)
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); //压力的用力度,分级,这里简单 要么0 要么1,要么按下 要么松开

参考:


1th

(4)注册 
input_register_device(s3c_ts_dev);

(5)硬件相关的操做

5.1首先要对触摸屏硬件了解,才能知道怎么设置硬件。

触摸屏:

触摸屏是欧姆定律的悄秒应用。触摸屏是很薄的两层膜,LCD与触摸屏是俩不一样的东西。

按下后,触电会连接到一块儿。



5.2坐标只不过是电压值,和LCD彻底无关。如何对应起来LCD和触摸屏是应用程序的事。

矫正的时候 知不是在LCD和触摸屏之间创建联系。

5.3触摸屏使用过程


为何有定时器,是由于应用中可能存在,长按滑动。

5.4四个测点的硬件链接


手册:


5.5硬件相关操做代码

读2440手册,看例子怎么写的

5.5.1使能时钟

ts.clock = clk_get(dev, "adc");

clk_enable(ts.clock);



为了省电,内核上电的时候会对一些没用模块关闭掉,暂时不使用的模块。


clk_get:



clk_enable会调用到s3c2410_clkcon_enable,这个函数就是把相应位使能。

代码:

//使能时钟(CLKCON[15]) 
clk = clk_get(NULL, "adc");
clk_enable(clk);

5.5.2

查看芯片手册AD和触摸屏部分

2440内部是10位的AD转换器。

最大工做频率2.5M。

最大电压3.3V



AD与触摸屏内部结构:.

8:1 

8路选择一路进行ADC转换


工做频率:

刚才提到最大工做频率是2.5M,因此要对PCLK进行分配。转换一次须要5个时钟周期,5us


触摸屏接口模式:


模式1:正常的转换模式,通常的ADC操做,测量某个电压等。

模式2:分离XY坐标,转换模式。

里面又分为2个模式。X模式,Y模式。进入测量X坐标模式,转换结果存在ADCDAT0,并产生中断。测量Y同理也会产生中断。

模式3:自动的连续转换模式:

即转换X,又转换Y。x:ADCDAT0,y:ADCDAT1,最后产生一个中断

模式4:等待中断模式

等待触摸笔按下模式,按下笔后,产生中断。触摸笔按下的时候会产生interrupt (INT_TC)中断。

接下来开始设置触摸屏相关寄存器及相关函数:

a:时钟

struct clk* clk;

/* 4.1 使能时钟(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);

b:操做寄存器


寄存器名称长度

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;

//设置S3C2440的ADC/TS寄存器 
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));


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


注册中断


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

enter_wait_pen_down_mode(); //根据芯片手册 工做在模式4.

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;
}



使能至关于开关闭合。

默认程序处在enter_wait_pen_down_mode模式,等待触摸笔按下,按下后触发pen_down_up_irq中断处理函数被执行。

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                                                 //按下 等待松开模式。
{
printk("pen down\n");
enter_wait_pen_up_mode();
}
return IRQ_HANDLED;
}

出口函数:

static void s3c_ts_exit(void)
{
free_irq(IRQ_TC, NULL);
iounmap(s3c_ts_regs);
input_unregister_device(s3c_ts_dev);
input_free_device(s3c_ts_dev);
}

2th


已经能够正常检测到按下和松开,接下来是在按下的时候,启动位置测量。

以上完成了一个简单的触摸屏触发中断的例子

触摸屏按下的时候 咱们不打印了,而是进行ADC转换,去获取位置信息。

else
{
//printk("pen down\n");
//enter_wait_pen_up_mode();
enter_measure_xy_mode();
start_adc();
}

ADC是须要时间的,咱们不能死等,ADC完成以后会出发中断,所以注册以下中断

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

static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, s3c_ts_regs->adcdat0 & 0x3ff, s3c_ts_regs->adcdat1 & 0x3ff);
enter_wait_pen_up_mode();
return IRQ_HANDLED;
}

按下触摸屏进入pen_down_up_irq处理函数,进入测量XY模式,而后启动ADC;

看2440芯片手册

测量XY模式:

static void enter_measure_xy_mode(void)
{
s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}

启动ADC:

static void start_adc(void)
{
s3c_ts_regs->adccon |= (1<<0);
}

AD转换完成,进入中断程序adc_irq

static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, s3c_ts_regs->adcdat0 & 0x3ff, s3c_ts_regs->adcdat1 & 0x3ff);
enter_wait_pen_up_mode();//等待松开模式
return IRQ_HANDLED;
}


到此能够打印出位置信息来,还须要优化(1)值不是很精确,没作均值处理(2)没法处理滑动

优化错施1: 

* 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
*/
s3c_ts_regs->adcdly = 0xffff;

优化措施2: 若是ADC完成时, 发现触摸笔已经松开, 则丢弃这次结果 */

adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;


if (s3c_ts_regs->adcdat0 & (1<<15))
{
/* 已经松开 */
enter_wait_pen_down_mode();
}
else
{
printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
enter_wait_pen_up_mode();
}


优化措施3:屡次AD转换求平均

/* 优化措施2: 若是ADC完成时, 发现触摸笔已经松开, 则丢弃这次结果 */
	adcdat0 = s3c_ts_regs->adcdat0;
	adcdat1 = s3c_ts_regs->adcdat1;

	if (s3c_ts_regs->adcdat0 & (1<<15))
	{
		/* 已经松开 */
		cnt = 0;
		enter_wait_pen_down_mode();
	}
	else
	{
		// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
		/* 优化措施3: 屡次测量求平均值 */
		x[cnt] = adcdat0 & 0x3ff;
		y[cnt] = adcdat1 & 0x3ff;
		++cnt;
		if (cnt == 4)
		{
			printk("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();
		}
		else
		{
			enter_measure_xy_mode();
			start_adc();
		}		
	}
优化措施4:增长软件过滤,屡次AD选合适值

if (cnt == 4)
		{
			/* 优化措施4: 软件过滤 */
			if (s3c_filter_ts(x, y))
			{			
				printk("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();
		}

优化措施5: 使用定时器处理长按,滑动的状况

static struct timer_list ts_timer;

/* 优化措施5: 使用定时器处理长按,滑动的状况

*/
init_timer(&ts_timer);
ts_timer.function = s3c_ts_timer_function;
add_timer(&ts_timer);

del_timer(&ts_timer);

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();
}
}


/* 启动定时器处理长按/滑动的状况 */
mod_timer(&ts_timer, jiffies + HZ/100);


最后 打印坐标改成上报事件

/* 优化措施4: 软件过滤 */
if (s3c_filter_ts(x, y))
{
//printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
input_sync(s3c_ts_dev);
}

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();
}

1. ls /dev/event* 
2. insmod s3c_ts.ko
3. ls /dev/event* 
4. hexdump /dev/event0
                       秒            微秒      type code    value
0000000 29a4 0000 8625 0008 0003 0000 0172 0000
0000010 29a4 0000 8631 0008 0003 0001 027c 0000
0000020 29a4 0000 8634 0008 0003 0018 0001 0000
0000030 29a4 0000 8638 0008 0001 014a 0001 0000
0000040 29a4 0000 863c 0008 0000 0000 0000 0000
0000050 29a4 0000 c85e 0008 0003 0000 0171 0000
0000060 29a4 0000 c874 0008 0003 0001 027d 0000
0000070 29a4 0000 c87b 0008 0000 0000 0000 0000
0000080 29a4 0000 ed37 0008 0003 0018 0000 0000
0000090 29a4 0000 ed48 0008 0001 014a 0000 0000
00000a0 29a4 0000 ed4a 0008 0000 0000 0000 0000


以后咱们编译tslib

export TSLIB_TSDEVICE=/dev/event1       //触摸屏是哪个
export TSLIB_CALIBFILE=/etc/pointercal   // 校验文件位置    校验后在  /etc/pointercal 生成校验文件

export TSLIB_CONFFILE=/etc/ts.conf     // 配置文件位置
export TSLIB_PLUGINDIR=/lib/ts               //插件位置
export TSLIB_CONSOLEDEVICE=none 
export TSLIB_FBDEVICE=/dev/fb0             //显示屏


到此触摸屏驱动移植成功。

3、代码及测试

一、完整触摸屏代码

#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 adcupdn;
};

static struct input_dev *s3c_ts_dev;
static volatile struct s3c_ts_regs *s3c_ts_regs;

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;
}

static void enter_measure_xy_mode(void)
{
	s3c_ts_regs->adctsc = (1<<3)|(1<<2);
}

static void start_adc(void)
{
	s3c_ts_regs->adccon |= (1<<0);
}

static int s3c_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 pen_down_up_irq(int irq, void *dev_id)
{
	if (s3c_ts_regs->adcdat0 & (1<<15))
	{
		//printk("pen up\n");
		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
	{
		//printk("pen down\n");
		//enter_wait_pen_up_mode();
		enter_measure_xy_mode();
		start_adc();
	}
	return IRQ_HANDLED;
}

static irqreturn_t adc_irq(int irq, void *dev_id)
{
	static int cnt = 0;
	static int x[4], y[4];
	int adcdat0, adcdat1;
	
	
	/* 优化措施2: 若是ADC完成时, 发现触摸笔已经松开, 则丢弃这次结果 */
	adcdat0 = s3c_ts_regs->adcdat0;
	adcdat1 = s3c_ts_regs->adcdat1;

	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
	{
		// printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
		/* 优化措施3: 屡次测量求平均值 */
		x[cnt] = adcdat0 & 0x3ff;
		y[cnt] = adcdat1 & 0x3ff;
		++cnt;
		if (cnt == 4)
		{
			/* 优化措施4: 软件过滤 */
			if (s3c_filter_ts(x, y))
			{			
				//printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
				input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
				input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
				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 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->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 使能时钟(CLKCON[15]) */
	clk = clk_get(NULL, "adc");
	clk_enable(clk);
	
	/* 4.2 设置S3C2440的ADC/TS寄存器 */
	s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));

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

	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: 
	 * 设置ADCDLY为最大值, 这使得电压稳定后再发出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 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");
二、采用tslib测试