输入子系统体系
• 核心层: Linux_Dir/drivers/input/input.c(提供最核心函数)
• 设备事件层: Linux_Dir/drivers/input/evdev.c(提供handler)
提供输入设备产生的原始数据并上报给应用程序,这适用于
所有输入设备, 该触摸屏也不例外
编写基于输入子系统的驱动时只需:
1.分配input_dev
2.设置能产生什么事件和这类事件的哪些事件
3. input_register_device注册
4. 硬件操作
触摸屏硬件原理
1.分配input_dev
2.设置能产生什么事件和这类事件的哪些事件
3. input_register_device注册
/* 1. 分配一个input结构体 */
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);
现在已将input_dev结构体设置好了,接下来就是对触摸屏硬件的设置了,而在设置硬件之前要先向大家介绍一下触摸屏的使用过程:
触摸屏的使用过程分析
1.按下触摸屏产生中断
2.在触摸屏中断处理程序中,将通过ADC采集的电压值转化为XY方向的坐标值,然后启动ADC。
3.AD转化结束产生ADC中断。
4.在ADC中断处理函数中上报事件,并启动定时器(用于处理长按和滑动事件)
5.定时器时间到,跳到步骤2继续往下执行
直到松开触摸屏
下面是硬件寄存器和中断的设置,为上面触摸屏的操作做准备,程序为:
/* 4.硬件相关操作 */
/* 4.1 使能时钟 */
clk = clk_get(NULL,"adc");
clk_enable(clk);
/* 4.2 设置S3C2440的ADC/TS相关操作寄存器 */
// s3c_ts_regs = ioremap(0x58000000,(sizeof(struct s3c_ts_regs));
s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
/*bit[14] PRSCEN :A/D converter 预分频使能
* 1 = Enable
*bit[13:6] PRSCVL : A/D converter prescaler value 预分频系数
* 49 ADCLK = PCLK/(49+1) = 50MHZ / (49+1) = 1MHZ
*bit[0] ENABLE_START ADC开始转化使能 这里先设为0,后面还会进行设置
*
*
*/
s3c_ts_regs->adccon = (1<<14) | (49<<6) | (0<<0);
/* 4.3 注册中断,当按下触摸屏时会产生中断 */
request_irq(IRQ_TC,pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
/* 4.5 当ADC转化结束后,会产生ADC转化结束中断 */
request_irq(IRQ_ADC,adc_irq,IRQF_SAMPLE_RANDOM,"adc",NULL);
enter_wait_pen_down_mode();
上面程序中的s3c_ts_regs是触摸屏寄存器的一个总的集合,并将其放入一个结构体中,代码为:
struct s3c_ts_regs{
unsigned long adccon;
unsigned long adctsc;
unsigned long adcdly;
unsigned long adcdat0;
unsigned long adcdat1;
unsigned long adcupdn;
};
上面是准备代码,下面我们将模仿触摸屏按下的过程来分析代码:
假设我们程序已经写好,并且在开发版上运行正常,当你拿起笔按在触摸屏上的一点,因为在之前我们已经注册了IRQ_TC中断:
/* 4.3 注册中断,当按下触摸屏时会产生中断 */
request_irq(IRQ_TC,pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
所以当你按下屏幕时触发中断,从而处理相应的中断处理函数: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(); //进入等待按下模式
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
}
else
{
//此时屏幕还处于按下状态
/* 4.4 在中断中启动ADC,转化XY坐标 */
//printk("pen down\n");
//enter_wait_open_up_mode();
enter_measure_xy_mode();
start_adc();
}
return IRQ_HANDLED;
}
如上面程序所示,此时如果你抬起了你的笔,程序将上报事件,而如果你还处于按下状态,我们将继续跟着程序走:
由于你已经注册了ADC中断:
/* 4.3 注册中断,当按下触摸屏时会产生中断 */
request_irq(IRQ_TC,pen_down_up_irq,IRQF_SAMPLE_RANDOM,"ts_pen",NULL);
而上面的程序已经开启了ADC:start_adc();
所以我们将进入ADC的中断处理函数adc_irq,详细的代码:
static irqreturn_t adc_irq(int irq, void *dev_id)
{
static int cnt = 0;
int adcdat0, adcdat1;
static int x[4], y[4];
/* 优化措施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
{
//优化措施3 多次测量求平均值
x[cnt] = adcdat0&0x3ff;
y[cnt] = adcdat1&0x3ff;
++cnt;
if(cnt==4)
{
//优化措施4 软件过滤
if(s3c_filter_ts(x, y))
{
/* 4.6 在ADC中断处理函数中上报(input_event),启动定时器 */
//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);
}
enter_wait_pen_up_mode();
cnt=0;
/* 启动定时器处理长按和滑动情况 */
mod_timer(&ts_timer, jiffies + HZ/100);
}
else
{
enter_measure_xy_mode();
start_adc();
}
}
return IRQ_HANDLED;
}
而通过上面的程序,我们知道了触摸屏的处理过程,此时,触摸屏驱动已经基本写好了。而下面就是对特殊的处理,如当长按或者滑动时,此时我们将通过定时器来处理这样的问题,假如此时你还没有松手,触摸屏还处于按下状态。(你可能觉得怎么这么长时间了笔还没抬起啊,其实上面分析的可能麻烦,但在程序中上面的代码还是跑的很快的)。那么,我们接下来就是对定时器的分析了,因为在上文中我们启动了定时器:mod_timer(&ts_timer,jiffies + HZ/100);
当定时时间(此处我们设置定时时间为10ms)到时,进入定时器处理函数:
static void s3c_ts_timer_function(unsigned long data)
{
if(s3c_ts_regs->adcdat0 & (1<<15))
{
//已经松开
/* 4.7 定时器时间到,在定时器处理函数中,重复4.4的步骤(用于处理长按和滑动) */
enter_wait_pen_down_mode();
input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
input_sync(s3c_ts_dev);
}
else
{
//继续测量XY坐标
/* 4.4 在中断中启动ADC,转化XY坐标 */
enter_measure_xy_mode();
start_adc();
}
}
在上面的程序中,可以看出定时时间到后定时器又会启动ADC然后将接着步骤2继续执行,一直这样循环,直到松开,而这样也就可以处理长按或者滑动了,当你长按或者滑动时,定时时间到后会采集你所在的位置的XY坐标,然后上报,然后继续进入定时器,继续进入ADC,继续采集上报,直到你松开触摸屏。
好了,上面就是触摸屏驱动的大致流程和代码分析了。
下面是进入不同模式和滤波的函数
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;
}