LINUX設備驅動之觸摸屏驅動

關鍵字:觸摸屏
   總線: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;

發佈了30 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章