zoukankan      html  css  js  c++  java
  • mini2440触摸屏驱动分析

    mini2440驱动分析系列之

    ---------------------------------------Mini2440触摸屏程序分析

    By JeefJiang July,8th,2009

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

    1.需要准备的硬件知识

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

    触摸屏附着在显示器的表面,与显示器相配合使用,如果能测量出触摸点在屏幕上的坐 标位置,则可根据显示屏上对应坐标点的显示内容或图符获知触摸者的意图。触摸屏按其技术原理可分为五类:矢量压力传感式、电阻式、电容式、红外线式、表面 声波式,其中电阻式触摸屏在嵌入式系统中用的较多。电阻触摸屏是一块4层的透明的复合薄膜屏,如图2所示,最下面是玻璃或有机玻璃构成的基层,最上面是一层外表面经过硬化处理从而光滑防刮的塑料层,中间是两层金属导电层,分别在基层之上和塑料层内表面,在两导电层之间有许多细小的透明隔离点把它们隔开。当手指触摸屏幕时,两导电层在触摸点处接触。

    触摸屏的两个金属导电层是触摸屏的两个工作面,在每个工作面的两端各涂有一条银胶,称为该工作面的一对电极,若在一个工作面的电极对上施加电压,则在该工作面上就会形成均匀连续的平行电压分布。如图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)。

    数据转换完毕后,也会产生中断。

    2. 输入子系统模型分析

    2.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         mini2440的触摸屏驱动

    3.1 初始化:

    static int __init s3c2410ts_init(void)

    {

           struct input_dev *input_dev;

           adc_clock = clk_get(NULL, "adc");

           if (!adc_clock) {

                  printk(KERN_ERR "failed to get adc clock source/n");

                  return -ENOENT;

           }

           clk_enable(adc_clock);

    //获取时钟,挂载APB BUS上的外围设备,需要时钟控制,ADC就是这样的设备。

           base_addr=ioremap(S3C2410_PA_ADC,0x20);

    I/O内存是不能直接进行访问的,必须对其进行映射,为I/O内存分配虚拟地址,这些虚拟地址以__iomem进行说明,但不能直接对其进行访问,需要使用专用的函数,如iowrite32

           if (base_addr == NULL) {

                  printk(KERN_ERR "Failed to remap register block/n");

                  return -ENOMEM;

           }

           /* Configure GPIOs */

         //  s3c2410_ts_connect();//2440不需要此步 2410 的芯片需要

           iowrite32(S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(0xFF),/

                       base_addr+S3C2410_ADCCON);//使能预分频和设置分频系数

           iowrite32(0xffff,  base_addr+S3C2410_ADCDLY);//设置ADC延时,在等待中断

    模式下表示产生INT_TC的间隔时间

           iowrite32(WAIT4INT(0), base_addr+S3C2410_ADCTSC);

    按照等待中断的模式设置TSC

    接下来的部分是注册输入设备

           /* Initialise input stuff */

           input_dev = input_allocate_device();

    //allocate memory for new input device,用来给输入设备分配空间,并做一些输入设备通用的初始的设置

           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);

           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);

    以上四句都是设置事件类型中的code,如何理解呢,先说明事件类型,常用的事件类型

    EV_KEY、EV_MOSSE, EV_ABS(用来接收像触摸屏这样的绝对坐标事件),而每种事件又会

    有不同类型的编码code,比方说ABS_X,ABS_Y,这些编码又会有相应的value

           dev->name = s3c2410ts_name;

           dev->id.bustype = BUS_RS232;

           dev->id.vendor = 0xDEAD;

           dev->id.product = 0xBEEF;

           dev->id.version = S3C2410TSVERSION;

    //以上是输入设备的名称和id,这些信息时输入设备的身份信息了,在用户空间如何看到呢,

    cat /proc/bus/input/devices,下面是我的截图

           /* Get irqs */

           if (request_irq(IRQ_ADC, stylus_action, IRQF_SAMPLE_RANDOM,

                  "s3c2410_action", dev)) {

                  printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_ADC !/n");

                  iounmap(base_addr);

                  return -EIO;

           }

           if (request_irq(IRQ_TC, stylus_updown, IRQF_SAMPLE_RANDOM,

                         "s3c2410_action", dev)) {

                  printk(KERN_ERR "s3c2410_ts.c: Could not allocate ts IRQ_TC !/n");

                  iounmap(base_addr);

                  return -EIO;

           }

           printk(KERN_INFO "%s successfully loaded/n", s3c2410ts_name);

           /* All went ok, so register to the input system */

           input_register_device(dev);

    //前面已经设置了设备的基本信息和所具备的能力,所有的都准备好了,现在就可以注册了

           return 0;

    }

    3.2    中断处理

    stylus_action和stylus_updown两个中断处理函数,当笔尖触摸时,会进入到stylus_updown,

    static irqreturn_t stylus_updown(int irq, void *dev_id)

    {

             unsigned long data0;

             unsigned long data1;

             int updown;

    //注意在触摸屏驱动模块中,这个ADC_LOCK的作用是保证任何时候都只有一个驱动程序使用ADC的中断线,因为在mini2440adc模块中也会使用到ADC,这样只有拥有了这个锁,才能进入到启动ADC,注意尽管LDD3中说过信号量因为休眠不适合使用在ISR中,但down_trylock是一个例外,它不会休眠。

             if (down_trylock(&ADC_LOCK) == 0) {

                       OwnADC = 1;

                       data0 = ioread32(base_addr+S3C2410_ADCDAT0);

                       data1 = ioread32(base_addr+S3C2410_ADCDAT1);

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

                       if (updown) {//means down

                                touch_timer_fire(0);//这是一个定时器函数,当然在这里是作为普通函数调用,用来启动ADC

                       } else {

                                OwnADC = 0;

                                up(&ADC_LOCK);//注意红色的部分是基本不会执行的,除非你触摸后以飞快的速度是否,还来不及启动ADC,当然这种飞快的速度一般是达不到的,笔者调试程序时发现这里是进入不了的

                        }

             }      

             return IRQ_HANDLED;

    }

    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 = (!(data0 & S3C2410_ADCDAT0_UPDOWN)) && (!(data1 & S3C2410_ADCDAT0_UPDOWN));

            if (updown) {//means down

             转换四次后进行事件汇报

                      if (count != 0) {

                                long tmp;

                                                                                                    

                                tmp = xp;

                                xp = yp;

                                yp = tmp;

          //这里进行转换是因为我们的屏幕使用时采用的是240*320,相当于把原来的屏幕的X,Y轴变换。

    个人理解,不只是否正确                                                                                          

                            xp >>= 2;

                            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);

    //这个表明我们上报了一次完整的触摸屏事件,用来间隔下一次的报告

                      }

                       xp = 0;

                      yp = 0;

                      count = 0;

                      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);

    如果还没有启动ADC或者ACD转换四次完毕后则启动ADC

             }      else {

    如果是up状态,则提出报告并让触摸屏处在等待触摸的阶段

                      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 irqreturn_t stylus_action(int irq, void *dev_id)

    {

             unsigned long data0;

             unsigned long data1;

             if (OwnADC) {

                       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)) {如果小如四次重新启动转换

                                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 {如果超过四次,则等待1ms后进行数据上报

                                mod_timer(&touch_timer, jiffies+1);

                                iowrite32(WAIT4INT(1), base_addr+S3C2410_ADCTSC);

                       }

             }

             return IRQ_HANDLED;

    }

    我们从整体上描述转换的过程:

    (1)         如果触摸屏感觉到触摸,则进入updown ISR,如果能获取ADC_LOCK则调用touch_timer_fire,启动ADC,

    (2)         ADC转换,如果小于四次继续转换,如果四次完毕后,启动1个时间滴答的定时器,停止ADC, 也就是说在这个时间滴答内,ADC是停止的,

    (3)         这样可以防止屏幕抖动。

    (4)         如果1个时间滴答到时候,触摸屏仍然处于触摸状态则上报转换数据,并重启ADC,重复(2)

    (5)         如果触摸笔释放了,则上报释放事件,并将触摸屏重新设置为等待中断状态。

    4 测试与校准

    关于应用程序的编写,请参照《Using the Input Subsystem, Part II》,讲解了input设备的API,

    触摸屏的校准时使触摸屏的坐标与LCD得坐标进行对应,这种对应需要映射,这个映射的过程即为校准,我们提供了一种线性算法的映射方法,具体的代码见附件。

  • 相关阅读:
    Maven关于web.xml中Servlet和Servlet映射的问题
    intellij idea的Maven项目运行报程序包找不到的错误
    修改Maven项目默认JDK版本
    刷题15. 3Sum
    刷题11. Container With Most Water
    刷题10. Regular Expression Matching
    刷题5. Longest Palindromic Substring
    刷题4. Median of Two Sorted Arrays
    刷题3. Longest Substring Without Repeating Characters
    刷题2. Add Two Numbers
  • 原文地址:https://www.cnblogs.com/0822vaj/p/3543654.html
Copyright © 2011-2022 走看看