Chinaunix首页 | 论坛 | 博客
  • 博客访问: 401886
  • 博文数量: 107
  • 博客积分: 2536
  • 博客等级: 少校
  • 技术积分: 783
  • 用 户 组: 普通用户
  • 注册时间: 2009-06-14 15:19
文章分类

全部博文(107)

文章存档

2017年(11)

2016年(8)

2015年(14)

2014年(32)

2012年(1)

2011年(1)

2010年(7)

2009年(33)

我的朋友

分类: LINUX

2009-09-17 14:39:34

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轴模式。为获得XY坐标,需首先进行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所示,包含XY坐标和状态(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

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.5  触摸屏驱动中断、定时器处理程序

由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位置转换中断发生后,应读取XY的坐标值,填入缓冲区,如代码清单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()用于获得XY坐标,它使用代码清单12.15的硬件操作宏实现,如代码清单12.20所示。

代码清单12.20  触摸屏设备驱动中获得XY坐标

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;

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

12.2.6  触摸屏设备驱动的打开、释放函数

在触摸屏设备驱动的打开函数中,应初始化缓冲区、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 }

12.2.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;

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 }

12.2.8  触摸屏设备驱动的轮询与异步通知

在触摸屏设备驱动中,通过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

12.2.9  Linux输入子系统

12.1及12.2.1~12.2.8节分别讲解按键与触摸屏的设备驱动,实际上,在Linux系统中,一种更值得推荐的实现这类设备驱动的方法是利用input子系统。

Linux系统提供了input子系统,按键、触摸屏、键盘、鼠标等输入都可以利用input接口函数来实现设备驱动,因此,12.1~12.2节的按键和触摸屏设备驱动都可以作为input设备驱动而实现。

在Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过InputCore和 Eventhandler最终到达用户空间。

通过input子系统,具体的输入设备驱动只需要完成如下工作。

l      在模块加载函数中告知input子系统它可以报告的事件。

设备驱动通过set_bit()告诉input子系统它支持哪些事件,如下所示:

set_bit(EV_KEY, button_dev.evbit);

l      在模块加载函数中注册输入设备。

注册输入设备的函数为:

int input_register_device(struct input_dev *dev);

l      在键被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时通过input_ report_xxx()报告发生的事件及对应的键值/坐标等状态。

主要的事件类型包括EV_KEY(按键事件)、EV_REL(相对值,如光标移动,报告的是相对最后一次位置的偏移)和EV_ABS(绝对值,如触摸屏和操纵杆,它们工作在绝对坐标系统)。

用于报告EV_KEY、EV_REL和EV_ABS事件的函数分别为:

void input_report_key(struct input_dev *dev, unsigned int code, int value);

void input_report_rel(struct input_dev *dev, unsigned int code, int value);

void input_report_abs(struct input_dev *dev, unsigned int code, int value);

input_sync()用于事件同步,它告知事件的接收者驱动已经发出了一个完整的报告。

例如,在触摸屏设备驱动中,一次坐标及按下状态的整个报告过程如下:

input_report_abs(input_dev, ABS_X, x);  //X坐标

input_report_abs(input_dev, ABS_Y, y);   //Y坐标

input_report_abs(input_dev, ABS_PRESSURE, pres); //压力

input_sync(input_dev);  //同步

l      在模块卸载函数中注销输入设备。

注销输入设备的函数为:

void input_unregister_device(struct input_dev *dev);

代码清单12.28给出了一个最简单的使用input接口实现按键设备驱动的范例,它在中断服务程序中向系统报告按键及同步事件。

代码清单12.28  最简单的input设备驱动

1  /*在按键中断中报告事件*/

2  static void button_interrupt(int irq, void *dummy, struct pt_regs *fp)

3  {

4    input_report_key(&button_dev, BTN_1, inb(BUTTON_PORT) &1);

5    input_sync(&button_dev);

6  }

8  static int _ _init button_init(void)

9  {

10   /*申请中断*/

11   if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL))

12   {

13     printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);

14     return  - EBUSY;

15   }

16

17   button_dev.evbit[0] = BIT(EV_KEY);    //支持EV_KEY事件

18   button_dev.keybit[LONG(BTN_0)] = BIT(BTN_0);

19

20   input_register_device(&button_dev);   //注册input设备

21 }

22

23 static void _ _exit button_exit(void)

24 {

25   input_unregister_device(&button_dev);   //注销input设备

26   free_irq(BUTTON_IRQ, button_interrupt); //释放中断

27 }

阅读(1666) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~