LINUX设备驱动之触摸屏驱动

来源:互联网 发布:vb中的sum是什么意思 编辑:程序博客网 时间:2024/06/06 14:10

关键字:触摸屏
   总线:Input.c
   左边:Input_dev  右边:Evdv.c
其实触摸屏使用的是输入子系统,只要把输入子系统的框架弄熟悉,大致的程序应该可以出来;

在linux内核中添加一个input设备变得很简单了。我们再也不必须去动手写那些该死的接口函数了。可是你有没有想过,是谁让我们的工作变得这么简单了呢?

答案是linux内核中的input core。她总是那么痴情,默默地不求回报地为你做许许多多的事情,在你背后默默的支持你爱着你。是的,你所想到的大多数事情,我们的input core都已经为你做好。除了感动,我们还能说什么呢?

触摸屏使用过程:

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

测试过程:
1. make menuconfig 去掉原来的触摸屏驱动程序

-> Device Drivers    -> Input device support     -> Generic input layer      -> Touchscreens      <>   S3C2410/S3C2440 touchscreens   

测试:
1. ls /dev/event*
2. insmod s3c_ts.ko
3. ls /dev/event*
4. hexdump /dev/event0

                秒       微秒   type code    value    0000000 29a4 0000 8625 0008 0003 0000 0172 0000    0000010 29a4 0000 8631 0008 0003 0001 027c 0000    0000020 29a4 0000 8634 0008 0003 0018 0001 0000    0000030 29a4 0000 8638 0008 0001 014a 0001 0000    0000040 29a4 0000 863c 0008 0000 0000 0000 0000    0000050 29a4 0000 c85e 0008 0003 0000 0171 0000    0000060 29a4 0000 c874 0008 0003 0001 027d 0000    0000070 29a4 0000 c87b 0008 0000 0000 0000 0000    0000080 29a4 0000 ed37 0008 0003 0018 0000 0000    0000090 29a4 0000 ed48 0008 0001 014a 0000 0000    00000a0 29a4 0000 ed4a 0008 0000 0000 0000 0000

Input_dev所要做的事情有:
1. 分配
2. 设置
3. 注册
4. 硬件相关


1.S3c_ts.c例函中初始化函数:

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

函数1分析:

①.设置产生哪类事件用set_bit()函数,而产生哪类事件之后还要进行判断可以产生 这类事件 里面的哪些事件也同样用 set_bit()函数;

  set_bit(EV_KEY, s3c_ts_dev->evbit); //按键类事件
  set_bit(EV_ABS, s3c_ts_dev->evbit); //相对位移类事件
  set_bit(BTN_TOUCH, s3c_ts_dev->keybit);//能够产生按键类事情里面的触摸屏事件

原函数:

    static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat)

自己所写函数:

    input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);//x    input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);//y    input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);//压力方向

所有触摸屏都是如上框架,剩下都是硬件操作!


②. 硬件相关

硬件相关操作:详看触摸屏datasheet及2440原理图
触摸屏原理:具体百度!

TOUCH SCREEN INTERFACE MODE> - Normal Conversion Mode  > - Separate X/Y position conversion Mode> - Auto(Sequential)X/Y Position Conversion Mode> - Waiting for interrupt Mode  

初始化中的enter_ wait_ pen_ down_mode()即是Waiting for interrupt Mode 模式
中断中的enter_ measure_ xy_mode函数即是Auto(Sequential)X/Y Position Conversion Mode模式

测X坐标:

  1. XP接3.3v
  2. XM接地
  3. YP,YM不接
  4. 测YP电压

测Y坐标:

  1. YP接3.3
  2. YM接地
  3. XP,XM不接
  4. 测XP电压

Ⅰ、CLKCON:2440片上系统,集成很多模块,例如Camera,SPI,IIC等等,为了省电,内核上电时候不运行的模块暂时关掉,当需要用到的时候又打开(0=DISABLE,1=ENABLE)。可在2440datasheet搜索CLKCON;
Ⅱ AD Conversion Time

这里写图片描述
ADC最大工作频率是2.5MHZ,而PCLK的频率是500MHZ所以要设置某些分频系数把工作频率降下来
以上图片是例子,举出如果是2.5MHZ的话,一秒钟可以工作500次;

Ⅲ、 设置s3c_ ts_regs寄存器,具体如上函数


2:S3c_ts.c例函之中断函数

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

函数2分析:
该函数大体流程如:
1、触摸按下>进入此中断程序>进入测量X/Y模式>启动ADC
2、触摸松开>上报事件>input_sync表示事件处理完毕>进入按键按下模式


3:S3c_ts.c例函之定时器函数及中断函数

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

函数3分析:

Ⅰ:s3c_ts_regs->adcdat0 & (1<<15)   //adcdat0这个寄存器中的第15位

0 = Stylus down state
1 = Stylus up state

可以用此寄存器的第15位来分辨触摸笔是松开还是按下!
假如是1的话表示松开,就应该进入等待按下模式,0的话则相反
而且这个还是在优化的条件下判断的,是在ADC进行转换的固定时间段进行判断
前面函数②已经进行过一次判断,进入这个ADC转换的时候又再做一次判断优化

    cnt = 0;    enter_wait_pen_up_mode();    mod_timer(&ts_timer, jiffies + HZ/100);

在进行了软件过滤之后加入定时器函数mod_timer

  当一个定时器已经被插入到内核动态定时器链表中后,我们还可以修改该定时器的expires值。函数mod_ timer()实现这一点
修改注册入计时器列表的handler的起动时间
  内核通过函数mod_timer来实现已经激活的定时器超时时间:
  mod_timer 函数也可以操作那些已经初始化,但还没有被激活的定时器,如果定时器没有激活,
mod _timer 会激活它。如果调用时定时器未被激活,该函数返回0,否则返回1。一旦从mod _timer 函数返回,定时器都将被激活而且设置了新的定时值。

如果需要在定时器超时前停止定时器,可以使用del_timer函数: del_ timer(&my_timer)

  被激活或未被激活的定时器都可以使用该函数,如果定时器还未被激活,该函数返回0;否则返回1。当删除定时器,必须小心一个潜在的竞争条件。当del_ timer返回后,可以保证的只是:定时器不会被再激活,但是多处理器上定时器中断可能已经在其他处理上运行了,所以需要等待可能在其他处理器上运行的定时器处理程序都退出,这时需要使用del_timer_sync函数执行删除工作:

和del_ timer函数不同,del_ timer_ sync数不能在中断上下文中使用。

Ⅲ、
input_report_abs(),input_report_key(),上报事件,报告发生的一些事件以及对应的坐标

Ⅳ、

input_sync()表示事件处理完毕,上报完毕,每个input_report()后面必须假如这个sync。

/**     * mod_timer - modify a timer's timeout     * @timer: the timer to be modified     * @expires: new timeout in jiffies     *     * mod_timer() is a more efficient way to update the expire field of an     * active timer (if the timer is inactive it will be activated)     *     * mod_timer(timer, expires) is equivalent to:     *     *     del_timer(timer); timer->expires = expires; add_timer(timer);     *     * Note that if there are multiple unserialized concurrent users of the     * same timer, then mod_timer() is the only safe way to modify the timeout,     * since add_timer() cannot modify an already running timer.     *     * The function returns whether it has modified a pending timer or not.     * (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an     * active timer returns 1.)     */    int mod_timer(struct timer_list *timer, unsigned long expires)    {        BUG_ON(!timer->function);        timer_stats_timer_set_start_info(timer);        /*         * This is a common optimization triggered by the         * networking code - if the timer is re-modified         * to be the same thing then just return:         */        if (timer->expires == expires && timer_pending(timer))            return 1;        return __mod_timer(timer, expires);    }

小记:
1、Struct 设置一个结构体的时候,假如寄存器的偏差值是0x4的话,应该用unsigned long,刚好是四个字节;
2、操作寄存器前一定要先ioremap;