分类: LINUX
2009-04-23 14:50:37
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 |
转自:
基于S3C2410的触摸屏驱动程序设计 |
作者:沈阳农业大学 徐昕 朴在林 许童羽 李征明 时间:2007-02-06 来源:电子产品世界 |
关键词: |
摘要: 本文介绍了基于三星X微处理器,采用SPI接口与">控制器芯片完成">模块的设计。具体包括在操作系统中的软件驱动开发,采用内核定时器的下半部机制进行了">硬件中断程序设计,采用16个时钟周期的坐标转换时序,实现触摸点数据采集的方法,给出了坐标采集的流程。设计完成的">驱动程序在博创公司教学实验设备UP-NETARM2410-S平台上运行效果良好。
关键词: ;SPI;;">;S;驱动程序
引言
随着信息家电和通讯设备的普及,作为与用户交互的终端媒介,">在生活中得到广泛的应用。如何在系统中集成">模块以及在嵌入式操作系统中实现其驱动程序,都成为嵌入式系统设计者需要考虑的问题。本文主要介绍在三星X微处理器的硬件平台上进行基于的">驱动程序设计。
硬件实现方案
SPI接口是Motorola推出的一种同步串行接口,采用全双工、四线通信系统,X是三星推出的自带">接口的ARM920T内核芯片,为Burr-Brown生产的一款性能优异的">控制器。本文采用SPI接口的">控制器外接四线电阻式">,这种方式最显著的特点是响应速度更快、灵敏度更高,微处理器与">控制器间的通讯时间大大减少,提高了微处理器的效率。与的硬件连接如图1所示,鉴于差分工作模式的优点,在硬件电路中将其配置为差分模式。
图1 ">输入系统示意图
系统下的驱动程序
设备驱动程序是Linux内核的重要组成部分,控制了操作系统和硬件设备之间的交互。Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,成为设备文件。应用程序可以打开、关闭、读写这些设备文件,对设备的操作就像操作普通的数据文件一样简便。为开发便利、提高效率,本设计采用可安装模块方式开发调试">驱动程序。
设备驱动在加载时首先需要调用入口函数init_module(),该函数完成设备驱动的初始化工作。其中最重要的工作就是向内核注册该设备,对于字符设备调用register_chrdev()完成注册,对于块设备需要调用register_blkdev()完成注册。注册成功后,该设备获得了系统分配的主设备号、自定义的次设备号,并建立起与文件系统的关联。字符设备驱动程序向Linux内核注册登记时,在字符设备向量表chrdevs中增加一个device_struct数据结构条目,这个设备的主设备标识符用作这个向量表的索引。向量表中的每一个条目,即一个device_struct数据结构包括两个元素:一个登记的设备驱动程序的名称的指针和一个指向一组文件操作的指针。这块文件操作本身位于这个设备的字符设备驱动程序中,每一个都处理特定的文件操作,比如打开、读写和关闭。所谓登记,就是将由模块提供的file_operations结构指针填入device_struct数据结构数组的某个表项。登记以后,位于上层的模块(内核)可以“看见”这个模块了。但是,应用程序却还不能“看见”它,因而还不能通过系统调用它。要使应用程序能“看见”这个模块或者它所驱动的设备,就要在文件系统中为其创建一个代表它的节点。通过系统调用mknod()创建代表此项设备的文件节点——设备入口点,就可使一项设备在系统中可见,成为应用程序可以访问的设备。另外,设备驱动在卸载时需要回收相应的资源,令设备的相应寄存器值复位并从系统中注销该设备。
Linux操作系统通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。设备驱动模块的功能就是扩展内核的功能,主要完成两部分任务:一个是系统调用,另一个是处理中断。图2是一个设备驱动模块动态挂接、卸载和系统调用的全过程。系统调用部分则是对设备的操作过程,比如open,read,write,ioctl等操作,设备驱动程序所提供的这组入口点由几个结构向系统进行说明,分别是file_operations数据结构、inode数据结构和file 数据结构。内核内部通过file结构识别设备,通过file_operations数据结构提供文件系统的入口点函数,也就是访问设备驱动的函数,结构中的每一个成员都对应着一个系统调用。在嵌入式系统的开发中,我们一般仅仅实现其中几个接口函数:read、write、open、ioctl及release就可以完成应用系统需要的功能。写驱动程序的任务之一就是完成file_operations中的函数指针。
">驱动程序设计
">驱动程序中重要数据结构
typedef struct {
unsigned short pressure;
unsigned short x;
unsigned short y;
unsigned short pad;
} TS_RET;
typedef struct {
unsigned int PenStatus;
TS_RET buf[MAX_TS_BUF];
unsigned int head, tail;
wait_queue_head_t wq;
spinlock_t lock;
} TS_DEV;
static struct file_operations s3c2410_fops = {
owner: THIS_MODULE,
open: s3c2410_ts_open,
read: s3c2410_ts_read, release: s3c2410_ts_release,
poll: s3c2410_ts_poll, };
在程序中有三个重要的数据结构:用于表示笔触点数据信息的结构TS_RET,表示中有关">控制器信息的结构TS_DEV,以及驱动程序与应用程序的接口file_operations结构的s3c2410_fops。
TS_RET结构体中的信息就是驱动程序提供给上层应用程序使用的信息,用来存储">的返回值。上层应用程序通过读接口,从底层驱动中读取信息,并根据得到的值进行其他方面的操作。
TS_DEV结构用于记录">运行的各种状态,PenStatus包括PEN_UP、PEN_DOWN和PEN_FLEETING。buf[MAX_TS_BUF]是用来存放数据信息的事件队列,head、tail分别指向事件队列的头和尾。程序中的笔事件队列是一个环形结构,当有事件加入时,队列头加一,当有事件被取走时,队列尾加一,当头尾位置指针一致时读取笔事件的信息,进程会被安排进入睡眠。wq等待队列,包含一个锁变量和一个正在睡眠进程链表。当有好几个进程都在等待某件事时,Linux会把这些进程记录到这个等待队列。它的作用是当没有笔触事件发生时,阻塞上层的读操作,直到有笔触事件发生。lock使用自旋锁,自旋锁是基于共享变量来工作的,函数可以通过给某个变量设置一个特殊值来获得锁。而其他需要锁的函数则会循环查询锁是否可用。MAX_TS_BUF的值为16,即在没有被读取之前,系统缓冲区中最多可以存放16个笔触数据信息。
s3c2410_fops就是内核对驱动的调用接口,完成了将驱动函数映射为标准接口。上面的这种特殊表示方法不是标准C的语法,而是GNU编译器的一种特殊扩展,它使用名字进行结构字段的初始化,它的好处体现在结构清晰,易于理解,并且避免了结构发生变化带来的许多问题。
init_module函数
这是模块的入口函数。在函数内部通过s3c2410_ts_init( )实现模块的初始化工作。在本设计中设备与系统之间以中断方式进行数据交换。整个">的驱动程序处理比较复杂,而且耗时较长,因而">驱动程序不可能在中断服务程序中完成。在Linux操作系统中一般把中断处理切为两个部分或两半。中断处理程序是上半部——接收到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接收的中断进行应答或复位硬件。这些工作都是在所有中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。在Linux中下半部的实现有多种机制。按">时,从输出的数值有一个抖动过程,即从ADS7846输出的数值有一个不稳定时期,这个过程大约为10ms。所以中断处理程序的下半部处理函数采用内核定时器机制,使下半部在中断发生50ms后再作处理。这样有效地避开了输出值的不稳定时期,使中断服务程序和中断处理任务串行化,达到了处理时间较长的">事件的目的。驱动程序通过request_irq函数注册并激活一个中断处理程序,以便处理中断。
图2 设备驱动在内核中的挂接、卸载和系统调用过程
int reguest_irq(unsigned int irq, void(*handler)(int, void *, struct pt_regs *), unsigned long irq_flags, const char *dev_name, void *dev_id)
参数irq表示所要申请的中断号;handler为向系统登记的中断处理子程序,中断产生时由系统来调用;dev_name为设备名;dev_id为申请时告诉系统的设备标识符;irq_flags是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是中断处理程序是快速处理程序还是慢速处理程序。
本设计中">控制器的中断输出通过外部中断5接在中断控制器上,当">上有触摸事件发生时,会引发中断号为IRQ_EINT5的中断服务程序s3c2410_isr_tc()。图3所示为该中断处理程序的流程图。
图3 ">硬件中断处理程序流程图
在s3c2410_isr_tc()中设定了定时器的定时时间为50ms,并立即激活。因此有">硬件中断的情况下50ms后就会引发定时中断,中断服务程序为ts_timer_handler(),这个程序实现了">中断的下半部,即在过了抖动时间之后如果">确实有有效事件发生则采集">坐标,并将定时器的时间重新设为100ms并重新激活,这样做的目的是如果触摸笔是拖动的情况,以后每100ms采集一次坐标值,并存入缓冲区,如果不是拖动在采集一次坐标值之后,在第二次进入ts_timer_handler()时,查询管脚的状态值,则变为高电平,就将">状态tsdev.PenStatus设为PEN_UP,并释放定时器,为下次">事件做好准备,定时中断服务程序流程图如图4所示。
图4 定时中断服务程序流程图
在s3c2410_ts_init()中的另一个重要任务是执行接口函数s3c2410_ts_open(),在这个函数中初始化缓冲区的头尾指针、">状态变量及">事件等待队列。
module_exit()
该函数调用s3c2410_ts_exit(),主要任务是撤销驱动程序向内核的登记以及释放申请的中断资源。
接口函数s3c2410_ts_read( )
这个函数实现的任务是将事件队列从设备缓存中读到用户空间的数据缓存中。实现的过程主要是通过一个循环,只有在事件队列的头、尾指针不重合时,才能成功的从tsdev.tail指向的队列尾部读取到一组触摸信息数据,并退出循环。否则调用读取函数的进程就要进入睡眠。
坐标读取函数s3c2410_get_XY()
在定时器中断处理程序中,当查询到与相连的EINT5/GPF5为低电平时,即表示有有效事件,应该调用s3c2410_get_XY()函数采集笔触信息。
有多种转换时序,时序规定了芯片与设备及CPU间是如何配合工作的。设计中采用16个时钟周期启动一次转换的坐标转换方式。的操作时序如图5所示。坐标的读取是通过多次采集取平均值的方法,以X坐标的读取为例,其读取过程如图6所示。循环过程中的每一步都在8个时钟周期内完成,数据的处理严格按照时序进行,Y坐标的采集与X坐标类似。
图5 操作时序
图6 X坐标采集流程
结语
在">的设计中,抗干扰设计是难点和重点,直接关系到">的工作性能。实验发现坐标采集时,丢弃第一次采集值读取的坐标转换值效果较好。本文所介绍的驱动程序已经在博创公司的教学实验设备UP-NETARM2410-S平台上经过实际验证,从数据稳定性和系统负载的角度看,效果良好。同时通过修改程序内部的定时器时钟频率可以改变笔在屏上移动所产生的数据量。
参考文献:
1. 毛德操,胡希明著.Linux内核源代码情景分析.杭州:浙江大学出版社,2001
2. 孙天泽,袁文菊,张海峰等.嵌入式设计及Linux驱动开发指南.北京:电子工业出版社,2005
3. R Love. Linux内核设计与实现. 陈莉君,康华,张波等译.北京:机械工业出版社,2006
4. 殷惠莉,刘少君,黄道平.基于uClinux">的设计.电子工程师.2004(2)