linux-2.6.32在mini2440开发板上移植(10)之触摸屏工作原理以及驱动程序详细分析

来源:互联网 发布:网络恐怖主义论文 编辑:程序博客网 时间:2024/06/08 11:14

编者:这部分还是比较长的,因此没有放在上个移植里面。这里主要说触摸屏的工作原理,以及对上述驱动程序代码的简单分析。分析中参考了网上的很多资料。感谢原作者的无私奉献,因为涉及多篇,在此就没有注出原作的链接。

 

本文分为三个部分,第一部分讲叙硬件知识,包括触摸屏的原理以及SCC2440 SOC 上的触摸屏是如何工作的。第二部分分析输入设备子系统的框架,并进行相应的代码分析。第三部分利用上述的原理来分析mini2440 的触摸屏驱动

1.1、电阻式触摸屏工作原理原理

       触摸屏附着在显示器的表面,与显示器相配合使用,如果能测量出触摸点在屏幕上的坐标位置,则可根据显示屏上对应坐标点的显示内容或图符获知触摸者的意图。触摸屏按其技术原理可分为五类:矢量压力传感式、电阻式、电容式、红外线式、表面声波式,其中电阻式触摸屏在嵌入式系统中用的较多。电阻触摸屏是一块4 层的透明的复合薄膜屏,最下面是玻璃或有机玻璃构成的基层,最上面是一层外表面经过硬化处理从而光滑防刮的塑料层,中间是两层金属导电层,分别在基层之上和塑料层内表面,两导电层之间有许多细小的透明隔离点把它们隔开。当手指触摸屏幕时,两导电层在触摸点处接触。触摸屏的两个金属导电层是触摸屏的两个工作面,在每个工作面的两端各涂有一条银胶,称为该工作面的一对电极,若在一个工作面的电极对上施加电压,则在该工作面上就会形成均匀连续的平行电压分布。当在X 方向的电极对上施加一确定的电压,而Y 方向电极对上不加电压时,在X 平行电压场
中,触点处的电压值可以在Y+(或Y-)电极上反映出来,通过测量Y+电极对地的电压大小,便可得知触点的X 坐标值。同理,当在Y 电极对上加电压,而X 电极对上不加电压时,通过测量X+电极的电压,便可得知触点的Y 坐标。电阻式触摸屏有四线和五线两种。四线式触摸屏的X 工作面和Y 工作面分别加在两个导电层上,共有四根引出线,分别连到触摸屏的X 电极对和Y 电极对上。五线式触摸屏把X 工作面和Y 工作面都加在玻璃基层的导电涂层上,但工作时,仍是分时加电压的,即让两个方向的电压场分时工作在同一工作面上,而外导电层则仅仅用来充当导体和电压测量电极。因此,五线式触摸屏的引出线需为5 根。

1.2、 在S3C2440 中的触摸屏接口
     SOC S3C2440 的触摸屏接口是与ADC 接口结合在一起的。转换速率:当PCLK=50MHz 时,分频设为49,则10 位的转换计算如下:
When the GCLK frequency is 50MHz and the prescaler value is 49,
A/D converter freq. = 50MHz/(49+1) = 1MHz
Conversion time = 1/(1MHz / 5cycles) = 1/200KHz = 5 us
This A/D converter was designed to operate at maximum 2.5MHz clock, so the conversion rate can
go up to 500 KSPS.
触摸屏接口的模式有以下几种:
普通ADC 转换模式
独立X/Y 位置转换模式
自动X/Y 位置转换模式


等待中断模式
我们主要接受触摸屏接口的等待中断模式和自动X/Y 位置转换模式(驱动程序中会用到):
自动转换模式操作流程如下:触摸屏控制器自动转换X,Y 的触摸位置,当转换完毕后将数据分别存放在
寄存器ADCDAT0 和ADCDAT1.并产生INT_ADC 中断通知转换完毕。
等待中断模式:
Touch Screen Controller generates interrupt (INT_TC) signal when the Stylus is down. Waiting for
Interrupt Modesetting value is rADCTSC=0xd3; // XP_PU, XP_Dis, XM_Dis, YP_Dis, YM_En.
当触摸后,触摸屏控制器产生INT_TC 中断,四个引脚设置应该为:
引脚 XP XM YP YM
状态 PULL UP/XP Disable Disable (初始值即是) Disable Enable
设置 1 0 1 1
当中断产生后,X/Y 的位置数据可以选择独立X/Y 位置转换模式,和自动X/Y 位置转换模式进行读取,
采用自动X/Y 位置转换模式进行读取需要对我们已经设置的TSC 寄存器进行更改,在原有的基础上或上
S3C2410_ADCTSC_PULL_UP_DISABLE | S3C2410_ADCTSC_AUTO_PST |
S3C2410_ADCTSC_XY_PST(0)。
数据转换完毕后,也会产生中断。

3. 输入子系统模型分析
3.1 整体框架:
输入子系统包括三个部分设备驱动、输入核心、事件处理器。
第一部分是连接在各个总线上的输入设备驱动,在我们的SOC 上,这个总线可以使虚拟总线platformbus,他们的作用是将底层的硬件输入转化为统一事件型式,向输入核心(Input core)汇报.

第二部分输入核心的作用如下:
(1) 调用input_register_device() used to 添加设备,调用input_unregister_device() 除去设备。(下面会结合触摸屏驱动讲述)
(2) 在/PROC 下产生相应的设备信息,下面这个例子即是:
/proc/bus/input/devices showing a USB mouse:
I: Bus=0003 Vendor=046d Product=c002 Version=0120
N: Name="Logitech USB-PS/2 Mouse M-BA47"
P: Phys=usb-00:01.2-2.2/input0
H: Handlers=mouse0 event2
B: EV=7
B: KEY=f0000 0 0 0 0 0 0 0 0
B: REL=103
(3) 通知事件处理器对事件进行处理
第三部分是事件处理器:
         输入子系统包括了您所需要的大所属处理器,如鼠标、键盘、joystick,触摸屏,也有一个通用的处理器被叫做event handler(对于内核文件evdev.C).需要注意的是随着内核版本的发展,event handler 将用来处理更多的不同硬件的输入事件。在Linux2.6.29 版本中,剩下的特定设备事件处理就只有鼠标和joystick。这就意味着越来越多的输入设备将通过event handler 来和用户空间打交道。事件处理层的主要作用就是和用户空间打交道,我们知道Linux 在用户空间将所有设备当成文件来处理,在一般的驱动程序中都有提供fops 接口,以及在/dev 下生成相应的设备文件nod,而在输入子系统的驱动中,这些动作都是在事件处理器层完成的,我们看看evdev.C 相关代码吧。
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
这是该模块的注册程序,将在系统初始化时被调用。初始化得过程很简单,就一句话,不过所有的秘密都被保藏在evdev_handler 中了:
static struct input_handler evdev_handler = {
      .event = evdev_event,
      .connect = evdev_connect,
      .disconnect = evdev_disconnect,
      .fops = &evdev_fops,
      .minor = EVDEV_MINOR_BASE,
      .name = "evdev",
      .id_table = evdev_ids,
   };
先看connect 函数中如下的代码:
snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

evdev->handle.dev = input_get_device(dev);
evdev->handle.name = evdev->name;
dev_set_name(&evdev->dev, evdev->name);
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
evdev->dev.class = &input_class; evdev
->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
error = device_add(&evdev->dev);
注意黑色的部分这将会在/sys/device/viture/input/input0/event0 这个目录就是在这里生成的,在event下会有一个dev 的属性文件,存放着设备文件的设备号,,这样 udev 就能读取该属性文件获得设备号,从而在/dev 目录下创建设备节点/dev/event0


再看evdev_fops 成员:
static const struct file_operations evdev_fops = {
     .owner = THIS_MODULE,
     .read = evdev_read,
     .write = evdev_write,
     .poll = evdev_poll,
     .open = evdev_open,
     .release = evdev_release, .unlocked_ioctl = evdev_ioctl,
     #ifdef CONFIG_COMPAT
     .compat_ioctl = evdev_ioctl_compat,
     #endif
    .fasync = evdev_fasync, .flush = evdev_flush
 };
看过LDD3 的人都知道,这是设备提供给用户空间的接口,用来提供对设备的操作,其中evdev_ioctl提供了很多命令,相关的命令使用参照《Using the Input Subsystem, Part II》

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 <plat/regs-adc.h>
#include <mach/regs-gpio.h>

/* For ts.dev.id.version */
#define S3C2410TSVERSION 0x0101
//x为0时为等待按下中断,x为1是为等待抬起中断
#define WAIT4INT(x)  (((x)<<8) | \
       S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
       S3C2410_ADCTSC_XY_PST(3))
//自动连续测量X坐标和Y坐标
#define AUTOPST      (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \
       S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))
//设备名
static char *tq2440ts_name = "TQ2440 TouchScreen";

static struct input_dev *dev;//
static long xp;
static long yp;
static int count;

extern struct semaphore ADC_LOCK;//申明一信号量该信号量在其他文件中定义
//该标志在按下中断处理函数中置1,抬起处理函数中置0,在AD转换结束中断处理函数中判断,
//如果为1则读取AD转换的数字,如果为0则什么也不做。
static int OwnADC = 0;
//寄存器基地址
static void __iomem *base_addr;
//管脚配置
static inline void tq2440_ts_connect(void)
{
 s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPG12_XMON);
 s3c2410_gpio_cfgpin(S3C2410_GPG13, S3C2410_GPG13_nXPON);
 s3c2410_gpio_cfgpin(S3C2410_GPG14, S3C2410_GPG14_YMON);
 s3c2410_gpio_cfgpin(S3C2410_GPG15, S3C2410_GPG15_nYPON);
}
//定时器定时时间到处理函数,该函数在按下抬起中断处理函数中直接调用,
//在AD转换结束中断处理函数中触发定时器经延时后被调用
static void touch_timer_fire(unsigned long data)
{
   unsigned long data0;
   unsigned long data1;
 int updown;

   data0 = ioread32(base_addr+S3C2410_ADCDAT0);
   data1 = ioread32(base_addr+S3C2410_ADCDAT1);
//updown为1则被按下,为0 则为抬起
  updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

  if (updown) {//按下中断执行以下语句
   if (count != 0)
   {
   long tmp;
                                                                                                
   tmp = xp;
   xp = yp;
   yp = tmp;
                                                                                                
   xp >>= 2;//四次AD转换求平均值
   yp >>= 2;

    input_report_abs(dev, ABS_X, xp);
    input_report_abs(dev, ABS_Y, yp);//将x,y的值发向用户空间

    input_report_key(dev, BTN_TOUCH, 1);//报告光标按下事件
    input_report_abs(dev, ABS_PRESSURE, 1);
    input_sync(dev);//表示报告结束
   }
/*以下五句作为在按下中断处理函数中直接调用该函数
时的执行的语句,而以上语句为在AD转换中断处理函数中,当4次AD转换结束时,触发定时器
经延时而调用该函数时执行的语句(向用户空间报告按下的结果)。以下五句也将在报告完后被执行,
用于初始化变量,并触发第二个四次AD转换。这样的AD转换会一直执行直到光标抬起即updown为0
*/
   xp = 0;
   yp = 0;
   count = 0;
//每次按下有四次AD转换,以下为在按下中断中触发的第一次AD转换,其余三次在AD转换中断处理函数中触发
   iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
   iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
  }
  else
  {
   /*对光标抬起的处理有两处,此处和抬起中断函数中。本函数可以触发AD转换,由本函数出发的
   AD转换将导致连续四次的AD转换。在光标按下和抬起的过程中本函数可能被很多次调用,第一次
   是在按下中断函数中调用,以后各次都是在四次AD转换完后的AD转换结束中断函数中触发定时器
   经延时后调用。所以整个时间可以分为两个时间段,一个是等待本函数被调用的时间过程,二是四
   次AD转换的时间过程。光标的抬起可能发生在这两个时间段的任意一个中。当光标抬起在前一个
   时间段时,中断抬起函数会被执行,即执行 这两句OwnADC = 0;up(&ADC_LOCK);而抬起在后
   一个时间段时中断函数不会被执行。因为只有WAIT4INT(1)时抬起中断才会被执行,而在AD转换
   过程中抬起中断不会被执行。所以抬起中断处理函数不一定会被执行,而此处肯定会被执行*/
   count = 0;

   input_report_key(dev, BTN_TOUCH, 0);//向用户空间报告光标抬起事件
   input_report_abs(dev, ABS_PRESSURE, 0);
   input_sync(dev);//报告结束

   iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);//置于按下中断等待状态
  if (OwnADC)//如果抬起中断函数执行则此处不执行
  {
   OwnADC = 0;
   up(&ADC_LOCK);
  }
  }
}

static struct timer_list touch_timer =//定义一内核定时器
  TIMER_INITIALIZER(touch_timer_fire, 0, 0);//初始化定时器赋予处理函数touch_timer_fire
//光标按下抬起抬起中断处理函数
static irqreturn_t stylus_updown(int irq, void *dev_id)
{
 unsigned long data0;
 unsigned long data1;
 int updown;

 if (down_trylock(&ADC_LOCK) == 0)//获取信号量,在抬起处理函数中释放
 {
  OwnADC = 1;//该标志置1表示处于光标按下状态中,在光标抬起处理函数中清零
  data0 = ioread32(base_addr+S3C2410_ADCDAT0);
  data1 = ioread32(base_addr+S3C2410_ADCDAT1);

  updown = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

  if (updown)
  {
   touch_timer_fire(0);//若为光标按下中断则调用该函数
  }
  else//光标抬起时执行的语句
  {
   OwnADC = 0;//清零
   up(&ADC_LOCK);//释放信号量
  }
 }

 return IRQ_HANDLED;//////
}

///AD转换结束中断处理函数
static irqreturn_t stylus_action(int irq, void *dev_id)
{
 unsigned long data0;
 unsigned long data1;

 if (OwnADC)//OwnADC为1表示现在处于光标按下中断中
 {
  data0 = ioread32(base_addr+S3C2410_ADCDAT0);
  data1 = ioread32(base_addr+S3C2410_ADCDAT1);

  xp += data0 & S3C2410_ADCDAT0_XPDATA_MASK;
  yp += data1 & S3C2410_ADCDAT1_YPDATA_MASK;
  count++;

  if (count < (1<<2))//四次AD转换,将四次转换值相加求平均值
  {//触发AD转换
   iowrite32(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, base_addr+S3C2410_ADCTSC);
   iowrite32(ioread32(base_addr+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, base_addr+S3C2410_ADCCON);
  }
  else
  {
   mod_timer(&touch_timer, jiffies+1);//触发内核定时器
   iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);//将设备置于等待抬起中断状态
  }
 }

 return IRQ_HANDLED;
}

static struct clk *adc_clock;

static int __init tq2440ts_init(void)
{
 struct input_dev *input_dev;

 adc_clock = clk_get(NULL, "adc");//获取时钟"adc"
 if (!adc_clock)
 {
  printk(KERN_ERR "failed to get adc clock source\n");
  return -ENOENT;
 }
 clk_enable(adc_clock);//使能时钟
//以S3C2410_PA_ADC为起点映射一段IO内存
 base_addr=ioremap(S3C2410_PA_ADC,0x20);
 if (base_addr == NULL)
 {
  printk(KERN_ERR "Failed to remap register block\n");
  return -ENOMEM;
 }

 /* Configure GPIOs */
 tq2440_ts_connect();//配置管脚

 iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),base_addr+S3C2410_ADCCON);
 iowrite32(0xffff,  base_addr+S3C2410_ADCDLY);
 iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);//将设备置于等待按下中断状态

 /* Initialise input stuff */
 input_dev = input_allocate_device();//为输入设备的结构体input_dev分配内存并做相应初始化

 if (!input_dev)
 {
  printk(KERN_ERR "Unable to allocate the input device !!\n");
  return -ENOMEM;
 }

 dev = input_dev;
 dev->evbit[0] = BIT(EV_SYN) | BIT(EV_KEY) | BIT(EV_ABS);//设置其支持的事件
 dev->keybit[BITS_TO_LONGS(BTN_TOUCH)] = BIT(BTN_TOUCH);//设置支持的keybit
 input_set_abs_params(dev, ABS_X, 0, 0x3FF, 0, 0);//设置其最大最小值
 input_set_abs_params(dev, ABS_Y, 0, 0x3FF, 0, 0);
 input_set_abs_params(dev, ABS_PRESSURE, 0, 1, 0, 0);

 dev->name = tq2440ts_name;
 dev->id.bustype = BUS_RS232;
 dev->id.vendor = 0xDEAD;
 dev->id.product = 0xBEEF;
 dev->id.version = S3C2410TSVERSION;
//申请AD转换结束中断
 if (request_irq(IRQ_ADC, stylus_action, IRQF_SHARED|IRQF_SAMPLE_RANDOM, tq2440ts_name, dev))
 {
  printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n");
  iounmap(base_addr);
  return -EIO;
 }//申请按下抬起中断
 if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM, tq2440ts_name, dev))
 {
  printk(KERN_ERR "tq2440_ts.c: Could not allocate ts IRQ_ADC !\n");
  iounmap(base_addr);
  return -EIO;
 }

 printk(KERN_INFO "%s successfully loaded\n", tq2440ts_name);
//注册输入设备,在/dev下会产生一个eventn(n为0,1,2。。。。)的设备结点
 input_register_device(dev);

 return 0;
}

static void __exit tq2440ts_exit(void)
{
 disable_irq(IRQ_ADC);
 disable_irq(IRQ_TC);
 free_irq(IRQ_TC,dev);//先禁能中断再释放
 free_irq(IRQ_ADC,dev);

 if (adc_clock)
 {
  clk_disable(adc_clock);
  clk_put(adc_clock);//先禁能时钟再释放
  adc_clock = NULL;
 }

 input_unregister_device(dev);
 iounmap(base_addr);
}


module_init(tq2440ts_init);
module_exit(tq2440ts_exit);

 

上面的分析还是比较精辟的,input_dev就带表了一个输入设备,在此就是触摸屏。static irqreturn_t stylus_updown(int irq, void *dev_id);这个函数是触摸屏按下抬起中断服务函数;static void touch_timer_fire(unsigned long data);这个是定时器中断服务函数。static irqreturn_t stylus_action(int irq, void *dev_id);这个是ADC中断服务函数,在转换完成时候,产生一个中断,然后进入此函数。在程序初始化后其执行的流程为:

(1) 如果触摸屏感觉到触摸,则进入updown ISR,如果能获取ADC_LOCK 则调用touch_timer_fire,启动ADC,
(2) ADC 转换,如果小于四次继续转换,如果四次完毕后,启动1 个时间滴答的定时器,停止ADC,也就是说在这个时间滴答内,ADC 是停止的,
(3) 这样可以防止屏幕抖动。
(4) 如果1 个时间滴答到时候,触摸屏仍然处于触摸状态则上报转换数据,并重启ADC,重复(2)
(5) 如果触摸笔释放了,则上报释放事件,并将触摸屏重新设置为等待按下中断状态。

着实,程序虽小,还是耐人寻味的。程序中的注释写的不错,可以仔细品味一下。

因为这是触摸屏的驱动,所以ADCTSC这个寄存器就很重要。

看下面的这个ADCTSC各个位的说明,以及下面两个宏的定义。这样对源码看更利于理解

#define WAIT4INT(x) (((x)<<8) | \

                       S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \

                       S3C2410_ADCTSC_XY_PST(3))

#define S3C2410_ADCTSC_YM_SEN                 (1<<7)

#define S3C2410_ADCTSC_YP_SEN                  (1<<6)

#define S3C2410_ADCTSC_XP_SEN                  (1<<4)

#define S3C2410_ADCTSC_XY_PST(x)     (((x)&0x3)<<0)

 

 

 

#define AUTOPST         (S3C2410_ADCTSC_YM_SEN | S3C2410_ADCTSC_YP_SEN | S3C2410_ADCTSC_XP_SEN | \

                       S3C2410_ADCTSC_AUTO_PST | S3C2410_ADCTSC_XY_PST(0))

#define S3C2410_ADCTSC_YM_SEN                 (1<<7)

#define S3C2410_ADCTSC_YP_SEN                  (1<<6)

#define S3C2410_ADCTSC_AUTO_PST            (1<<2)

#define S3C2410_ADCTSC_XY_PST(x)    (((x)&0x3)<<0)

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 动车票没赶上车怎么办 铁路用户名已存在要怎么办 铁路12306用户名忘了怎么办 铁路12306的用户名忘了怎么办 铁路12306注册名已存在怎么办 12306账号密码忘记了怎么办 12306登录名忘记了怎么办 电脑系统崩溃开不了机怎么办 高铁车票没赶上怎么办 机票错点了退票怎么办 快递号码留错了怎么办 物流号码留错了怎么办 12306身份信息被注册怎么办 12306注册身份信息重复怎么办 12306账号被注册了怎么办 高铁账号忘记了怎么办 铁路12306网站密码错误怎么办 网上买火车票密码忘了怎么办 快递没收到点了确认收货怎么办 快递没收到自动确认收货怎么办 房地产股市汇率一齐暴跌怎么办 尼日利亚落地签过期了怎么办 期货亏光了所有怎么办 期货钱亏完了该怎么办 做黄金亏损500万怎么办 炒黄金被骗35万怎么办 淘宝发货填错单号怎么办 发货单号填错了怎么办 发快递忘了单号怎么办 国际物流查不到物流怎么办 纸币上印邪教该怎么办 钥匙掉到电梯缝里怎么办 汽车电子钥匙铜线折一根怎么办 防盗门的锁不好开怎么办 同学帮刷饭卡说不用还钱了怎么办 em231电源指示灯不亮怎么办 运行广联达卡住怎么办 马桶被粪便(大便)堵了怎么办 子宫壁厚12mm怎么办 管子太多每次洗澡都是冷水怎么办 热水冷水装反了怎么办