13.Linux鍵盤按鍵驅動 (詳解)


上一節分析輸入子系統內的intput_handler軟件處理部分后,接下來我們開始寫input_dev驅動

本節目標:

       實現鍵盤驅動,讓開發板的4個按鍵代表鍵盤中的L、S、空格鍵、回車鍵

 


 

1.先來介紹以下幾個結構體使用和函數,下面代碼中會用到

1)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)絕對坐標事件,比如搖桿、觸摸屏感應
       //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)

       unsigned long relbit[NBITS(REL_MAX)];    //存放支持的相對坐標值
       unsigned long absbit[NBITS(ABS_MAX)];   //存放支持的絕對坐標值
       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)];     //存放支持的開關功能

 ... ...

2)函數如下:

struct input_dev *input_allocate_device(void);  //向內核中申請一個input_dev設備,然后返回這個設備
  
input_unregister_device(struct input_dev *dev);   //卸載/sys/class/input目錄下的input_dev這個類設備, 一般在驅動出口函數寫
 
input_free_device(struct input_dev *dev);   //釋放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”
*/

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);  //上報事件
 // input_dev *dev :要上報哪個input_dev驅動設備的事件
 // type : 要上報哪類事件, 比如按鍵事件,則填入: EV_KEY
 // code: 對應的事件里支持的哪個變量,比如按下按鍵L則填入: KEY_L
 //value:對應的變量里的數值,比如松開按鍵則填入1,松開按鍵則填入0
input_sync(struct input_dev *dev); //同步事件通知

為什么使用了input_event()上報事件函數,就要使用這個函數?

因為input_event()函數只是個事件函數,所以需要這個input_sync()同步事件函數來通知系統,然后系統才會知道

input_sync()代碼如下:

static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0); //就是上報同步事件,告訴內核:input_event()事件執行完畢
}

2.然后開始寫代碼

1)向內核申請input_dev結構體

2)設置input_dev的成員

3)注冊input_dev 驅動設備

4)初始化定時器和中斷

5)寫中斷服務函數

6)寫定時器超時函數

7)在出口函數中 釋放中斷函數,刪除定時器,卸載釋放驅動

具體代碼如下(都加了注釋):

#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/gpio_keys.h>
#include <asm/gpio.h>

 
struct input_dev *buttons_dev;            //  定義一個input_dev結構體  
static struct ping_desc *buttons_id;          //保存dev_id,在定時器中用
static struct timer_list buttons_timer;    //定時器結構體  

struct  ping_desc{

       unsigned  char  *name;          //中斷設備名稱
       int            pin_irq;          //按鍵的外部中斷標志位
       unsigned  int    pin;                //引腳
       unsigned int  irq_ctl;           //觸發中斷狀態:   IRQ_TYPE_EDGE_BOTH
       unsigned  int    button;         //dev_id,對應鍵盤的 L ,  S,  空格,  enter      
};

        // KEY1 -> L
        // KEY2 -> S
        // KEY3 -> 空格
        // KEY4 -> enter
static  struct ping_desc   buttons_desc[5]=
{
       {"s1", IRQ_EINT0,   S3C2410_GPF0,  IRQ_TYPE_EDGE_BOTH,KEY_L},
       {"s2", IRQ_EINT2,   S3C2410_GPF2,  IRQ_TYPE_EDGE_BOTH,KEY_S},
       {"s3", IRQ_EINT11, S3C2410_GPG3 , IRQ_TYPE_EDGE_BOTH,KEY_SPACE},
       {"s4", IRQ_EINT19, S3C2410_GPG11,IRQ_TYPE_EDGE_BOTH,KEY_ENTER},
};

 

/*5. 寫中斷服務函數*/
static irqreturn_t  buttons_irq (int irq, void *dev_id)       //中斷服務函數
{
       buttons_id=(struct ping_desc *)dev_id;             //保存當前的dev_id
       mod_timer(&buttons_timer, jiffies+HZ/100 );   //更新定時器值 10ms 
       return 0;
}

 

/*6.寫定時器超時函數*/
void buttons_timer_function(unsigned long i)
{
   int val;
   val=s3c2410_gpio_getpin(buttons_id->pin);             //獲取是什么電平 
  if(val)         //高電平,松開
       {
         /*上報事件*/
         input_event(buttons_dev,EV_KEY,buttons_id->button, 0);  //上報EV_KEY類型,button按鍵,0(沒按下)
         input_sync(buttons_dev);         // 上傳同步事件,告訴系統有事件出現                       
      }

  else      //低電平,按下
      {
         /*上報事件*/
         input_event(buttons_dev, EV_KEY, buttons_id->button, 1);  //上報EV_KEY類型,button按鍵,1(按下)
         input_sync(buttons_dev);       // 上傳同步事件,告訴系統有事件出現
     }
}


static int buttons_init(void)   //入口函數
{
       int i;      
       buttons_dev=input_allocate_device();  //1.向內核 申請input_dev結構體
       /*2.設置input_dev ,  */
       set_bit(EV_KEY,buttons_dev->evbit);       //支持鍵盤事件
       set_bit(EV_REP,buttons_dev->evbit);       //支持鍵盤重復按事件
    
       set_bit(KEY_L,buttons_dev->keybit);                  //支持按鍵 L
       set_bit(KEY_S,buttons_dev->keybit);                //支持按鍵 S
       set_bit(KEY_SPACE,buttons_dev->keybit);      //支持按鍵 空格
       set_bit(KEY_ENTER,buttons_dev->keybit);     //支持按鍵 enter

       /*3.注冊input_dev */
       input_register_device(buttons_dev);

      
       /*4. 初始化硬件:初始化定時器和中斷*/      
       // KEY1 -> L
       // KEY2 -> S
       // KEY3 -> 空格
       // KEY4 -> enter
       init_timer(&buttons_timer);
       buttons_timer.function=buttons_timer_function;
       add_timer(&buttons_timer);

       for(i=0;i<4;i++)
       request_irq(buttons_desc[i].pin_irq, buttons_irq, buttons_desc[i].irq_ctl, buttons_desc[i].name, &buttons_desc[i]);

       return 0;
}


static int buttons_exit(void)  //出口函數
{
       /*7.釋放中斷函數,刪除定時器,卸載釋放驅動*/
       int i;
       for(i=0;i<4;i++)
              free_irq(buttons_desc[i].pin_irq,&buttons_desc[i]);    //釋放中斷函數

       del_timer(&buttons_timer);   //刪除定時器

       input_unregister_device(buttons_dev);     //卸載類下的驅動設備
       input_free_device(buttons_dev);                //釋放驅動結構體
       return 0; 
}

module_init(buttons_init);
module_exit(buttons_exit);
MODULE_LICENSE("GPL v2");

 

3.測試運行:

掛載鍵盤驅動后, 如下圖,可以通過  ls -l /dev/event*   命令查看已掛載的設備節點:

 

上一節輸入子系統里分析到:輸入子系統的主設備號為13,其中event驅動本身的此設備號是從64開始的,如上圖,內核啟動時,會加載自帶觸摸屏驅動,所以我們的鍵盤驅動的次設備號=64+1

3.1測試運行有兩種,一種是直接打開/dev/tyy1,第二種是使用exec命令

(exec命令詳解入口地址: http://www.cnblogs.com/lifexy/p/7553228.html)

方法1:

cat /dev/tty1     //tty1:LCD終端,就會通過tty_io.c來訪問鍵盤驅動,然后打印在tty1終端上

方法2:

exec 0</dev/tty1    //將/dev/tty1掛載到-sh進程描述符0下,此時的鍵盤驅動就會直接打印在tty1終端上

 

3.2 調試:

若測試不成功,板子又在QT下進行的:

1)可以使用vi命令,在記事本中按按鍵試

2)或者刪除/etc/init.d/rcS 里面有關QT自啟動的命令,然后重啟

 

若板子沒在QT下進行,也無法測試成功:

1)可以使用hexdump命令來調試代碼

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

 

 

接下來開始學習:

 

14.linux-platform機制實現驅動層分離(詳解)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM