美文网首页
7. Linux - 触摸屏(电阻屏)驱动程序实现

7. Linux - 触摸屏(电阻屏)驱动程序实现

作者: JalynFang | 来源:发表于2019-05-12 07:28 被阅读0次

    一、触摸屏概述

          触摸屏作为一种输入设备,是目前最简单、方便、自然的一种人机交互方式。按照触摸屏的工作原理和传输信息的介质,可以将触摸屏分为四种:电阻式、电容感应式、红外线式和表面声波式。每一种触摸屏都有其各自的优缺点,要了解哪种触摸屏适用于哪种场合,关键就在要了解每一类触摸屏的工作原理和特点。本节我们主要介绍的是4线电阻式触摸屏。

    1.1 电阻式触摸屏

          电阻式触摸屏利用压力感应进行控制。电阻触摸屏的主要部分是一块与显示器表面紧密结合的电阻薄膜屏,这是一种多层的复合薄膜,以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也涂有一层涂层、在他们之间有许多细小的的透明隔离点把两层导电层隔开绝缘。所有的电阻式触摸屏都采用分压器原理来产生代表X坐标和Y坐标的电压。分压器是通过将两个电阻进行串联来实现的。电阻R1连接正参考电压VREF,电阻R2接地。两个电阻连接点处的电压测量值与R2的阻值成正比。当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在X和Y两个方向上产生信号,然后送触摸屏控制器。控制器侦测到这一接触并计算出(X,Y)的位置,再根据模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本原理。实际上就是欧姆定律的巧妙运用!

    1.2 电阻式触摸屏信号测量

    4线触摸屏包含了两个阻性层,如下图所示:


    当没有触摸按下时,X层和Y层是分离的,此时就测不到电压。

    X坐标方向值测量:
          将XP接到3.3V , XM接0V, YP和YM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时触摸屏控制器 (ADC)就可测量到YP输出的当前X坐标值1.66V,如下图:

    Y坐标方向值测量:
          将YP接3.3V , YM接0V, XP和XM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时触摸屏控制器 (ADC)就可测量到XP输出的当前Y坐标值1.66V,如下图:

    二、触摸屏驱动模型

    Linux内核中触摸屏驱动实现基于输入子系统架构,具体请移步 Linux - 输入子系统框架详解 ;


    对于触摸屏驱动开发者,核心层和事件处理层由Linux内核提供,主要实现xxx_ts.c,
    1)分配一个input_dev结构体
    2)设置input_dev的成员
    3)注册input_dev 驱动设备到内核中
    4)设置触摸屏相关的硬件

    触摸屏使用过程简单处理简述如下(流程):

    1. 按下,产生中断;
    2. 在中断服务程序里,启动ADC,转换X,Y坐标;
    3. ADC结束,产生ADC中断;
    4. 在ADC中断处理函数里上报(input_event),启动定时器;
    5. 定时器时间到,执行2再次启动ADC,转换X,Y坐标;(处理长按、滑动)
    6. 松开

    二、触摸屏驱动相关的重要数据结构和函数

          在输入子系统架构中,会将设备抽象出一个input_dev结构体;它是驱动的主体。每个struct input_dev代表一个输入设备。

    2.1 struct input_dev结构体
    struct input_dev {     
           void *private;
           const char *name;  //设备名字
           const char *phys;  //文件路径,比如 input/buttons
           const char *uniq;  
           struct input_id id;
    
           unsigned long evbit[NBITS(EV_MAX)];  //表示支持哪类事件,常用有以下几种事件(可以多选)
           //EV_SYN      同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
           //EV_KEY       键盘事件
           //EV_REL       (relative)相对坐标事件,比如鼠标
           //EV_ABS       (absolute)绝对坐标事件,比如摇杆、触摸屏感应
           //EV_MSC      其他事件,功能
           //EV_LED       LED灯事件
           //EV_SND      (sound)声音事件
           //EV_REP       重复键盘按键事件
           //(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)  
    
           //EV_FF         受力事件
           //EV_PWR      电源事件
           //EV_FF_STATUS  受力状态事件
    
           unsigned long keybit[NBITS(KEY_MAX)];   //存放支持的键盘按键值
           //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)、BTN_TOUCH(触摸屏的按键)
    
           unsigned long relbit[NBITS(REL_MAX)];    //存放支持的相对坐标值
           unsigned long absbit[NBITS(ABS_MAX)];   //存放支持的绝对坐标值,存放下面4个absxxx[]
           unsigned long mscbit[NBITS(MSC_MAX)];   //存放支持的其它事件,也就是功能
           unsigned long ledbit[NBITS(LED_MAX)];    //存放支持的各种状态LED
           unsigned long sndbit[NBITS(SND_MAX)];    //存放支持的各种声音
           unsigned long ffbit[NBITS(FF_MAX)];       //存放支持的受力设备
           unsigned long swbit[NBITS(SW_MAX)];     //存放支持的开关功能
             ... ...
    
     
    /*以下4个数组都会保存在上面成员absbit[]里,数组号为:ABS_xx ,位于include/linux/input.h */
    /*比如数组0,标志就是ABS_X,以下4个的absXXX[0]就是表示绝对位移X方向的最大值、最小值... */
    /*对于触摸屏常用的标志有:
    ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向,比如绘图,越用力线就越粗)* / 
           int absmax[ABS_MAX + 1];      //绝对坐标的最大值
           int absmin[ABS_MAX + 1];      //绝对坐标的最小值
           int absfuzz[ABS_MAX + 1];     //绝对坐标的干扰值,默认为0,
           int absflat[ABS_MAX + 1];     //绝对坐标的平焊位置,默认为0
    ... ...
    
    2.2 分配/释放input_dev结构体

    函数原型:struct input_dev *input_allocate_device(void)
    参数说明 :无
    返回值 : 分配到的input_dev结构体

    函数原型:input_free_device(struct input_dev dev)
    参数说明 :
    dev 待释放的input_dev结构体

    2.3 注册/注销input_dev设备

    函数原型:input_register_device(struct input_dev *dev)
    参数说明 :*dev 注册的input_dev结构体
    说       明 :注册一个input_dev,若有对应的驱动事件,则在/sys/class/input下创建这个类设备。

    函数原型:input_unregister_device(struct input_dev *dev)
    参数说明 :*dev 注销的input_dev结构体
    说       明 :卸载/sys/class/input目录下的input_dev这个类设备;

    2.4 事件支持(初始化)

    函数原型:int set_bit(int nr,long * addr)
    参数说明 :设置某个结构体成员 * addr里面的某位等于nr,支持这个功能
    说       明 :告诉input输入子系统支持哪些事件,哪些按键;例如:
    set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev类型)
    struct input_dev中有两个成员为:
    evbit:
    事件类型(EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)
    keybit:
    按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)

    2.5 设置绝对位移的支持参数

    函数原型:input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)
    参数说明 :*dev : 需要设置的input_dev结构体
                        axis : 需要设置的数组号,常用的有: ABS_X(X坐标方向), ABS_Y(Y坐标方向), ABS_PRESSURE(压力方向)
                        min : axis方向的最小值
                        max : axis方向的最大值
                        fuzz : axis方向的干扰值
                        flat : axis方向的平焊位置

    2.5 上报EV_ABS事件

    函数原型:input_report_abs(struct input_dev *dev, unsigned int code, int value);
    参数说明 :*dev : 要上报哪个input_dev驱动设备的事件
                        code : EV_ABS事件里支持的哪个方向,比如X坐标方向则填入: ABS_X
                        value : 对应的方向的值,比如X坐标126
    说       明 :该函数实际就是调用的input_event(dev, EV_ABS, code, value);

    2.6 上报EV_KEY事件

    函数原型:input_report_key(struct input_dev *dev, unsigned int code, int value);

    2.7 同步事件通知,通知系统有事件上报

    函数原型:input_sync(struct input_dev *dev) ;
    参数说明 :*dev : 要上报哪个input_dev驱动设备的事件
    说       明 :同步用于告诉input core子系统报告结束

    三、触摸屏驱动实现

    3.1 在init入口函数中:

    1)分配一个input_dev结构体
    2)设置input_dev的成员
      -> 2.1)设置input_dev->evbit支持按键事件,绝对位移事件
          (触摸屏:通过按键BTN_TOUCH获取按下/松开,通过绝对位移获取坐标)
      -> 2.2)设置input_dev-> keybit支持BTN_TOUCH触摸屏笔尖按下
      -> 2.3)设置input_dev-> absbit 支持ABS_X、ABS_Y、 ABS_PRESSURE
           input_set_abs_params(ts.dev, ABS_X, 0, 0x3FF, 0, 0);
          input_set_abs_params(ts.dev, ABS_Y, 0, 0x3FF, 0, 0); // 0x3FF:最大值为10位ADC,
          input_set_abs_params(ts.dev, ABS_PRESSURE, 0, 1, 0, 0); //压力最多就是1
    3)注册input_dev 驱动设备到内核中
    4)设置触摸屏相关的硬件
      -> 4.1)开启ADC时钟,使用clk_get ()和clk_enable()函数
      -> 4.2) ioremap获取寄存器地址,设置寄存器ADCCON =(1<<14)|(49<<6),分频
      ->4.3)设置寄存器ADCDLY=0xffff,ADC启动延时时间设为最大值,使触摸按压更加稳定
      ->4.4)开启IRQ_TC笔尖中断、开启IRQ_ADC中断获取XY坐标
      -> 4.5)初始化定时器,增加触摸滑动功能
      ->4.6)最后设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断

    3.2 在出口函数中:

    1)注销内核里的input_dev、
    2)释放中断、删除定时器、iounmap注销地址、
    3)释放input_dev、

    3.3 在IRQ_TC中断函数中:

    1)若判断笔尖为松开,设置寄存器ADCTSC =0XD3(按下中断)
    2)若判断笔尖按下,设置为XY自动转换模式,启动一次ADC转换,ADC转换成功,会进入ADC中断

    3.4 在IRQ_ADC中断函数中:

    1)获取ADCDAT0的位[9:0],来算出XY方向坐标值
    2)测量n次值保存在数组中,然后再次设置为XY自动转换模式,启动ADC
    (PS:要启动ADC转换之前必须设置一次XY为自动转换模式,不然获取的数据会不准)
    3)采集完毕,使用快速排序将n次值排序后,以最小值为基准,如有误差非常大的数,则舍弃,如果没有则打印数组的中间值,实现中值滤波。
    (PS: 使用快速排序,比冒泡更快,详解 )
    4)打印数据后,必须设置寄存器ADCTSC =0X1D3(松开中断IRQ_TC)
    (PS:在ADC采样模式下是判断不到ADCDAT0的bit15位的,因为ADCDAT0已被自动设置为X坐标的采样值)
    5)设置定时器10ms超时时间

    3.5 在定时器超时函数中:

    1)判断ADCDAT0的bit15位,若还在按下再次启动ADC转换(实现触摸滑动功能)
    2)若松开,设置寄存器ADCTSC =0XD3(按下中断)

    代码如下:

    #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 <asm/plat-s3c24xx/ts.h> 
    #include <asm/arch/regs-adc.h>
    #include <asm/arch/regs-gpio.h>
     
     
    
    static struct input_dev  *ts_dev;
    static struct clk    *ADC_CLK;                    //adc时钟
    static struct timer_list    ts_timer;             //定时器
    
    struct adc_regs{
           unsigned long   adccon;
           unsigned long   adctsc;
           unsigned long   adcdly;
           unsigned long  adcdat0;
           unsigned long  adcdat1;
           unsigned long  adcupdn;
    };
    
    static volatile struct adc_regs  *adc_regs;
    
    /*启动TC 函数*/
    static void set_pen_down(void)
    {
           /* 设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断*/
              adc_regs->adctsc = 0xd3;
    }
    
    static void set_pen_up(void)
    {
         /* 设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断*/
             adc_regs->adctsc = 0x1d3;
    }
    
    
    
    /*启动ADC  转换函数*/
    static void adc_start(void)
    {
       adc_regs->adctsc= (1<<3)| (1<<2);     //启动XY自动转换
       adc_regs->adccon|=(1<<0);                         //启动1次ADC转换
    }
    
     
    
    /*快速排序,比冒泡更快*/
    /*快速排序详解:http://www.cnblogs.com/lifexy/p/7597276.html*/
    static void find_frst(int *s,int lift,int right)
    {
        int i=lift,j=right,temp;  //(1)初始化i、j   
        if(lift>=right)
         return ;
        temp=s[i];                //(2)以第一个数组为比较值,保存到temp中
        while(i<j)
        {   
          while(j>i&&s[j]>=temp)  //(3)j--,找小值
          j--;
          s[i]= s[j];             //保存小值,到s[i]上  
          while(i<j&&s[i]<=temp)  //(4)i++,找大值
          i++;
          s[j--]=s[i];            //保存大值 到s[j]上
        }
    
        s[i]=temp;             //(5)将比较值放在s[i]上       
    
      /*(6)拆分成两个数组 s[0,i-1]、s[i+1,n-1]又开始排序 */
      find_frst(s,lift,i-1);         //左
      find_frst(s,i+1,right);        //右    
    }
    
    
    /*查找X Y坐标偏移值是否太大*/
    /*return:  0误差大,   1误差小            */
    static int  find_xy_offset(int x[], int      y[],int n)
    {
           int i;
           for(i=n;i>=1;i--)
           {
           if(x[i]-x[i-1]>10)    //判断是否大于误差10, 
           return 0;
                 
           if(y[i]-y[i-1]>10)  //判断是否大于误差10,
           return 0;          
           }
          
           return 1;
    }
    
     
           /*定时器函数,实现触摸滑动功能    */
    void pen_updown_timer(unsigned long  cnt)
    {
       if((adc_regs->adcdat0>>15)&0x01)       //此时笔尖已经抬起
       {
           set_pen_down();                   //设置TC中断
       }
       else
       {
           adc_start();                          //启动一次ADC转换
       }
    
    }
       
           /*触摸中断IRQ_TC  */
    static irqreturn_t tc_handler(int irq, void *dev_id)
    {
    
           if((adc_regs->adcdat0>>15)&0x01)     //此时笔尖已经抬起
           {      
           set_pen_down();
           }
    
           else
           {
           adc_start();     //启动一次ADC转换
           }
           return IRQ_HANDLED;
    }
    
    
           /*ADC中断IRQ_ADC:测XY坐标  */
    static irqreturn_t adc_handler(int irq, void *dev_id)
    {
           static int                      adc_x[5],adc_y[5];          //保存XY坐标
           static unsigned char     xy_cnt=0;                            //计数   
    
           adc_y[xy_cnt]  =adc_regs->adcdat1&0x3ff;               //10位ADC
           adc_x[xy_cnt]  =adc_regs->adcdat0&0x3ff;               //10位ADC     
    
           if (adc_regs->adcdat0 & (1<<15))
                  {
                         /* 已经松开 */
                         xy_cnt = 0;
                         set_pen_down();
                  }
           else{
            xy_cnt++;
            if(xy_cnt>=5)
                  {
                  xy_cnt=0;
                    find_frst(adc_x,0,4);                      // 快速排序X
                    find_frst(adc_y,0,4);                      // 快速排序y         
                         if(find_xy_offset(adc_x,adc_y,4))                         
                         {
                                printk("X:  %04d,y:  %04d \n",adc_x[2],adc_y[2]);      //中值滤波                  
                         }
    
                         set_pen_up();
                         mod_timer(&ts_timer    ,jiffies+HZ/100);            //启动定时10ms
                  }
             else     //在测一次
                {
                  adc_start();    //启动 ADC
                }
              }     
           return IRQ_HANDLED;
    }
    
     
    
     
    
    /*入口函数*/
    static int myts_init(void)
    {
           /*1. 申请input_dev   */
           ts_dev=input_allocate_device();
           /*2. 设置input_dev*/
           set_bit(EV_ABS, ts_dev->evbit);
           set_bit(EV_KEY, ts_dev->evbit);
           set_bit(BTN_TOUCH, ts_dev->keybit);
           input_set_abs_params(ts_dev, ABS_X , 0 , 0x3ff , 0 , 0);       //adc是个10位的,所以为0X3FF
           input_set_abs_params(ts_dev, ABS_Y , 0 , 0x3ff , 0 , 0);       //adc是个10位的,所以为0X3FF
           input_set_abs_params(ts_dev, ABS_PRESSURE, 0 , 1 , 0 , 0);       //adc是个10位的,所以为0X3FF   
    
           /*3.注册input_dev 驱动设备到内核中*/
           input_register_device(ts_dev);   
    
           /*4.设置触摸屏相关的硬件*/
           /*4.1 开启ADC时钟 */
           ADC_CLK        =clk_get(0,"adc");
           clk_enable(ADC_CLK);
          
           /*4.2 设置寄存器ADCCON分频,*/
           adc_regs=ioremap(0x58000000, sizeof(struct adc_regs));
           adc_regs->adccon=(1<<14)|(49<<6);        //50Mhz/(49+1)=1Mhz
      
    
           /*4.3 设置中断IRQ_TC   IRQ_ADC  */
           request_irq(IRQ_TC       , tc_handler, IRQF_SAMPLE_RANDOM, "pen_updown", 0);
           request_irq(IRQ_ADC     , adc_handler, IRQF_SAMPLE_RANDOM, "adc"    , 0);
    
    
           /*4.4设置寄存器ADCDLY=0xffff */
           adc_regs->adcdly  =0xffff;   
    
           /*4.5 初始化定时器*/
           init_timer(&ts_timer);
           ts_timer.function    =pen_updown_timer;
           add_timer(&ts_timer);
    
           /*4.6设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断*/
           set_pen_down();
    
           return 0;
    }
    
    /*出口函数*/
    static void myts_exit(void)
    {
           /*1.注销内核里的input_dev、*/
           input_unregister_device(ts_dev);
           /*2.释放中断、删除定时器、iounmap注销地址、*/
           free_irq(IRQ_TC, NULL);
           free_irq(IRQ_ADC, NULL);    
    
           del_timer(&ts_timer);
           iounmap(adc_regs);
    
           /*3.释放input_dev、*/
           input_free_device(ts_dev);
    }
    
    module_init(myts_init);
    module_exit(myts_exit); 
    MODULE_LICENSE("GPL");
    

    相关文章

      网友评论

          本文标题:7. Linux - 触摸屏(电阻屏)驱动程序实现

          本文链接:https://www.haomeiwen.com/subject/sxofoqtx.html