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