Linux觸摸屏驅(qū)動

在事件處理層的函數(shù)都是通過input_register_handler()函數(shù)注冊到input_hander_list鏈表中,搜索input_register_handler注冊函數(shù),就可以看到事件處理層里的函數(shù):


右邊的驅(qū)動事件處理,內(nèi)核是已經(jīng)寫好了的,所以觸摸屏只需要寫具體的驅(qū)動設(shè)備,然后內(nèi)核會與觸摸屏驅(qū)動tsdev.c自動連接。
結(jié)構(gòu)體成員如下

struct input_dev {     
   void *private;
   const char *name;  //設(shè)備名字
   const char *phys;  //文件路徑,比如 input/buttons
   const char *uniq;  
   struct input_id id;

   unsigned long evbit[NBITS(EV_MAX)];  //表示支持哪類事件,常用有以下幾種事件(可以多選)
   //EV_SYN      同步事件,當(dāng)使用input_event()函數(shù)后,就要使用這個上報個同步事件
   //EV_KEY       鍵盤事件
   //EV_REL       (relative)相對坐標(biāo)事件,比如鼠標(biāo)
   //EV_ABS       (absolute)絕對坐標(biāo)事件,比如搖桿、觸摸屏感應(yīng)
   //EV_MSC      其他事件,功能
   //EV_LED       LED燈事件
   //EV_SND      (sound)聲音事件
   //EV_REP       重復(fù)鍵盤按鍵事件
   //(內(nèi)部會定義一個定時器,若有鍵盤按鍵事件一直按下/松開,就重復(fù)定時,時間一到就上報事件)  

   //EV_FF         受力事件
   //EV_PWR      電源事件
   //EV_FF_STATUS  受力狀態(tài)事件

   unsigned long keybit[NBITS(KEY_MAX)];   //存放支持的鍵盤按鍵值
   //鍵盤變量定義在:include/linux/input.h, 比如: KEY_L(按鍵L)、BTN_TOUCH(觸摸屏的按鍵)

   unsigned long relbit[NBITS(REL_MAX)];    //存放支持的相對坐標(biāo)值
   unsigned long absbit[NBITS(ABS_MAX)];   //存放支持的絕對坐標(biāo)值,存放下面4個absxxx[]
   unsigned long mscbit[NBITS(MSC_MAX)];   //存放支持的其它事件,也就是功能
   unsigned long ledbit[NBITS(LED_MAX)];    //存放支持的各種狀態(tài)LED
   unsigned long sndbit[NBITS(SND_MAX)];    //存放支持的各種聲音
   unsigned long ffbit[NBITS(FF_MAX)];       //存放支持的受力設(shè)備
   unsigned long swbit[NBITS(SW_MAX)];     //存放支持的開關(guān)功能
     ... ...


/*以下4個數(shù)組都會保存在上面成員absbit[]里,數(shù)組號為:ABS_xx ,位于 
 include/linux/input.h */
/*比如數(shù)組0,標(biāo)志就是ABS_X,以下4個的absXXX[0]就是表示絕對位移X方向的最大值、最小值... */
/*對于觸摸屏常用的標(biāo)志有:
ABS_X(X坐標(biāo)方向), ABS_Y(Y坐標(biāo)方向), ABS_PRESSURE(壓力方向,比如繪圖,越用力線就越粗)* / 
   int absmax[ABS_MAX + 1];      //絕對坐標(biāo)的最大值
   int absmin[ABS_MAX + 1];      //絕對坐標(biāo)的最小值
   int absfuzz[ABS_MAX + 1];     //絕對坐標(biāo)的干擾值,默認(rèn)為0,
   int absflat[ABS_MAX + 1];     //絕對坐標(biāo)的平焊位置,默認(rèn)為0
... ...

需要用到的函數(shù)

struct input_dev *input_allocate_device(void);  //向內(nèi)存中分配input_dev結(jié)構(gòu)體

input_free_device(struct input_dev *dev);   //釋放內(nèi)存中的input_dev結(jié)構(gòu)體

input_register_device(struct input_dev *dev);   //注冊一個input_dev,若有對應(yīng)的驅(qū)動事件,
則在/sys/class/input下創(chuàng)建這個類設(shè)備

input_unregister_device(struct input_dev *dev);   //卸載/sys/class/input目錄下的
input_dev這個類設(shè)備


set_bit(nr,p);                  //設(shè)置某個結(jié)構(gòu)體成員p里面的某位等于nr,支持這個功能
/* 比如:
set_bit(EV_KEY,buttons_dev->evbit);   //設(shè)置input_dev結(jié)構(gòu)體buttons_dev->evbit支持EV_KEY
set_bit(KEY_S,buttons_dev->keybit);  //設(shè)置input_dev結(jié)構(gòu)體buttons_dev->keybit支持按鍵”S”

*/

input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat); 
//設(shè)置絕對位移的支持參數(shù)
//dev: 需要設(shè)置的input_dev結(jié)構(gòu)體
//axis : 需要設(shè)置的數(shù)組號,常用的有: ABS_X(X坐標(biāo)方向), ABS_Y(Y坐標(biāo)方向), ABS_PRESSURE(壓力方向)//min: axis方向的最小值, max:axis方向的最大值, fuzz: axis方向的干擾值, flat:axis方向的平焊位置


input_report_abs(struct input_dev *dev, unsigned int code, int value);   
//上報EV_ABS事件
//該函數(shù)實際就是調(diào)用的input_event(dev, EV_ABS, code, value);
//*dev :要上報哪個input_dev驅(qū)動設(shè)備的事件
// code: EV_ABS事件里支持的哪個方向,比如X坐標(biāo)方向則填入: ABS_X
//value:對應(yīng)的方向的值,比如X坐標(biāo)126

input_report_key(struct input_dev *dev, unsigned int code, int value);  
//上報EV_KEY事件 

input_sync(struct input_dev *dev); //同步事件通知,通知系統(tǒng)有事件上報

struct  clk *clk_get(struct device *dev, const char *id);    
//獲得*id模塊的時鐘,返回一個clk結(jié)構(gòu)體
//*dev:填0即可,     *id:模塊名字, 比如"adc","i2c"等,名字定義在clock.c中

clk_enable(struct clk *clk);   
//開啟clk_get()到的模塊時鐘,就是使能CLKCON寄存器的某個模塊的位

電阻式觸摸屏介紹


引腳說明:
YM: (Y Minus)觸摸屏的Y坐標(biāo)的負(fù)線,也可以用Y -表示
YP : (Y Power)觸摸屏的Y坐標(biāo)的正線, 也可以用Y+表示
XM: (Y Minus)觸摸屏的X坐標(biāo)的負(fù)線, 也可以用X-表示
XP : (Y Power)觸摸屏的X坐標(biāo)的正線, 也可以用X+表示
觸摸屏包含了兩個阻性層,如下圖所示:

當(dāng)沒有觸摸按下時,x層和Y層是分離的,此時就測不到電壓。
測X坐標(biāo)方向時
如下圖, 把XP接3.3V , XM接0V, YP和YM懸空,我們以按壓X坐標(biāo)的中間位置, X層和Y層便閉合了,此時YP就會輸出當(dāng)前X坐標(biāo)值的1.66V給CPU。

測Y坐標(biāo)方向時:
如下圖, 把YP接3.3V , YM接0V, XP和XM懸空,我們以按壓X坐標(biāo)的中間位置, X層和Y層便閉合了,此時XP就會輸出當(dāng)前X坐標(biāo)值的1.66V給CPU 。

ADC模型數(shù)據(jù)轉(zhuǎn)換器
,2440的ADC分辨率為10位(0~0X3FFF)

如下圖,若工作在普通ADC模式,則通過寄存器ADCCON->SEL_MUX來選擇轉(zhuǎn)換哪個引腳的模擬信號。當(dāng)設(shè)置為ADC等待中斷模式時,測到有屏幕筆尖觸摸,就會產(chǎn)生INT_TC中斷,其中ADC的工作頻率最大為2.5MHZ,需要設(shè)置寄存器ADCCON->PRSCVL更改分頻系數(shù)。

獲取筆尖觸摸屏按下/松開使用的是ADC等待中斷模式:
當(dāng)筆尖落下時觸摸屏控制器產(chǎn)生中斷(INT_TC)信號。需要設(shè)置寄存器ADCTSC=0xd3/0x1d3
設(shè)置寄存器ADCTSC=0x0d3/0x1d3 (X 1101 0011)時(如下圖):
開啟 YM開關(guān),使能XP上拉, 開啟等待中斷模式。當(dāng)有筆尖按下時,X層和Y層閉合,然后會拉低XP和XM電平,輸出低電平,設(shè)置為0x0d3是檢測觸摸低電平, 設(shè)置為0x1d3是檢測觸摸上拉電平。
獲取XY坐標(biāo)時使用的是自動 X/Y 方向轉(zhuǎn)換模式
當(dāng)ADC轉(zhuǎn)換成功, X 坐標(biāo)值到 ADCDAT0 和 Y 坐標(biāo)值到ADCDAT1 后,就會產(chǎn)生INT_ADC中斷
自動獲取XY坐標(biāo)時(如下圖):

設(shè)置寄存器ADCTSC=0X0C (關(guān)閉XP上拉、啟動自動XY方向轉(zhuǎn)換)
設(shè)置寄存器ADCCON的位[0]=1(開啟一次ADC轉(zhuǎn)換,當(dāng)ADC轉(zhuǎn)換成功該位清0)
代碼編寫
在init入口函數(shù)中:
1.分配一個input_dev結(jié)構(gòu)體
2.設(shè)置input_dev的成員

  • 設(shè)置input_dev->evbit支持按鍵事件,絕對位移事件(觸摸屏:通過按鍵BTN_TOUCH獲取按下/松開,通過絕對位移獲取坐標(biāo))
  • 設(shè)置input_dev-> keybit支持BTN_TOUCH觸摸屏筆尖按下。
  • 設(shè)置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驅(qū)動設(shè)備到內(nèi)核中
4.設(shè)置觸摸屏相關(guān)的硬件

  • 開啟ADC時鐘,使用clk_get ()和clk_enable()函數(shù)。
  • ioremap獲取寄存器地址,設(shè)置寄存器ADCCON =(1<<14)|(49<<6),分頻
  • 設(shè)置寄存器ADCDLY=0xffff,ADC啟動延時時間設(shè)為最大值,使觸摸按壓更加穩(wěn)定
  • 開啟IRQ_TC筆尖中斷、開啟IRQ_ADC中斷獲取XY坐標(biāo)
  • 初始化定時器,增加觸摸滑動功能
  • 最后設(shè)置寄存器ADCTSC=0x0d3,開啟IRQ_TC中斷

5.在出口函數(shù)中:

  • 注銷內(nèi)核里的input_dev
  • 釋放中斷、刪除定時器、iounmap注銷地址
  • 釋放input_dev

6.在IRQ_TC中斷函數(shù)中:

  • 若判斷筆尖為松開,設(shè)置寄存器ADCTSC =0XD3(按下中斷)
  • 若判斷筆尖按下,設(shè)置為XY自動轉(zhuǎn)換模式,啟動一次ADC轉(zhuǎn)換,ADC轉(zhuǎn)換成功,會進(jìn)入ADC中斷

7.在IRQ_ADC中斷函數(shù)中:

  • 獲取ADCDAT0的位[9:0],來算出XY方向坐標(biāo)值
  • 測量n次值保存在數(shù)組中,然后再次設(shè)置為XY自動轉(zhuǎn)換模式,啟動ADC
  • 采集完畢,使用快速排序?qū)次值排序后,以最小值為基準(zhǔn),如有誤差非常大的數(shù),則舍棄,如果沒有則打印數(shù)組的中間值,實現(xiàn)中值濾波。
  • 打印數(shù)據(jù)后,必須設(shè)置寄存器ADCTSC =0X1D3(松開中斷IRQ_TC)
  • 設(shè)置定時器10ms超時時間
  1. 在定時器超時函數(shù)中:
  • 判斷ADCDAT0的bit15位,若還在按下再次啟動ADC轉(zhuǎn)換(實現(xiàn)觸摸滑動功能)
  • 若松開,設(shè)置寄存器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))
{
    /* 已經(jīng)松開 */
    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坐標(biāo) */
    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;


/* 優(yōu)化措施2: 如果ADC完成時, 發(fā)現(xiàn)觸摸筆已經(jīng)松開, 則丟棄此次結(jié)果 */
adcdat0 = s3c_ts_regs->adcdat0;
adcdat1 = s3c_ts_regs->adcdat1;

if (s3c_ts_regs->adcdat0 & (1<<15))
{
    /* 已經(jīng)松開 */
    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);
    /* 優(yōu)化措施3: 多次測量求平均值 */
    x[cnt] = adcdat0 & 0x3ff;
    y[cnt] = adcdat1 & 0x3ff;
    ++cnt;
    if (cnt == 4)
    {
        /* 優(yōu)化措施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結(jié)構(gòu)體 */
s3c_ts_dev = input_allocate_device();

/* 2. 設(shè)置 */
/* 2.1 能產(chǎn)生哪類事件 */
set_bit(EV_KEY, s3c_ts_dev->evbit);
set_bit(EV_ABS, s3c_ts_dev->evbit);

/* 2.2 能產(chǎn)生這類事件里的哪些事件 */
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. 硬件相關(guān)的操作 */
/* 4.1 使能時鐘(CLKCON[15]) */
clk = clk_get(NULL, "adc");
clk_enable(clk);

/* 4.2 設(shè)置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. 先設(shè)為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);

/* 優(yōu)化措施1: 
 * 設(shè)置ADCDLY為最大值, 這使得電壓穩(wěn)定后再發(fā)出IRQ_TC中斷
 */
s3c_ts_regs->adcdly = 0xffff;

/* 優(yōu)化措施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");
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容