分类: LINUX
2010-07-16 21:15:56
12.2 触摸屏的设备驱动
12.2.1 触摸屏的硬件原理
按照触摸屏的工作原理和传输信息的介质,我们把触摸屏分为4种:电阻式、电容感应式、红外线式以及表面声波式。
电阻式触摸屏利用压力感应进行控制,包含上下叠合的两个透明层,通常还要用一种弹性材料来将两层隔开。在触摸某点时,两层会在此点接通。四线和八线触摸屏由两层具有相同表面电阻的透明阻性材料组成,五线和七线触摸屏由一个阻性层和一个导电层组成。
所有的电阻式触摸屏都采用分压器原理来产生代表X坐标和Y坐标的电压。如图12.4所示,分压器是通过将两个电阻进行串联来实现的。电阻R1连接正参考电压VREF,电阻R2接地。两个电阻连接点处的电压测量值与R2的阻值成正比。
为了在电阻式触摸屏上的特定方向测量一个坐标,需要对一个阻性层进行偏置:将它的一边接VREF,另一边接地。同时,将未偏置的那一层连接到一个ADC的高阻抗输入端。当触摸屏上的压力足够大,两层之间发生接触时,电阻性表面被分隔为两个电阻。它们的阻值与触摸点到偏置边缘的距离成正比。触摸点与接地边之间的电阻相当于分压器中下面的那个电阻。因此,在未偏置层上测得的电压与触摸点到接地边之间的距离成正比。
四线触摸屏包含两个阻性层。其中一层在屏幕的左右边缘各有一条垂直总线,另一层在屏幕的底部和顶部各有一条水平总线,如图12.5所示。为了在X轴方向进行测量,将左侧总线偏置为0V,右侧总线偏置为VREF。将顶部或底部总线连接到ADC,当顶层和底层相接触时即可作一次测量。为了在Y轴方向进行测量,将顶部总线偏置为VREF,底部总线偏置为0V。将ADC输入端接左侧总线或右侧总线,当顶层与底层相接触时即可对电压进行测量。
图12.4 电阻触摸屏分压 |
图12.5 四线电阻式触摸屏 |
S3C2410接4线电阻式触摸屏的电路原理如图12.6所示。S3C2410提供了nYMON、YMON、nXPON和XMON直接作为触摸屏的控制信号,它通过连接FDC6321场效应管触摸屏驱动器控制触摸屏。输入信号在经过阻容式低通滤器滤除坐标信号噪声后被接入S3C2410内集成的ADC(模数转换器)的模拟信号输入通道AIN5、AIN7。
图12.6 S3C2410连接4线电阻式触摸屏 |
S3C2410内置了一个8信道的10位ADC,该ADC能以500KS/S的采样速率将外部的模拟信号转换为10位分辨率的数字量。因此,ADC能与触摸屏控制器协同工作,完成对触摸屏绝对地址的测量。
S3C2410的ADC和触摸屏接口可工作于5种模式,分别如下。
1.普通转换模式(Normal Converson Mode)
普通转换模式(AUTO_PST = 0,XY_PST = 0)用来进行一般的ADC转换,例如通过ADC测量电池电压等。
2.独立X/Y位置转换模式(Separate X/Y Position Conversion Mode)
独立X/Y轴坐标转换模式其实包含了X轴模式和Y轴模式。为获得X、Y坐标,需首先进行X轴的坐标转换(AUTO_PST = 0,XY_PST = 1),X轴的转换资料会写到ADCDAT0寄存器的XPDAT中,等待转换完成后,触摸屏控制器会产生INT_ADC中断。然后,进行Y轴的坐标转换(AUTO_PST = 0,XY_PST = 2),Y轴的转换资料会写到ADCDAT1寄存器的YPDAT中,等待转换完成后,触摸屏控制器也会产生INT_ADC中断。
3.自动(连续)X/Y位置转换模式(Auto X/Y Position Conversion Mode)
自动(连续)X/Y位置转换模式(AUTO_PST = 1,XY_PST = 0)运行方式是触摸屏控制自动转换X位置和Y位置。触摸屏控制器在ADCDAT0的XPDATA位写入X测定数据,在ADCDAT1的YPADATA位写入Y测定数据。自动(连续)位置转换后,触摸屏控制器产生INT_ADC中断。
4.等待中断模式(Wait for Interrupt Mode)
当触摸屏控制器等待中断模式时,它等待触摸屏触点信号的到来。当触点信号到来时,控制器产生INT_TC中断信号。然后,X位置和Y位置能被适当地转换模式(独立X/Y位置转换模式或自动X/Y位置转换模式)读取到。
5.待机模式(Standby Mode)
当ADCCON寄存器的STDBM位置1时,待机模式被激活。在这种模式下,A/D转换动作被禁止,ADCDAT0的XPDATA位和ADXDATA1的YPDAT保留以前被转换的数据。
12.2.2 触摸屏设备驱动中数据结构
触摸屏设备结构体的成员与按键设备结构体的成员类似,也包含一个缓冲区,同时包括自旋锁、等待队列和fasync_struct指针,如代码清单12.12所示。
代码清单12.12 触摸屏设备结构体
1 typedef struct 2 { 3 unsigned int penStatus; /* PEN_UP, PEN_DOWN, PEN_SAMPLE */ 4 TS_RET buf[MAX_TS_BUF]; /* 缓冲区 */ 5 unsigned int head, tail; /* 缓冲区头和尾 */ 6 wait_queue_head_t wq; /*等待队列*/ 7 spinlock_t lock; 8 #ifdef USE_ASYNC 9 struct fasync_struct *aq; 10 #endif 11 struct cdev cdev; 12 } TS_DEV; |
触摸屏结构体中包含的TS_RET值的类型定义如代码清单12.13所示,包含X、Y坐标和状态(PEN_DOWN、PEN_UP)等信息,这个信息会在用户读取触摸信息时复制到用户空间。
代码清单12.13 TS_RET结构体
1 typedef struct 2 { 3 unsigned short pressure;//PEN_DOWN、PEN_UP 4 unsigned short x;//x坐标 5 unsigned short y;//y坐标 6 unsigned short pad; 7 } TS_RET; |
在触摸屏设备驱动中,将实现open()、release()、read()、fasync()和poll()函数,因此,其文件操作结构体定义如代码清单12.14所示。
代码清单12.14 触摸屏驱动文件操作结构体
1 static struct file_operations s3c2410_fops = 2 { 3 owner: THIS_MODULE, 4 open: s3c2410_ts_open, //打开 5 read: s3c2410_ts_read, //读坐标 6 release: 7 s3c2410_ts_release, 8 #ifdef USE_ASYNC 9 fasync: s3c2410_ts_fasync, // fasync()函数 10 #endif 11 poll: s3c2410_ts_poll,//轮询 12 }; |
12.2.3 触摸屏驱动中的硬件控制
代码清单12.15中的一组宏用于控制触摸屏和ADC进入不同的工作模式,如等待中断、X/Y位置转换等。
代码清单12.15 触摸屏和ADC硬件控制
1 #define wait_down_int(){ ADCTSC = DOWN_INT | XP_PULL_UP_EN |\ 2 XP_AIN | XM_HIZ | YP_AIN | YM_GND | \ 3 XP_PST(WAIT_INT_MODE); } 4 #define wait_up_int(){ ADCTSC = UP_INT | XP_PULL_UP_EN | XP_AIN |\ 5 XM_HIZ |YP_AIN | YM_GND | XP_PST(WAIT_INT_MODE); } 6 #define mode_x_axis(){ ADCTSC = XP_EXTVLT | XM_GND | YP_AIN \ 7 | YM_HIZ |XP_PULL_UP_DIS | XP_PST(X_AXIS_MODE); } 8 #define mode_x_axis_n(){ ADCTSC = XP_EXTVLT | XM_GND | YP_AIN | \ 9 YM_HIZ |XP_PULL_UP_DIS | XP_PST(NOP_MODE); } 10 #define mode_y_axis(){ ADCTSC = XP_AIN | XM_HIZ | YP_EXTVLT \ 11 | YM_GND |XP_PULL_UP_DIS | XP_PST(Y_AXIS_MODE); } 12 #define start_adc_x(){ ADCCON = PRESCALE_EN | PRSCVL(49) | \ 13 ADC_INPUT(ADC_IN5) | ADC_START_BY_RD_EN | \ 14 ADC_NORMAL_MODE; \ 15 ADCDAT0; } 16 #define start_adc_y(){ ADCCON = PRESCALE_EN | PRSCVL(49) | \ 17 ADC_INPUT(ADC_IN7) | ADC_START_BY_RD_EN | \ 18 ADC_NORMAL_MODE; \ 19 ADCDAT1; } 20 #define disable_ts_adc(){ ADCCON &= ~(ADCCON_READ_START); } |
12.2.4 触摸屏驱动模块加载和卸载函数
在触摸屏设备驱动的模块加载函数中,要完成申请设备号、添加cdev、申请中断、设置触摸屏控制引脚(YPON、YMON、XPON、XMON)等多项工作,如代码清单12.16所示。
代码清单12.16 触摸屏设备驱动的模块加载函数
1 static int __init s3c2410_ts_init(void) 2 { 3 int ret; 4 tsEvent = tsEvent_dummy; 5 ...//申请设备号,添加cdev 6 7 /* 设置XP、YM、YP和YM对应引脚 */ 8 set_gpio_ctrl(GPIO_YPON); 9 set_gpio_ctrl(GPIO_YMON); 10 set_gpio_ctrl(GPIO_XPON); 11 set_gpio_ctrl(GPIO_XMON); 12 13 /* 使能触摸屏中断 */ 14 ret = request_irq(IRQ_ADC_DONE, s3c2410_isr_adc, 15 SA_INTERRUPT, DEVICE_NAME,s3c2410_isr_adc); 16 if (ret) 17 goto adc_failed; 18 ret = request_irq(IRQ_TC, s3c2410_isr_tc, SA_INTERRUPT, 19 DEVICE_NAME,s3c2410_isr_tc); 20 if (ret) 21 goto tc_failed; 22 23 /*置于等待触点中断模式*/ 24 wait_down_int(); 25 26 printk(DEVICE_NAME " initialized\n"); 27 28 return 0; 29 tc_failed: 30 free_irq(IRQ_ADC_DONE, s3c2410_isr_adc); 31 adc_failed: 32 return ret; 33 } |
在触摸屏设备驱动的模块卸载函数中,要完成释放设备号、删除cdev、释放中断等工作,如代码清单12.17所示。
代码清单12.17 触摸屏设备驱动模块卸载函数
1 static void __exit s3c2410_ts_exit(void) 2 { 3 ...//释放设备号,删除cdev 4 free_irq(IRQ_ADC_DONE, s3c2410_isr_adc); 5 free_irq(IRQ_TC, s3c2410_isr_tc); 6 } |
由12.2.1小节对触摸屏和ADC模式的分析,可知触摸屏驱动中会产生两类中断,一类是触点中断(INT-TC),一类是X/Y位置转换中断(INT-ADC)。在前一类中断发生后,若之前处于PEN_UP状态,则应该启动X/Y位置转换。另外,将抬起中断也放在INT-TC处理程序中,它会调用tsEvent()完成等待队列和信号的释放,如代码清单12.18所示。
代码清单12.18 触摸屏设备驱动的触点/抬起中断处理程序
1 static void s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg) 2 { 3 spin_lock_irq(&(tsdev.lock)); 4 if (tsdev.penStatus == PEN_UP) 5 { 6 start_ts_adc(); //开始X/Y位置转换 7 } 8 else 9 { 10 tsdev.penStatus = PEN_UP; 11 DPRINTK("PEN UP: x: %08d, y: %08d\n", x, y); 12 wait_down_int();//置于等待触点中断模式 13 tsEvent(); 14 } 15 spin_unlock_irq(&(tsdev.lock)); 16 } |
当X/Y位置转换中断发生后,应读取X、Y的坐标值,填入缓冲区,如代码清单12.19所示。
代码清单12.19 触摸屏设备驱动X/Y位置转换中断处理程序
1 static void s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg) 2 { 3 spin_lock_irq(&(tsdev.lock)); 4 if (tsdev.penStatus == PEN_UP) 5 s3c2410_get_XY(); //读取坐标 6 #ifdef HOOK_FOR_DRAG 7 else 8 s3c2410_get_XY(); 9 #endif 10 spin_unlock_irq(&(tsdev.lock)); 11 } |
上述程序中调用的s3c2410_get_XY()用于获得X、Y坐标,它使用代码清单12.15的硬件操作宏实现,如代码清单12.20所示。
代码清单12.20 触摸屏设备驱动中获得X、Y坐标
1 static inline void s3c2410_get_XY(void) 2 { 3 if (adc_state == 0) 4 { 5 adc_state = 1; 6 disable_ts_adc(); //禁止INT-ADC 7 y = (ADCDAT0 &0x3ff); //读取坐标值 8 mode_y_axis(); 9 start_adc_y(); //开始y位置转换 10 } 11 else if (adc_state == 1) 12 { 13 adc_state = 0; 14 disable_ts_adc(); //禁止INT-ADC 15 x = (ADCDAT1 &0x3ff); //读取坐标值 16 tsdev.penStatus = PEN_DOWN; 17 DPRINTK("PEN DOWN: x: %08d, y: %08d\n", x, y); 18 wait_up_int(); //置于等待抬起中断模式 19 tsEvent(); 20 } 21 } |
代码清单12.18、12.20中调用的tsEvent最终为tsEvent_raw(),这个函数很关键,当处于PEN_DOWN状态时调用该函数,它会完成缓冲区的填充、等待队列的唤醒以及异步通知信号的释放;否则(处于PEN_UP状态),将缓冲区头清0,也唤醒等待队列并释放信号,如代码清单12.21所示。
代码清单12.21 触摸屏设备驱动的tsEvent_raw()函数
1 static void tsEvent_raw(void) 2 { 3 if (tsdev.penStatus == PEN_DOWN) 4 { 5 /*填充缓冲区*/ 6 BUF_HEAD.x = x; 7 BUF_HEAD.y = y; 8 BUF_HEAD.pressure = PEN_DOWN; 9 10 #ifdef HOOK_FOR_DRAG 11 ts_timer.expires = jiffies + TS_TIMER_DELAY; 12 add_timer(&ts_timer);//启动定时器 13 #endif 14 } 15 else 16 { 17 #ifdef HOOK_FOR_DRAG 18 del_timer(&ts_timer); 19 #endif 20 21 /*填充缓冲区*/ 22 BUF_HEAD.x = 0; 23 BUF_HEAD.y = 0; 24 BUF_HEAD.pressure = PEN_UP; 25 } 26 27 tsdev.head = INCBUF(tsdev.head, MAX_TS_BUF); 28 wake_up_interruptible(&(tsdev.wq)); //唤醒等待队列 29 30 #ifdef USE_ASYNC 31 if (tsdev.aq) 32 kill_fasync(&(tsdev.aq), SIGIO, POLL_IN);//异步通知 33 #endif 34 } |
在包含了对拖动轨迹支持的情况下,定时器会被启用,周期为10ms,在每次定时器处理函数被引发时,调用start_ts_adc()开始X/Y位置转换过程,如代码清单12.22所示。
代码清单12.22 触摸屏设备驱动的定时器处理函数
1 #ifdef HOOK_FOR_DRAG 2 static void ts_timer_handler(unsigned long data) 3 { 4 spin_lock_irq(&(tsdev.lock)); 5 if (tsdev.penStatus == PEN_DOWN) 6 { 7 start_ts_adc(); //开始X/Y位置转换 8 } 9 spin_unlock_irq(&(tsdev.lock)); 10 } 11 #endif |
在触摸屏设备驱动的打开函数中,应初始化缓冲区、penStatus和定期器、等待队列及tsEvent时间处理函数指针,如代码清单12.23所示。
代码清单12.23 触摸屏设备驱动的打开函数
1 static int s3c2410_ts_open(struct inode *inode, struct file *filp) 2 { 3 tsdev.head = tsdev.tail = 0; 4 tsdev.penStatus = PEN_UP;//初始化触摸屏状态为PEN_UP 5 #ifdef HOOK_FOR_DRAG //如果定义了拖动钩子函数 6 init_timer(&ts_timer);//初始化定时器 7 ts_timer.function = ts_timer_handler; 8 #endif 9 tsEvent = tsEvent_raw; 10 init_waitqueue_head(&(tsdev.wq));//初始化等待队列 11 12 return 0; 13 } |
触摸屏设备驱动的释放函数非常简单,删除为用于拖动轨迹所使用的定时器即可,如代码清单12.24所示。
代码清单12.24 触摸屏设备驱动的释放函数
1 static int s3c2410_ts_release(struct inode *inode, struct file *filp) 2 { 3 #ifdef HOOK_FOR_DRAG 4 del_timer(&ts_timer);//删除定时器 5 #endif 6 return 0; 7 } |
触摸屏设备驱动的读函数实现缓冲区中信息向用户空间的复制,当缓冲区有内容时,直接复制;否则,如果用户阻塞访问触摸屏,则进程在等待队列上睡眠,否则,立即返回-EAGAIN,如代码清单12.25所示。
代码清单12.25 触摸屏设备驱动的读函数
1 static ssize_t s3c2410_ts_read(struct file *filp, char *buffer, size_t count, 2 loff_t *ppos) 3 { 4 TS_RET ts_ret; 5 6 retry: 7 if (tsdev.head != tsdev.tail) //缓冲区有信息 8 { 9 int count; 10 count = tsRead(&ts_ret); 11 if (count) 12 copy_to_user(buffer, (char*) &ts_ret, count);//复制到用户空间 13 return count; 14 } 15 else 16 { 17 if (filp->f_flags &O_NONBLOCK) //非阻塞读 18 return - EAGAIN; 19 interruptible_sleep_on(&(tsdev.wq)); //在等待队列上睡眠 20 if (signal_pending(current)) 21 return - ERESTARTSYS; 22 goto retry; 23 } 24 25 return sizeof(TS_RET); 26 } |
在触摸屏设备驱动中,通过s3c2410_ts_poll()函数实现了轮询接口,这个函数的实现非常简单。它将等待队列添加到poll_table,当缓冲区有数据时,返回资源可读取标志,否则返回0,如代码清单12.26所示。
代码清单12.26 触摸屏设备驱动的poll()函数
1 static unsigned int s3c2410_ts_poll(struct file *filp, struct poll_table_struct *wait) 2 { 3 poll_wait(filp, &(tsdev.wq), wait);//添加等待队列到poll_table 4 return (tsdev.head == tsdev.tail) ? 0 : (POLLIN | POLLRDNORM); 5 } |
而为了实现触摸屏设备驱动对应用程序的异步通知,设备驱动中要实现s3c2410_ts_fasync()函数,这个函数与第9章给出的模板完全一样,如代码清单12.27所示。
代码清单12.27 触摸屏设备驱动的fasync()函数
1 #ifdef USE_ASYNC 2 static int s3c2410_ts_fasync(int fd, struct file *filp, int mode) 3 { 4 return fasync_helper(fd, filp, mode, &(tsdev.aq)); 5 } 6 #endif |