18.Llinux-觸摸屏驅動(詳解)【轉】

轉自:http://www.javashuo.com/article/p-rkfbfzay-dw.htmlhtml

本節的觸摸屏驅動也是使用以前的輸入子系統linux


 

 

1.先來回憶以前第12節分析的輸入子系統數組

其中輸入子系統層次以下圖所示,函數

 

其中事件處理層的函數都是經過input_register_handler()函數註冊到input_handler_list鏈表中post

搜索input_register_handler註冊函數,就能夠看到都是事件處理層裏的函數:學習

因此最終以下圖所示:測試

 

 

 

右邊的驅動事件處理,內核是已經寫好了的,因此咱們的觸摸屏只須要寫具體的驅動設備,而後內核會與觸摸屏驅動tsdev.c自動鏈接    url

2.本節須要用到的結構體成員以下:spa

複製代碼
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
... ...
複製代碼

 

3.本節須要用到的函數:3d

複製代碼
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寄存器的某個模塊的位
複製代碼

4.電阻式觸摸屏介紹:

以下圖所示,2440開發板使用的是4線觸摸屏,該4線鏈接在2440的AIN4~AIN7引腳上,該引腳專門是用來接收模擬輸入信號.

 

引腳說明:

YM: (Y Minus)觸摸屏的Y座標的負線,也能夠用Y -表示

YP : (Y Power)觸摸屏的Y座標的正線, 也能夠用Y+表示

XM: (Y Minus)觸摸屏的Y座標的負線, 也能夠用X-表示

XP : (Y Power)觸摸屏的Y座標的正線, 也能夠用X+表示

4.1  4線觸摸屏包含了兩個阻性層,以下圖所示:

 

 

當沒有觸摸按下時,X層和Y層是分離的,此時就測不到電壓

4.2 測X座標方向時:

以下圖,  把XP接3.3V , XM接0V, YP和YM懸空,咱們以按壓X座標的中間位置, X層和Y層便閉合了,此時YP就會輸出當前X座標值的1.66V給CPU 

 

4.3 測Y座標方向時:

以下圖, 把YP接3.3V , YM接0V, XP和XM懸空,咱們以按壓X座標的中間位置, X層和Y層便閉合了,此時XP就會輸出當前X座標值的1.66V給CPU 

 

 

5.接下來開始看2440手冊

以下圖,2440的ADC分辨率爲10位(0~0X3FFF)

 

以下圖,若工做在普通ADC模式,則經過寄存器ADCCON->SEL_MUX來選擇轉換哪一個引腳的模擬信號

當設置爲ADC等待中斷模式時,測到有屏幕筆尖觸摸,就會產生INT_TC中斷

其中ADC的工做頻率最大爲2.5MHZ,須要設置寄存器ADCCON->PRSCVL更改分頻係數

 

5.1 獲取筆尖觸摸按下/鬆開使用的是ADC等待中斷模式:
當筆尖落下時觸摸屏控制器產生中斷(INT_TC)信號。須要設置寄存器ADCTSC=0xd3/0x1d3 

設置寄存器ADCTSC=0x0d3/0x1d3 (X 1101 0011)時(以下圖):

開啓 YM開關,使能XP上拉, 開啓等待中斷模式

當有筆尖按下時,X層和Y層閉合,而後會拉低XP和XM電平,輸出低電平

設置爲0x0d3是檢測觸摸低電平, 設置爲0x1d3是檢測觸摸上拉電平

(PS:  ADCDAT0的bit15位用來標誌筆尖是按下仍是鬆開)

 

5.2 獲取XY座標時使用的是自動 X/Y 方向轉換模式

當ADC轉換成功,  X 座標值到 ADCDAT0 和 Y 座標值到ADCDAT1 後,就會產生INT_ADC中斷

自動獲取XY座標時(以下圖):

設置寄存器ADCTSC=0X0C (關閉XP上拉、啓動自動XY方向轉換)

設置寄存器ADCCON的位[0]=1(開啓一次ADC轉換,當ADC轉換成功該位清0)

 

6.編寫代碼

步驟以下:

6.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中斷

6.2 在出口函數中:

1)註銷內核裏的input_dev、

2)釋放中斷、刪除定時器、iounmap註銷地址、

3)釋放input_dev、

6.3 在IRQ_TC中斷函數中:

1)若判斷筆尖爲鬆開,設置寄存器ADCTSC =0XD3(按下中斷)

2)若判斷筆尖按下,設置爲XY自動轉換模式,啓動一次ADC轉換,ADC轉換成功,會進入ADC中斷   

6.4 在IRQ_ADC中斷函數中:

1)獲取ADCDAT0的位[9:0],來算出XY方向座標值

2)測量n次值保存在數組中,而後再次設置爲XY自動轉換模式,啓動ADC

(PS:要啓動ADC轉換以前必須設置一次XY爲自動轉換模式,否則獲取的數據會不許)

3)採集完畢,使用快速排序將n次值排序後,以最小值爲基準,若有偏差很是大的數,則捨棄,若是沒有則打印數組的中間值,實現中值濾波。

(PS: 使用快速排序,比冒泡更快,詳解:http://www.cnblogs.com/lifexy/p/7597276.html )

4)打印數據後,必須設置寄存器ADCTSC =0X1D3(鬆開中斷IRQ_TC)

(PS:在ADC採樣模式下是判斷不到ADCDAT0的bit15位的,由於ADCDAT0已被自動設置爲X座標的採樣值)

5)設置定時器10ms超時時間

6.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.測試運行

7.1 從新設置編譯內核(去掉默認的觸摸屏驅動)

make menuconfig ,進入menu菜單從新設置內核參數:

進入Device Drivers-> Input device support -> Touchscreens ->  

< >   S3C2410/S3C2440 touchscreens     //將自帶的觸摸屏驅動去掉, 不編進內核和模塊

而後make uImage 編譯內核

將新的觸摸屏驅動模塊放入nfs文件系統目錄中

7.2而後燒寫內核,裝載觸摸屏驅動模塊

以下圖, 經過 ls -l /dev/event* 命令能夠看到咱們的觸摸屏驅動的設備爲event0

 

7.3 測試運行:

以下圖所示,能夠看到在同一個點按下時,變化都一致,沒有偏差

 

有了定時器後,也能支持滑動功能, 以下圖滑動Y方向:

 

此時的驅動只是打印數據,並無上報EV_KYE事件和EV_ABS事件

8.添加上報事件

設置上報事件以前還要刪除printk打印信息,步驟以下:

8.1 在IRQ_TC中斷函數濾波處,添加:

input_report_abs(ts_dev, ABS_X, adc_x[2]);   //上報X方向值
input_report_abs(ts_dev, ABS_X, adc_y[2]);   //上報Y方向值
input_report_abs(ts_dev, ABS_PRESSURE, 1);  //上報壓力方向值
input_report_key(ts_dev,BTN_TOUCH,1);      //上報BTN_TOUCH按鍵值按下
input_sync(ts_dev);                                   //上報同步事件,通知系統有事件上報

8.2 在(ADCDAT0的bit15位==1)觸摸鬆開處,添加:

input_report_abs(ts_dev, ABS_PRESSURE, 0);  //上報壓力值爲0
//(PS:必需要上報一次壓力值,不然壓力值會一直爲1,會影響tslib的測試運行)
input_report_key(ts_dev,BTN_TOUCH,0); //上報BTN_TOUCH按鍵值鬆開 input_sync(ts_dev); //上報同步事件,通知系統有事件上報

9.測試運行:

以下圖, 經過 ls -l /dev/event* 命令能夠看到咱們的觸摸屏驅動的設備爲event0

 

9.1使用hexdump命令來調試代碼

(hexdump命令調試代碼詳解地址:http://www.cnblogs.com/lifexy/p/7553550.html)

測試效果以下:

 

(PS:必需要保證有ABS_X、ABS_Y、壓力、觸摸按鍵上傳,否則TSLIB測試會失敗)

9.2 使用TSLIB應用程序測試

(TSLIB安裝以及使用詳解地址: http://www.cnblogs.com/lifexy/p/7628780.html)

TSLIB: 爲觸摸屏驅動得到的採樣提供諸如濾波、去抖、校準等功能,一般做爲觸摸屏驅動的適配層,爲上層的應用提供了一個統一的接口。

校驗界面以下圖所示:

 

運行測試以下圖所示,能隨意畫圖:

 

 

最終,觸摸屏驅動測試成功

 

 

下章開始學習:

 

19.Linux-USB總線驅動分析

相關文章
相關標籤/搜索