1. 程式人生 > >Linux驅動之觸摸屏程序編寫

Linux驅動之觸摸屏程序編寫

kcon ech 驅動 sin gpl 制作 變量 touch nom

本篇博客分以下幾部分講解

1、介紹電阻式觸摸屏的原理

2、介紹觸摸屏驅動的框架(輸入子系統)

3、介紹程序用到的結構體

4、介紹程序用到的函數

5、編寫程序

6、測試程序

1、介紹電阻式觸摸屏的原理

所謂的電阻式觸摸屏,只不過是在LCD屏幕上貼了一層膜,這層膜的大小與LCD的尺寸剛好相同,它分為上下兩層膜(假設上層為X膜,下層為Y膜),按下膜的不同位置,會產生不同的電壓值,這樣根據不同的電壓值可以確定觸點的位置,這就是觸摸屏的基本原理。其實是利用了最簡單的電阻分壓原理。

下面的圖是四線式電阻觸摸屏的原理圖,它的四根線都接到芯片的IO口上

技術分享圖片

1、如圖14.4是什麽都不設置的觸摸屏的情況,其中S1、S2、S3、S4是由芯片內部寄存器控制的,四線電阻的四根線其實等效於接到了這個四個開關上了。

2、如圖14.5是將觸摸屏設置為等待中斷的情況,可以看到只要觸摸板被按下,Y_ADC點的電壓就會發生變化,然後就會產生一個中斷事件報告CPU有按鍵按下待處理。

3、確定按鍵被按下後,通過設置觸摸屏ADC相關寄存器,設置為自動測量坐標模式,首先會進入測量X坐標模式,如圖14.6可以將Y_ADC的值測量出來

4、接著進入測量Y坐標模式,如圖14.7可以將X_ADC的值測量出來。x,y坐標測量出來後會存放在相應寄存中,然後又會產生一個中斷,等待CPU讀取x,y的坐標值

5、CPU確認讀取玩x,y坐標的值之後就進入等待松開模式,當Y_ADC的電壓為VCC時就認為松開,並進入相應中斷。等待CPU處理。

技術分享圖片技術分享圖片

2、介紹觸摸屏驅動的框架(輸入子系統)

觸摸對於linux內核來說也是一個輸入的器件,與按鍵一樣,只不過比按鍵的值豐富了許多。在Linux驅動之輸入子系統簡析已經介紹過了輸入子系統的框架,這裏再簡單回顧一下。

輸入子系統按框架可以分為設備驅動層、事件層、以及核心層。

技術分享圖片

整個調用過程如下:

app_read->evdev_read->kbtab_irq->input_report_key->input_event->evdev_event->evdev_read

應用層 事件層 設備層 核心層 核心層 事件層 事件層

如果要自己添加一個輸入子系統的設備,只需要添加設備層的文件即可。

1、在裏面添加設備層input_dev結構並初始化

2、編寫中斷處理程序

3、介紹程序用到的結構體

其實編寫觸摸屏的驅動程序就是編寫一個輸入子系統的設備層。它需要用的結構體有

1、struct input_dev結構體

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)絕對坐標事件,比如搖桿、觸摸屏感應
    
    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)];//存放支持的絕對坐標值
    unsigned long mscbit[NBITS(MSC_MAX)];
    unsigned long ledbit[NBITS(LED_MAX)];
    unsigned long sndbit[NBITS(SND_MAX)];
    unsigned long ffbit[NBITS(FF_MAX)];
    unsigned long swbit[NBITS(SW_MAX)];

    ...
    ...

    int absmax[ABS_MAX + 1];//絕對坐標的最大值
    int absmin[ABS_MAX + 1];//絕對坐標的最小值
    int absfuzz[ABS_MAX + 1];//絕對坐標的幹擾值,默認為0,
    int absflat[ABS_MAX + 1];//絕對坐標的平焊位置,默認為0
    
    ...
    ...
};

2、struct timer_list結構體(用於觸摸屏的長按處理)

struct timer_list {
    struct list_head entry;
    unsigned long expires;//期望定時器執行的jiffies值,假設需要定時10ms,那麽應該設置expires = jiffies + HZ/100(10ms為100hz)

    void (*function)(unsigned long);//定時器定時時間到了之後的回調函數
    unsigned long data;//回調函數傳入的數據

    struct tvec_t_base_s *base;
#ifdef CONFIG_TIMER_STATS
    void *start_site;
    char start_comm[16];
    int start_pid;
#endif
};

4、介紹程序用到的函數

觸摸屏程序用到的函數眾多大致分為以下幾類:

1、輸入子系統相關的函數

struct input_dev *input_allocate_device(void);//分配一個struct input_dev結構體,返回的是struct input_dev *
inline void set_bit(int nr, volatile unsigned long *addr);//這是一個內聯函數,在調用的時候展開,功能為設置*addr的nr位為1
inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);//設置絕對位移的參數
//struct input_dev *dev表示哪個輸入子系統設備的絕對位移參數
//axis表示哪個坐標的絕對位移參數,可以設置為ABS_X、ABS_Y、ABS_PRESSURE等,位於include/linux/input.h中
//min絕對位移坐標的最小值
//max絕對位移坐標的最大值
//fuzz絕對位移坐標的幹擾值,默認為0
//flat絕對位移坐標的平焊位置,默認為0
int input_register_device(struct input_dev *dev);//註冊輸入子系統設備驅動,輸入參數為struct input_dev *
void input_unregister_device(struct input_dev *dev);//反註冊輸入子系統的設備驅動,輸入參數為struct input_dev *
void input_free_device(s3c_ts_input);//釋放分配的input_dev結構,,輸入參數為struct input_dev *
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value);//上傳絕對位移事件
//struct input_dev *dev表示哪個輸入子系統設備的事件上傳
//code表示絕對位移事件的哪類事件,可以取值ABS_PRESSURE、ABS_X、ABS_Y
//value表示事件的按鍵值
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);//上傳按鍵事件
//struct input_dev *dev表示哪個輸入子系統設備的事件上傳
//code表示按鍵事件的哪類事件,可以取值BTN_TOUCH等
//value表示事件的按鍵值
static inline void input_sync(struct input_dev *dev);//上傳同步事件,表示這次事件數據已經傳送完成了

2、中斷相關的函數

int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id);//註冊一個中斷
//返回0表示註冊成功,反之註冊失敗
//irq表示註冊的中斷號:有IRQ_TC、IRQ_ADC等等,位於include\asm-arm\arch-s3c2410irqs.h
//handler表示中斷出現後的回調函數,函數原型為typedef irqreturn_t (*irq_handler_t)(int, void *);
//irqflags表示中斷類型,可以設置為IRQF_SHARED、IRQF_DISABLED、IRQF_SAMPLE_RANDOM。位於include\linux\interrupt.h
//char *devname表示中斷的名稱
//void *dev_id傳給中斷回調函數的參數
void free_irq(unsigned int irq, void *dev_id);//釋放中斷
//irq表示中斷號
//void *dev_id為傳給中斷回調函數的參數

3、定時器相關的函數

void fastcall init_timer(struct timer_list *timer);//初始化一個定時器*timer
inline void add_timer(struct timer_list *timer);//將定時器*timer將入內核
int mod_timer(struct timer_list *timer, unsigned long expires);//更改定時器*timer的定時值

5、編寫程序

程序的大致思路為:

1、分配一個input_dev結構體

2、設置input_dev結構體

3、註冊input_dev結構體

4、硬件相關的操作

  a、使能ADC時鐘  

  b、設置S3C2440的ADC/TS寄存器

  c、註冊INT_TC中斷

  d、註冊INT_ADC中斷

  f、設置一個定時器用於長按處理

程序流程:

1、首先觸摸屏位於等待按下狀態;

2、待按鍵按下後,進入INTC_TC中斷,在中斷裏面設置為INT_ADC中斷等待ADC轉換完成;

3、轉換完成後進入INT_ADC中斷:在裏面上報X、Y坐標值;改變定時器值,使得10ms後調用定時器處理函數;設置為等待松開模式。

4、若按鍵在10ms內還未松開,定時器處理函數裏面將觸摸屏設置為INT_ADC中斷模式,繼續轉到第3步。

5、若在10ms內松開,則進入INTC_TC中斷,在中斷裏面設置為等待按鍵模式

下面是完整程序

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/io.h>        //含有iomap函數iounmap函數
#include <asm/uaccess.h>//含有copy_from_user函數
#include <linux/device.h>//含有類相關的處理函數
#include <linux/fb.h>      //含有fb_info結構體定義
//#include <asm/dma-mapping.h> //含有dma_free_writecombine宏定義
//#include <linux/dma-mapping.h>  //含有dma_free_writecombine宏定義
#include <linux/platform_device.h>//含有平臺設備總線模型相關變量
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <asm-generic/errno-base.h>  //含有各種錯誤返回值
#include <linux/input.h>                    //含有輸入子系統相關的類型
#include <linux/irq.h>    //含有IRQ_HANDLED\IRQ_TYPE_EDGE_RISING
#include <asm/irq.h>   //含有IRQT_BOTHEDGE觸發類型
#include <linux/interrupt.h> //含有request_irq、free_irq函數

struct 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_input;     //新建一個輸入子系統的設備層結構
static struct ts_regs *ts_reg;                  //分配一個adc觸摸相關的寄存器
static struct timer_list ts_timer;                       //定義一個定時器處理長按的情況

static void enter_wait_pen_down_mode(void)//進入等待觸摸筆按下的模式
{
    ts_reg->adctsc = 0xd3;//設置等待按下 模式中斷
}

static void enter_wait_pen_up_mode(void)//進入等待觸摸筆松開的模式
{
    ts_reg->adctsc = 0x1d3;//設置等待松開 模式中斷
}

static void enter_measure_xy_mode(void)
{
    ts_reg->adctsc  = ((1<<2) | (1<<3));//設置自動測量adc模式,上拉電阻必須去掉
    ts_reg->adccon |= (1<<0);//啟動adc轉換
}


static irqreturn_t pen_up_down_irq(int irq, void *dev_id)
{
    if (ts_reg->adcdat0 & (1<<15))//如果是釋放中斷
    {
    //    printk("pen up\n");
        enter_wait_pen_down_mode();//進入等待按下模式
    }
    else//如果是按下中斷
    {
    //    printk("pen down\n");
        //enter_wait_pen_up_mode();
        enter_measure_xy_mode();//進入自動轉換adc模式等待ADC轉換完成進入ADC中斷
    }
//    if (ts_reg->adcdat1 & (1<<15))
//    {
//        printk("pen up\n");
//        enter_wait_pen_down_mode();
//    }
//    else
//    {
//        printk("pen down\n");
//        enter_wait_pen_up_mode();
//    }
    return IRQ_HANDLED;
}


static int s3c_filter_ts(int x[], int y[])//adc軟件濾波
{
#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 = (avr_x > x[2])?(avr_x - x[2]):(x[2] - avr_x);
    det_y = (avr_y > y[2])?(avr_y - y[2]):(y[2] - avr_y);

    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 = (avr_x > x[3])?(avr_x - x[3]):(x[3] - avr_x);
    det_y = (avr_y > y[3])?(avr_y - y[3]):(y[3] - avr_y);

    if((det_x >ERR_LIMIT) || (det_y >ERR_LIMIT))
        return 0;
    
    return 1;
    
}


static irqreturn_t adc_irq(int irq, void *dev_id)
{
    static int x[4],y[4];
    static int cnt=0;
    
    int xvalue;
    int yvalue;
    int i;
    
    /*優化措施2,如果ADC完成時,發現觸摸筆已經松開,則丟棄此次結果*/
    if (ts_reg->adcdat0 & (1<<15))//如果已經松開了
    {
        cnt = 0;//不能漏
        input_report_abs(s3c_ts_input,ABS_PRESSURE,0);  //壓力為0
        input_report_key(s3c_ts_input,BTN_TOUCH,0);      //0表示松開
        input_sync(s3c_ts_input);                                    //事件已經處理完
        enter_wait_pen_down_mode();//直接進入等待按下模式
    }
    else
    {
          /*優化措施3:多次測量求平均值*/
        x[cnt] = ts_reg->adcdat0&0x3ff;
        y[cnt] = ts_reg->adcdat1&0x3ff;
        if(++cnt==4)//先++,再判斷
        {
            if(s3c_filter_ts(x,y))//如果差值小於10
            {
                xvalue = 0;
                yvalue = 0;
                for(i=0;i<4;i++)
                {
                    xvalue = x[i] + xvalue;
                    yvalue = y[i] + yvalue;
                }
                xvalue /= 4;
                yvalue /= 4;
                input_report_abs(s3c_ts_input,ABS_X,xvalue);
                input_report_abs(s3c_ts_input,ABS_Y,yvalue);
                input_report_abs(s3c_ts_input,ABS_PRESSURE,1);//壓力值為1
                input_report_key(s3c_ts_input,BTN_TOUCH,1);     //1表示按下
                input_sync(s3c_ts_input);//事件已經處理完畢
            
                //printk("x = %d ,y = %d\n",xvalue,yvalue);
                mod_timer(&ts_timer,jiffies+HZ/100);//
                
                //enter_wait_pen_up_mode();//進入等待松開模式
            }
            //else//如果差值大於10則丟棄
            //{
                enter_wait_pen_up_mode();//進入等待松開模式
            //}
            cnt = 0;
        }
        else
        {
            enter_measure_xy_mode();//繼續進入自動測量模式
        }
    }
        
    return IRQ_HANDLED;
}

static void s3c_ts_timer_funcitin(unsigned long t)
{
    if (ts_reg->adcdat0 & (1<<15))//如果已經松開了
    {
        input_report_abs(s3c_ts_input,ABS_PRESSURE,0);  //壓力為0
        input_report_key(s3c_ts_input,BTN_TOUCH,0);      //0表示松開
        input_sync(s3c_ts_input);                                    //事件已經處理完畢
        
        enter_wait_pen_down_mode();//直接進入等待按下模式
    }
    else
    {
        enter_measure_xy_mode();//繼續進入自動測量模式
    }
}

static int s3c_ts_init(void)
{
    struct clk    *adc_clock;
    int ret;
    /* 1、分配一個input_dev結構體 */
    s3c_ts_input = input_allocate_device();//在設備層分配一個input_dev結構
    if (!s3c_ts_input)
        return -ENOMEM;
    
    /* 2、設置它 */
        /* 2.1 能產生哪些事件 */
    set_bit(EV_KEY, s3c_ts_input->evbit); //按鍵事件
    set_bit(EV_ABS, s3c_ts_input->evbit);//絕對位移事件
            
        /* 2.2 能產生這類事件裏的哪些事件 */
       set_bit(BTN_TOUCH, s3c_ts_input->keybit);//按鍵事件裏的BTN_TOUCH事件
       
       input_set_abs_params(s3c_ts_input, ABS_X, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_input, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_input, ABS_PRESSURE, 0, 1, 0, 0);

    /* 3、註冊 */
    input_register_device(s3c_ts_input);//註冊設備驅動    
    

    /* 4、硬件相關的操作 */
   /* 4.1 使能時鐘(CLKCON[15])*/ //為了省電,內核將不相關的外設時鐘關掉 用的是PCLK時鐘
    adc_clock = clk_get(NULL, "adc");
    clk_enable(adc_clock);//使能ACD時鐘
    
   /* 4.2 設置S3C2440的ADC/TS寄存器 */
   ts_reg = ioremap(0x58000000, sizeof(struct ts_regs));//將ADC相關的寄存器的物理地址轉換為虛擬地址

   /*預分配使能、預分配系數=49+1;所以ADC時鐘為1M*/
   ts_reg->adccon = (1<<14) | (49<<6);
   
   /*優化措施1,設置ADCDLY為最大值,這使得電壓穩定後再發出IRQ_TC中斷*/
   ts_reg->adcdly = 0xffffffff;    //設置adc延時值,等待數值穩定後再讀取相應的值

   
   /*註冊INT_TC中斷*/
  ret = request_irq(IRQ_TC, pen_up_down_irq, IRQF_SAMPLE_RANDOM,  "ts_pen", NULL);
   
  if(ret)
      return -1;
  
   /*註冊INT_ADC中斷*/
  ret = request_irq(IRQ_ADC,adc_irq,IRQF_SAMPLE_RANDOM,"adc",NULL);

  if(ret)
      return -1;

   /*優化措施4:使用定時器處理長按滑動的情況*/
 init_timer(&ts_timer);
 ts_timer.function = s3c_ts_timer_funcitin;
 add_timer(&ts_timer);
    
   /*進入等待中斷模式*/
  enter_wait_pen_down_mode();//等待進入按下中斷

   return 0;
   
}


static void s3c_ts_exit(void)
{
    input_unregister_device(s3c_ts_input);//反註冊
    input_free_device(s3c_ts_input);//釋放分配的input_dev結構
    iounmap(ts_reg);
    free_irq(IRQ_TC, NULL);//釋放觸摸中斷
    free_irq(IRQ_ADC, NULL);//釋放adc中斷
}


module_init(s3c_ts_init);
module_exit(s3c_ts_exit);

MODULE_LICENSE("GPL");

6、測試程序

1、make menuconfog 去掉原來的觸摸屏驅動程序

  Device Drives
    Input device supoport
      Generic input layer
        Touchscreens
          <>S3c2410/s3c2440 touchscreens
2、make uImage

3、重啟開發版,進入UBOOT,下載編譯完成的uImage_nots文件到0x30000000地址處:nfs 30000000 192.168.1.5:/work/nfs_root/uImage_nolcd。然後bootm 30000000進入此內核

4、進入系統後掛接網絡文件系統mount 0t nfs -o nolock,vers=2 192.168.1.5:/work/nfs_root/first_fs /mnt

5、切換到/mnt目錄。操作的就是服務器上的目錄

6、制作tslib庫,tslib庫是介於觸摸屏驅動程序與應用程序之間的接口。

  a、從網上下載tslib-1.4.tar.gz安裝包

  b、tar xzf tslib-1.4.tar.gz解壓

  c、cd tslib切換到tslib目錄

  d、./autogen.sh運行

  e、mkdir tmp新建一個temp目錄

  f、echo "ac_cv_func_malloc_0_nonnull=yes" >arm-linux.cache

  g、./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp將安裝目錄設置為新建的tmp目錄

  h、make編譯;make install安裝

  i、cd tmp切換到安裝目錄;cp * -rf /work/nfs_root/first_fs將當前所有文件都拷貝到網絡文件系統下

7、使用tslib庫,tslib庫已經拷貝到了/mnt目錄下

8、先安裝10th_ts_drv.ko

9、修改 /etc/ts.conf第1行(去掉#號和第一個空格):

# module_raw input
改為:
module_raw input

10、配置環境變量

export TSLIB_TSDEVICE=/dev/event0    //使用的輸入設備
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0          //使用的顯示設備

11、運行ts_calibrate程序進行觸摸屏校驗

12、運行ts_test可以進行寫字、畫圖等操作。

Linux驅動之觸摸屏程序編寫