專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機教程網(wǎng) >> MCU設(shè)計實例 >> 瀏覽文章

輸入設(shè)備驅(qū)動之按鍵設(shè)備驅(qū)動

作者:龔平   來源:本站原創(chuàng)   點擊數(shù):  更新時間:2014年03月14日   【字體:

      我的環(huán)境:
      
Fedora14 內(nèi)核版本,2.6.38.1

開發(fā)板:TQ2440
移植內(nèi)核版本:2.6.30.4
2.6版本的內(nèi)核中,驅(qū)動的開發(fā)逐漸發(fā)展成基于總線模型等一定結(jié)構(gòu)的開發(fā)模式,采用了分層的設(shè)計思想,這樣的變化使得驅(qū)動開發(fā)的工作量相對而言越來越少,但是也增加了我們閱讀、分析源碼的思想的難度。
Linux輸入子系統(tǒng)就是一個基于分層模式的系統(tǒng),其基本的層次分解如下圖所示。
    在圖中我們可以發(fā)現(xiàn)輸入子系統(tǒng)主要包括三個部分設(shè)備驅(qū)動層(input driver)、核心層(input core)和輸入事件驅(qū)動層。輸入子系統(tǒng)的劃分使得輸入設(shè)備的驅(qū)動程序設(shè)計越來越簡單,但是其中的思想采用我們學(xué)習(xí)的重點和難點。
Input子系統(tǒng)處理輸入事務(wù),任何輸入設(shè)備的驅(qū)動程序都可以通過Input輸入子系統(tǒng)提供的接口注冊到內(nèi)核,利用子系統(tǒng)提供的功能來與用戶空間交互。輸入設(shè)備一般包括鍵盤,鼠標(biāo),觸摸屏等,在內(nèi)核中都是以輸入設(shè)備出現(xiàn)的。下面分析input輸入子系統(tǒng)的結(jié)構(gòu),以及功能實現(xiàn)。
1. Input子系統(tǒng)是分層結(jié)構(gòu)的,總共分為三層:硬件驅(qū)動層,子系統(tǒng)核心層,事件處理層。 
(1)、其中硬件驅(qū)動層負責(zé)操作具體的硬件設(shè)備,這層的代碼是針對具體的驅(qū)動程序的,需要驅(qū)動程序的作者來編寫。
(2)、子系統(tǒng)核心層是鏈接其他兩個層之間的紐帶與橋梁,向下提供驅(qū)動層的接口,向上提供事件處理層的接口。
(3)、事件處理層負責(zé)與用戶程序打交道,將硬件驅(qū)動層傳來的事件報告給用戶程序。
2. 各層之間通信的基本單位就是事件,任何一個輸入設(shè)備的動作都可以抽象成一種事件,如鍵盤的按下,觸摸屏的按下,鼠標(biāo)的移動等。事件有三種屬性:類型(type),編碼(code),值(value),Input子系統(tǒng)支持的所有事件都定義在input.h中,包括所有支持的類型,所屬類型支持的編碼等。事件傳送的方向是硬件驅(qū)動層-->子系統(tǒng)核心-->事件處理層-->用戶空間。
在驅(qū)動程序設(shè)計中,我們對于設(shè)備的驅(qū)動設(shè)計主要集中在設(shè)備驅(qū)動層的實現(xiàn),但是這與之前的設(shè)備驅(qū)動開發(fā)存在較大的差別,主要是因為設(shè)備驅(qū)動不再是編寫基本操作的實現(xiàn)過程,也就是不在是對struct file_operations 這個結(jié)構(gòu)體對象的填充和實現(xiàn)。在輸入設(shè)備驅(qū)動中的主要實現(xiàn)包括下面幾個過程:
1、分配一個輸入設(shè)備對象。并完成響應(yīng)結(jié)構(gòu)體元素的填充,主要包括支持的事件類型和事件代號等。
分配對象的函數(shù):
struct input_dev *input_allocate_device(void);
釋放對象函數(shù):
void input_free_device(struct input_dev *dev);
設(shè)置支持的事件類型和事件代碼:
通常采用set_bit函數(shù)實現(xiàn):
設(shè)置支持的事件類型(支持按鍵事件)
        set_bit(EV_KEY, input_dev->evbit);
設(shè)置支持的事件代碼(支持按鍵1)
      set_bit(KEY_1, input_dev->keybit);
2、完成輸入設(shè)備對象的注冊,將設(shè)備對象注冊到輸入子系統(tǒng)當(dāng)中去,當(dāng)然也有對應(yīng)的釋放函數(shù)。
注冊設(shè)備到內(nèi)核:
int input_register_device(struct input_dev *dev);
注銷設(shè)備:
void input_unregister_device(struct input_dev *dev);
3、向核心層(input core)匯報事件的發(fā)生以及傳輸事件類型和事件代碼等。這一部分通常是采用中斷的方法實現(xiàn),在中斷中向上一層次(Input Core)傳送發(fā)生事件的事件類型、事件代號以及事件對應(yīng)的值等。但是上報的內(nèi)容結(jié)構(gòu)體都是基于一個固定結(jié)構(gòu)體的
struct input_event,在用戶空間也可以采用這個結(jié)構(gòu)體實現(xiàn)對事件的訪問。
 struct input_event {
       /*事件發(fā)生的時間*/
       struct timeval time;
       /*事件類型*/
       __u16 type;
       /*事件代號*/
       __u16 code;
       /*事件對應(yīng)的值*/
       __s32 value;
};
基本的事件類型包括如下:
#define EV_SYN               0x00
/*常用的事件類型*/
#define EV_KEY               0x01
#define EV_REL                0x02
#define EV_ABS                0x03
#define EV_MSC               0x04
#define EV_SW                 0x05
#define EV_LED                0x11
#define EV_SND               0x12
#define EV_REP                0x14
#define EV_FF                   0x15
#define EV_PWR               0x16
#define EV_FF_STATUS          0x17
#define EV_MAX                     0x1f
#define EV_CNT               (EV_MAX+1)
支持的事件代號:
...
#define KEY_3                  4
#define KEY_4                  5
#define KEY_5                  6
#define KEY_6                  7
#define KEY_7                  8
#define KEY_8                  9
#define KEY_9                  10
#define KEY_0                  11
#define KEY_MINUS              12
#define KEY_EQUAL              13
#define KEY_BACKSPACE           14
主要的匯報函數(shù)如下:
/*匯報鍵值函數(shù)*/
void input_report_key(struct input_dev *dev, unsigned int code, int value);
/*匯報相對坐標(biāo)值函數(shù)*/
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
/*匯報絕對坐標(biāo)值函數(shù)*/
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
也可以采用更加一般的函數(shù)匯報,上面的三個函數(shù)是通過下面這個函數(shù)實現(xiàn)的。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
完成上面的三個部分,一個輸入設(shè)備的驅(qū)動程序也就完成了。但是其中的具體實現(xiàn)還需要閱讀相關(guān)的源碼。特別是設(shè)備的操作是如何通過輸入設(shè)備實現(xiàn)等基本的操作是我們應(yīng)該去關(guān)注的,設(shè)備的具體操作函數(shù)實質(zhì)上已經(jīng)因為一些共性被設(shè)計成了通用的接口,在輸入子系統(tǒng)內(nèi)部已經(jīng)實現(xiàn)。
TQ2440中的按鍵主要是采用外部中斷的形式實現(xiàn),所以我在實驗過程中也采用了外部中斷的模式直接對按鍵進行操作。
具體的程序如下所示:驅(qū)動代碼:
  1.  
  2.     #include<linux/module.h>
        #include<linux/kernel.h>
        #include<linux/init.h>
        #include<linux/types.h>
        #include<linux/slab.h>
        #include<linux/string.h>
        #include<linux/input.h>
        #include<linux/irq.h>
        #include<mach/gpio.h>
        #include<mach/regs-gpio.h>
        #include<asm/irq.h>
        #include<asm/io.h>
        #include<linux/interrupt.h>
        #include<linux/delay.h>
        #define DEVICE_NAME    "TQ2440_BUTTON"
        #define NUMBERS_BUTTONS        4
        static const char * input_name = "Tq2440_button";
        static struct input_dev *button_dev;
        /*中斷集合*/
        static const int irqs[NUMBERS_BUTTONS]={
            IRQ_EINT0,
            IRQ_EINT1,
            IRQ_EINT2,
            IRQ_EINT4,
        };
        /*端口集合*/
        static const int gpios[NUMBERS_BUTTONS]={
            S3C2410_GPF0,
            S3C2410_GPF1,
            S3C2410_GPF2,
            S3C2410_GPF4,
        };
        /*事件代號集合*/
        static const int keys[NUMBERS_BUTTONS]={
            KEY_1,
            KEY_2,
            KEY_3,
            KEY_4,
        };
        /*中斷處理程序*/
        static irqreturn_t button_interrupt(int irq,void * p)
        {
            int val = 0, i= 0;
            for(i = 0; i < NUMBERS_BUTTONS; ++ i)
            {
                /*如果中斷號正確*/
               
                if(irqs[i] == irq)
                {
                    /*讀取端口的值*/
                   
                    val = s3c2410_gpio_getpin(gpios[i]);
                    /*匯報端口的值,實質(zhì)上是將事件key[i]匯報*/
                   
                    input_report_key(button_dev,keys[i],val);
                    /*匯報同步,也就是完成匯報工作*/
                   
                    input_sync(button_dev);
                    break;
                }   
            }
            /*返回值*/
           
            return IRQ_RETVAL(IRQ_HANDLED);
        }
        /*設(shè)備初始化函數(shù)*/
        static int __init tq2440_button_init(void)
        {
            int err,i = 0;
            /*主要要先申請設(shè)備,然后申請中斷*/
           
            /*申請輸入設(shè)備*/
            button_dev = input_allocate_device();
           
            if(NULL == button_dev)
            {
                printk(KERN_ERR "not enough memory\n");
                err = - ENOMEM;
            }
           
            /*設(shè)置相應(yīng)的事件處理類型,按鍵事件*/
            set_bit(EV_KEY,button_dev->evbit);
            /*申請中斷*/
            for(i = 0; i < NUMBERS_BUTTONS; ++ i)
            {
                /*申請中斷*/
                request_irq(irqs[i],button_interrupt,IRQ_TYPE_EDGE_BOTH,
                    DEVICE_NAME,NULL);
                /*設(shè)置案件的代碼值,即code值*/
                set_bit(keys[i],button_dev->keybit);
            }
               
            /*設(shè)備名,防止出現(xiàn)錯誤*/
            button_dev->name = input_name;   
           
            /*注冊輸入設(shè)備*/
            err = input_register_device(button_dev);
           
            if(err)
            {
                printk(KERN_ERR "failed to register device\n");
               
                goto err_free_dev;
            }
           
            printk("initialized\n");
            return 0;
        /*錯誤處理*/
        err_free_dev:
            /*注銷設(shè)備*/
            input_unregister_device(button_dev);
            return err;
        }
        /*設(shè)備退出函數(shù)*/
        static void __exit tq2440_button_exit(void)
        {
            int i = 0;
            input_unregister_device(button_dev);
            /*釋放中斷號*/
            for(i = 0; i < NUMBERS_BUTTONS; ++ i)
            {
                free_irq(irqs[i],NULL);
            }
        }
        /*加載和卸載*/
        module_init(tq2440_button_init);
        module_exit(tq2440_button_exit);
        /*LICENSE和作者信息*/
        MODULE_LICENSE("GPL");
        MODULE_AUTHOR("GP-<gp19861112@yahoo.com.cn>");
    測試代碼:
        #include<stdio.h>
        #include<stdlib.h>
        #include<sys/types.h>
        #include<sys/stat.h>
        #include<fcntl.h>
        #include<unistd.h>
        #include<linux/input.h>
        /*事件結(jié)構(gòu)體*/
        struct input_event buff;
        int main(int argc ,char **argv)
        {
            int fd;
            int count;
            /*打開設(shè)備文件*/
            fd = open("/dev/event0",O_RDWR);
            if(fd == -1)
            {
                printf("Open Failed!\n");
                exit(-1);
            }
            while(1)
            {
                /*讀操作*/
                if(count = read(fd,&buff,sizeof(struct input_event))!=0)
                {
                    printf("type: %d\tcode: %d\t value: %d\n",buff.type,buff.code,buff.value);
                }
            }
           
            close(fd);
            exit(0);
        }
  1.  
測試效果:
從效果上來看,代碼基本上實現(xiàn)了按鍵的識別,但是該驅(qū)動程序的問題是按鍵并不能實現(xiàn)消抖操作。
這一年中最后的一天,希望自己明年更美好,堅持寫博客,作總結(jié)。
關(guān)閉窗口

相關(guān)文章