1. 程式人生 > >GPIO-KEY的實現原理及使用方法

GPIO-KEY的實現原理及使用方法

本文將以imx6q的板子和相應BSP程式碼來詳細描述在linux下, 使用GPIO當做按鍵的實現原理及使用方法。

Linux 核心下的 drivers/input/keyboard/gpio_keys.c實現了一個體繫結構無關的GPIO按鍵驅動,使用此按鍵驅動,只需在相應的板級支援包中(imx6q的是board-mx6q-sabresd.c)定義相關的資料即可。驅動的實現非常簡單,但是較適合於實現獨立式按鍵驅動。

gpio-keys是基於input架構實現的一個通用GPIO按鍵驅動。該驅動基於platform_driver架構,實現了驅動和裝置分離,符合Linux裝置驅動模型的思想。工程中的按鍵驅動我們一般都會基於gpio-keys來寫,所以我們有必要對gpio_keys進行分析。

一.   GPIO-KEY的實現原理

1.      定義GPIO按鍵:

首先定義一個gpio_keys_button的陣列, 該型別定義了一個具體的GPIO按鍵資訊

arch\arm\mach-mx6\board-mx6q-sabresd.c:

static struct gpio_keys_buttonsabresd_buttons[] = {

GPIO_BUTTON(SABRESD_VOLUME_UP,KEY_VOLUMEUP, 1, "volume-up", 0, 1),

GPIO_BUTTON(SABRESD_VOLUME_DN,KEY_POWER, 1, "volume-down", 1, 1),

/*addby aaron 2015.6.29*/

#ifdef FEATURE_YUTONG_ICARD2

GPIO_BUTTON(SABRESD_KEY_MENU,248, 1, "menu-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_UP,249, 1, "up-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_DOWN,250, 1, "down-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_CONFIRM,251, 1, "confirm-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_ICCARD_DET,247, 1, "iccard-det", 0, 3),

#endif

/*endby aaron*/

};

struct gpio_keys_button型別如下, 這是對按鍵的描述:

include\linux\gpio_keys.h:

struct gpio_keys_button {

/*Configuration parameters */

unsignedint code;   /* input event code (KEY_*,SW_*) */

intgpio;

intactive_low;

constchar *desc;

unsignedint type;    /* input event type (EV_KEY,EV_SW, EV_ABS) */

int wakeup;               /* configure the button as awake-up source */

intdebounce_interval;    /* debounce ticksinterval in msecs */

boolcan_disable;

intvalue;           /* axis value for EV_ABS*/

};

巨集 GPIO_BUTTON()就是初始化每個gpio_key的描述:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

#define GPIO_BUTTON(gpio_num, ev_code,act_low, descr, wake, debounce)      \

{                                                                           \

.gpio                   = gpio_num,                                 \

.type                  = EV_KEY,                            \

.code                  = ev_code,                                    \

.active_low      = act_low,                                     \

.desc                  = "btn " descr,                             \

.wakeup            = wake,                                         \

.debounce_interval= debounce,                                 \

}

接著定義一個gpio_keys_platform_data變數:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

staticstruct gpio_keys_platform_data new_sabresd_button_data = {

         .buttons   = new_sabresd_buttons,

         .nbuttons = ARRAY_SIZE(new_sabresd_buttons),

};

struct gpio_keys_platform_data的定義也在gpio_keys.h中:

include\linux\gpio_keys.h:

structgpio_keys_platform_data {

         struct gpio_keys_button *buttons;

         int nbuttons;

         unsigned int poll_interval;       /* polling interval in msecs -

                                                  for polling driver only */

         unsigned int rep:1;           /* enable input subsystem auto repeat*/

         int (*enable)(struct device *dev);

         void (*disable)(struct device *dev);

         const char *name;            /* input device name */

};

2.      把 1 中定義的new_sabresd_button_data 註冊到系統中去:

我們把gpio_key當成一個platform_device裝置, 所以要先定義一個platform_device裝置:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

staticstruct platform_device sabresd_button_device = {

         .name                ="gpio-keys", /*名字非常關鍵, 找驅動就靠它來匹配了*/

         .id              =-1,

         .num_resources  = 0,

};

然後把我們定義的gpio_key 跟這個platform_device裝置繫結在一起:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

platform_device_add_data(&sabresd_button_device,

                    &new_sabresd_button_data,

                             sizeof(new_sabresd_button_data));

最後註冊到系統中去:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

platform_device_register(&sabresd_button_device);

其中, platform_device_add_data()函式如下:

drivers/base/platform.c:

intplatform_device_add_data(struct platform_device *pdev, const void *data,

                                 size_t size)

{

         void *d = NULL;

         if (data) {

                   d = kmemdup(data, size,GFP_KERNEL); /*分配memory,並把data的內容拷貝進去*/

                   if (!d)

                            return -ENOMEM;

         }

         kfree(pdev->dev.platform_data);

         pdev->dev.platform_data = d;  /*把gpio_key繫結到這個platform_device上去*/

         return 0;

}

3.      匹配驅動:

當用platform_device_register()把 gpio_key的platform_device的新增到系統中去後, 系統會去匹配是否有合適的驅動, 這裡對應的驅動就是:gpio_keys.c, 很顯然這是一個platform_driver的驅動:

drivers/input/keyboard/gpio_keys.c:

static struct platform_drivergpio_keys_device_driver = {

.probe                = gpio_keys_probe,

.remove            = __devexit_p(gpio_keys_remove),

.driver                = {

           .name       = "gpio-keys",   /*發現沒有, 名字跟裝置的名字一模一樣*/

           .owner     = THIS_MODULE,

#ifdef CONFIG_PM

           .pm  = &gpio_keys_pm_ops,

#endif

}

};

系統能找到這個驅動, 主要就是因為他們的名字都是: gpio-keys, 這個很關鍵。 接下來我們就來分析一下這個驅動, 首先找到裝置後, 會呼叫probe函式, 這裡就是gpio_keys_probe();

在gpio_keys_probe()函式中, 會註冊一個input裝置, 並建立相應的裝置檔案。

二. GPIO_KEY使用

使用方式比較簡單,和普通的檔案操作一樣, 先開啟裝置檔案, 再讀檔案獲取鍵值即可:

1.      開啟裝置檔案,

我的裝置上gpio_key對應的裝置檔案是/dev/input/event0, 不同的平臺裝置檔案可能會有差異, 如果不清楚對應的裝置檔案, 可以用下面的命令來檢視:

開啟裝置程式碼如下:

/*1.key device*/

fd_key= open(KEY_DEVICE_FILE, O_RDONLY);

if(fd_key< 0) {

           LOGE("can'topen key device file");

           returnfd_key;

}

2.      獲取按鍵值及按鍵型別:

         struct input_event key_evt;

monitor_key:

         ret = read(fd_key, (unsigned char*)&key_evt, sizeof(struct input_event)); /*阻塞型讀函式*/

         if(ret < 0) {

                   LOGE("read key eventfailed :%d", ret);

         }

         /*filter unknown key*/

         else if(key_evt.code != 248&&     /*KEY_MENU*/ 

                             key_evt.code != 249 &&    /*KEY_UP*/

                             key_evt.code != 250 &&    /*KEY_DOWN*/

                             key_evt.code != 251 &&    /*KEY_CONFIRM*/

                             key_evt.code != 247){     /*ICCARD detect pin*/

                   LOGE("unknown key code:%d", key_evt.code);

                   goto monitor_key;

         }

         else {  /*valid key*/

           /*

            * key_val[0..7] = key code

            * key_val[8] = key value: 0 - released, 1 - pressed.

            */

                   key_val = ((int8_t)key_evt.value<< 8) | ((uint8_t)key_evt.code);

                   //LOGE("get key eventcode:%d, value:%d, type:%d, %d", key_evt.code, key_evt.value,key_evt.type, key_val);

         }

         return key_val;

實際上就是呼叫一個阻塞型的讀函式, 所以這個函式儘量放在單獨的一個執行緒中處理, 鍵值就是在前面板級支援包中定義並註冊的值。