美文网首页
Linux触摸屏驱动

Linux触摸屏驱动

作者: ZebraWei | 来源:发表于2021-03-08 11:42 被阅读0次

    在事件处理层的函数都是通过input_register_handler()函数注册到input_hander_list链表中,搜索input_register_handler注册函数,就可以看到事件处理层里的函数:


    右边的驱动事件处理,内核是已经写好了的,所以触摸屏只需要写具体的驱动设备,然后内核会与触摸屏驱动tsdev.c自动连接。
    结构体成员如下
    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
    ... ...
    

    需要用到的函数

    struct input_dev *input_allocate_device(void);  //向内存中分配input_dev结构体
    
    input_free_device(struct input_dev *dev);   //释放内存中的input_dev结构体
    
    input_register_device(struct input_dev *dev);   //注册一个input_dev,若有对应的驱动事件,
    则在/sys/class/input下创建这个类设备
    
    input_unregister_device(struct input_dev *dev);   //卸载/sys/class/input目录下的
    input_dev这个类设备
    
    
    set_bit(nr,p);                  //设置某个结构体成员p里面的某位等于nr,支持这个功能
    /* 比如:
    set_bit(EV_KEY,buttons_dev->evbit);   //设置input_dev结构体buttons_dev->evbit支持EV_KEY
    set_bit(KEY_S,buttons_dev->keybit);  //设置input_dev结构体buttons_dev->keybit支持按键”S”
    
    */
    
    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方向的平焊位置
    
    
    input_report_abs(struct input_dev *dev, unsigned int code, int value);   
    //上报EV_ABS事件
    //该函数实际就是调用的input_event(dev, EV_ABS, code, value);
    //*dev :要上报哪个input_dev驱动设备的事件
    // code: EV_ABS事件里支持的哪个方向,比如X坐标方向则填入: ABS_X
    //value:对应的方向的值,比如X坐标126
    
    input_report_key(struct input_dev *dev, unsigned int code, int value);  
    //上报EV_KEY事件 
    
    input_sync(struct input_dev *dev); //同步事件通知,通知系统有事件上报
    
    struct  clk *clk_get(struct device *dev, const char *id);    
    //获得*id模块的时钟,返回一个clk结构体
    //*dev:填0即可,     *id:模块名字, 比如"adc","i2c"等,名字定义在clock.c中
    
    clk_enable(struct clk *clk);   
    //开启clk_get()到的模块时钟,就是使能CLKCON寄存器的某个模块的位
    

    电阻式触摸屏介绍


    引脚说明:
    YM: (Y Minus)触摸屏的Y坐标的负线,也可以用Y -表示
    YP : (Y Power)触摸屏的Y坐标的正线, 也可以用Y+表示
    XM: (Y Minus)触摸屏的X坐标的负线, 也可以用X-表示
    XP : (Y Power)触摸屏的X坐标的正线, 也可以用X+表示
    触摸屏包含了两个阻性层,如下图所示:

    当没有触摸按下时,x层和Y层是分离的,此时就测不到电压。
    测X坐标方向时
    如下图, 把XP接3.3V , XM接0V, YP和YM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时YP就会输出当前X坐标值的1.66V给CPU。

    测Y坐标方向时:
    如下图, 把YP接3.3V , YM接0V, XP和XM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时XP就会输出当前X坐标值的1.66V给CPU 。

    ADC模型数据转换器
    ,2440的ADC分辨率为10位(0~0X3FFF)
    如下图,若工作在普通ADC模式,则通过寄存器ADCCON->SEL_MUX来选择转换哪个引脚的模拟信号。当设置为ADC等待中断模式时,测到有屏幕笔尖触摸,就会产生INT_TC中断,其中ADC的工作频率最大为2.5MHZ,需要设置寄存器ADCCON->PRSCVL更改分频系数。
    获取笔尖触摸屏按下/松开使用的是ADC等待中断模式:
    当笔尖落下时触摸屏控制器产生中断(INT_TC)信号。需要设置寄存器ADCTSC=0xd3/0x1d3
    设置寄存器ADCTSC=0x0d3/0x1d3 (X 1101 0011)时(如下图):
    开启 YM开关,使能XP上拉, 开启等待中断模式。当有笔尖按下时,X层和Y层闭合,然后会拉低XP和XM电平,输出低电平,设置为0x0d3是检测触摸低电平, 设置为0x1d3是检测触摸上拉电平。
    获取XY坐标时使用的是自动 X/Y 方向转换模式
    当ADC转换成功, X 坐标值到 ADCDAT0 和 Y 坐标值到ADCDAT1 后,就会产生INT_ADC中断
    自动获取XY坐标时(如下图):
    设置寄存器ADCTSC=0X0C (关闭XP上拉、启动自动XY方向转换)
    设置寄存器ADCCON的位[0]=1(开启一次ADC转换,当ADC转换成功该位清0)
    代码编写
    在init入口函数中:
    1.分配一个input_dev结构体
    2.设置input_dev的成员
    • 设置input_dev->evbit支持按键事件,绝对位移事件(触摸屏:通过按键BTN_TOUCH获取按下/松开,通过绝对位移获取坐标)
    • 设置input_dev-> keybit支持BTN_TOUCH触摸屏笔尖按下。
    • 设置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.设置触摸屏相关的硬件

    • 开启ADC时钟,使用clk_get ()和clk_enable()函数。
    • ioremap获取寄存器地址,设置寄存器ADCCON =(1<<14)|(49<<6),分频
    • 设置寄存器ADCDLY=0xffff,ADC启动延时时间设为最大值,使触摸按压更加稳定
    • 开启IRQ_TC笔尖中断、开启IRQ_ADC中断获取XY坐标
    • 初始化定时器,增加触摸滑动功能
    • 最后设置寄存器ADCTSC=0x0d3,开启IRQ_TC中断

    5.在出口函数中:

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

    6.在IRQ_TC中断函数中:

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

    7.在IRQ_ADC中断函数中:

    • 获取ADCDAT0的位[9:0],来算出XY方向坐标值
    • 测量n次值保存在数组中,然后再次设置为XY自动转换模式,启动ADC
    • 采集完毕,使用快速排序将n次值排序后,以最小值为基准,如有误差非常大的数,则舍弃,如果没有则打印数组的中间值,实现中值滤波。
    • 打印数据后,必须设置寄存器ADCTSC =0X1D3(松开中断IRQ_TC)
    • 设置定时器10ms超时时间
    1. 在定时器超时函数中:
    • 判断ADCDAT0的bit15位,若还在按下再次启动ADC转换(实现触摸滑动功能)
    • 若松开,设置寄存器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>
    
    struct s3c_ts_regs {
    unsigned long adccon;
    unsigned long adctsc;
    unsigned long adcdly;
    unsigned long adcdat0;
    unsigned long adcdat1;
    unsigned long adcupdn;
    };
    
    static struct input_dev *s3c_ts_dev;
    static volatile struct s3c_ts_regs *s3c_ts_regs;
    
    static struct timer_list ts_timer;
    
    static void enter_wait_pen_down_mode(void)
    {
       s3c_ts_regs->adctsc = 0xd3;
     }
    
    static void enter_wait_pen_up_mode(void)
    {
       s3c_ts_regs->adctsc = 0x1d3;
    }
    
    static void enter_measure_xy_mode(void)
    {
       s3c_ts_regs->adctsc = (1<<3)|(1<<2);
    }
    
    static void start_adc(void)
    {
       s3c_ts_regs->adccon |= (1<<0);
    }
    
    static int s3c_filter_ts(int x[], int y[])
    {
    #define ERR_LIMIT 10
    
    int avr_x, avr_y;
    int det_x, det_y;
    
    avr_x = (x[0] + x[1])/2;
    avr_y = (y[0] + y[1])/2;
    
    det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
    det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);
    
    if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
        return 0;
    
    avr_x = (x[1] + x[2])/2;
    avr_y = (y[1] + y[2])/2;
    
    det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
    det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);
    
    if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
        return 0;
    
    return 1;
    }
    
    static void s3c_ts_timer_function(unsigned long data)
    {
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开 */
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
        input_sync(s3c_ts_dev);
        enter_wait_pen_down_mode();
    }
    else
    {
        /* 测量X/Y坐标 */
        enter_measure_xy_mode();
        start_adc();
    }
    }
    
    
    static irqreturn_t pen_down_up_irq(int irq, void *dev_id)
    {
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        //printk("pen up\n");
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
        input_sync(s3c_ts_dev);
        enter_wait_pen_down_mode();
    }
    else
    {
        //printk("pen down\n");
        //enter_wait_pen_up_mode();
        enter_measure_xy_mode();
        start_adc();
    }
    return IRQ_HANDLED;
    }
    
    static irqreturn_t adc_irq(int irq, void *dev_id)
    {
    static int cnt = 0;
    static int x[4], y[4];
    int adcdat0, adcdat1;
    
    
    /* 优化措施2: 如果ADC完成时, 发现触摸笔已经松开, 则丢弃此次结果 */
    adcdat0 = s3c_ts_regs->adcdat0;
    adcdat1 = s3c_ts_regs->adcdat1;
    
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 已经松开 */
        cnt = 0;
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
        input_sync(s3c_ts_dev);
        enter_wait_pen_down_mode();
    }
    else
    {
        // printk("adc_irq cnt = %d, x = %d, y = %d\n", ++cnt, adcdat0 & 0x3ff, adcdat1 & 0x3ff);
        /* 优化措施3: 多次测量求平均值 */
        x[cnt] = adcdat0 & 0x3ff;
        y[cnt] = adcdat1 & 0x3ff;
        ++cnt;
        if (cnt == 4)
        {
            /* 优化措施4: 软件过滤 */
            if (s3c_filter_ts(x, y))
            {           
                //printk("x = %d, y = %d\n", (x[0]+x[1]+x[2]+x[3])/4, (y[0]+y[1]+y[2]+y[3])/4);
                input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
                input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
                input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
                input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
                input_sync(s3c_ts_dev);
            }
            cnt = 0;
            enter_wait_pen_up_mode();
    
            /* 启动定时器处理长按/滑动的情况 */
            mod_timer(&ts_timer, jiffies + HZ/100);
        }
        else
        {
            enter_measure_xy_mode();
            start_adc();
        }       
    }
    
    return IRQ_HANDLED;
    }
    
    static int s3c_ts_init(void)
    {
    struct clk* clk;
    
    /* 1. 分配一个input_dev结构体 */
    s3c_ts_dev = input_allocate_device();
    
    /* 2. 设置 */
    /* 2.1 能产生哪类事件 */
    set_bit(EV_KEY, s3c_ts_dev->evbit);
    set_bit(EV_ABS, s3c_ts_dev->evbit);
    
    /* 2.2 能产生这类事件里的哪些事件 */
    set_bit(BTN_TOUCH, s3c_ts_dev->keybit);
    
    input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);
    
    
    /* 3. 注册 */
    input_register_device(s3c_ts_dev);
    
    /* 4. 硬件相关的操作 */
    /* 4.1 使能时钟(CLKCON[15]) */
    clk = clk_get(NULL, "adc");
    clk_enable(clk);
    
    /* 4.2 设置S3C2440的ADC/TS寄存器 */
    s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
    
    /* bit[14]  : 1-A/D converter prescaler enable
     * bit[13:6]: A/D converter prescaler value,
     *            49, ADCCLK=PCLK/(49+1)=50MHz/(49+1)=1MHz
     * bit[0]: A/D conversion starts by enable. 先设为0
     */
    s3c_ts_regs->adccon = (1<<14)|(49<<6);
    
    request_irq(IRQ_TC, pen_down_up_irq, IRQF_SAMPLE_RANDOM, "ts_pen", NULL);
    request_irq(IRQ_ADC, adc_irq, IRQF_SAMPLE_RANDOM, "adc", NULL);
    
    /* 优化措施1: 
     * 设置ADCDLY为最大值, 这使得电压稳定后再发出IRQ_TC中断
     */
    s3c_ts_regs->adcdly = 0xffff;
    
    /* 优化措施5: 使用定时器处理长按,滑动的情况
     * 
     */
    init_timer(&ts_timer);
    ts_timer.function = s3c_ts_timer_function;
    add_timer(&ts_timer);
    
    enter_wait_pen_down_mode();
    
    return 0;
    }
    
    static void s3c_ts_exit(void)
    {
    free_irq(IRQ_TC, NULL);
    free_irq(IRQ_ADC, NULL);
    iounmap(s3c_ts_regs);
    input_unregister_device(s3c_ts_dev);
    input_free_device(s3c_ts_dev);
    del_timer(&ts_timer);
    }
    
    module_init(s3c_ts_init);
    module_exit(s3c_ts_exit);
    
    
    MODULE_LICENSE("GPL");
    

    相关文章

      网友评论

          本文标题:Linux触摸屏驱动

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