輸入子系統——按鍵編寫
- 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
- 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
- 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
- 開發環境:Linux 2.6.22.6 內核、arm-linux-gcc-3.4.5-glibc-2.3.6工具鏈
目錄
一、前言
對於我們之前寫按鍵驅動,步驟如下:
- 確定主設備號(可自己指定,也可以有系統指定)
- 構造
file_operation
結構體,並把編寫的read、write、open
等函數的地址保存在其中 register_chrdrv()
註冊驅動設備- 編寫入口與出口函數
而對於上節中分析的輸入子系統,編寫驅動時,步驟如下:
- 分配一個
input_dev
結構體 - 設置可觸發哪類事件
input_register_device
註冊設備- 進行硬件相關操作
可以看到,在利用輸入子系統編寫按鍵驅動時,並沒有進行相關read、write等函數編寫,至於爲什麼下面會進行分析,下面正式開始在輸入子系統的基礎上編寫按鍵驅動。
二、buttons.c文件編寫
- 參考內核程序爲:
drivers/input/keyboard/gpio_keys.c
- 實現功能:開發板上的四個按鍵,各自代表鍵盤的:L、S、ENTER、LEFTSHIF
1、大致程序框架
其中頭文件參考drivers/input/keyboard/gpio_keys.c
#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 <asm/io.h>
#include <linux/gpio_keys.h>
#include <asm/arch/regs-gpio.h>
/* 入口函數 */
static int buttons_init(void)
{
/* 1、分配一個input_dev結構體 */
/* 2、設置 */
/* 3、註冊: 會建立連接 */
/* 4、硬件相關操作 */
}
/* 出口函數 */
static void buttons_exit(void)
{
}
/* 修飾 */
module_init(buttons_init);
module_exit(buttons_exit);
/* 協議 */
MODULE_LICENSE("GPL");
2、入口函數buttons_init()
編寫
2.1 分配一個input_dev結構體
分析drivers/input/keyboard/gpio_keys.c
文件的入口函數可以得到下圖
對於其中的註冊平臺設備函數我們先不關心。
分配input_dev
結構體方法如下:
static struct input_dev *buttons_inputdev;
buttons_inputdev = input_allocate_device();
if (!buttons_inputdev)
return -ENOMEM;
2.2 設置事件
追蹤input_dev
結構體的原型:可以知道需要設置爲哪一類事件,這類事件的哪些事件。
參考drivers/input/keyboard/gpio_keys.c
這裏我們直接採用set_bit()
函數
/* 2、設置 */
/* 2.1 設置可以產生按鍵類事件 */
set_bit(EV_KEY, buttons_inputdev->evbit);
/* 2.2 設置這類操作裏產生哪些事件:L, S,ENTER,SHIFT */
set_bit(KEY_L, buttons_inputdev->keybit);
set_bit(KEY_S, buttons_inputdev->keybit);
set_bit(KEY_ENTER, buttons_inputdev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_inputdev->keybit);
2.3 input_register_device()
註冊設備
/* 3、註冊: 會建立連接 */
error = input_register_device(buttons_inputdev);
if (error) {
printk(KERN_ERR "Unable to register buttons input device\n");
goto fail;
}
2.4 硬件相關操作
對於硬件操作,無論是自己編寫驅動還是使用輸入子系統,硬件操作都是一致。
/* 按鍵信息 */
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11,"S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19,"S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer; //定義一個定時器
/* 4、硬件相關操作 */
/* 4.1 初始化定時器相關操作 */
init_timer(&buttons_timer); //定時器初始化
buttons_timer.function = buttons_timer_function; //定時器處理函數
add_timer(&buttons_timer);
/* 4.2 註冊中斷 */
for (i = 0; i < 4; i++) {
error = request_irq(pins_desc[i].irq, button_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
if (error) {
printk(KERN_ERR "buttons: unable to claim irq error %d\n", error);
goto fail;
}
}
return error;
fail:
for (i = i - 1; i >= 0; i--)
free_irq(pins_desc[i].irq, &pins_desc[i]));
input_free_device(buttons_inputdev);
return error;
}
3、 中斷相關函數編寫
分析drivers/input/keyboard/gpio_keys.c
文件的中斷服務函數可以知道:需要在中斷函數中進行下列兩個事件上報
追蹤input_sync()
源碼可知
- input_sync()同步用於告訴子系統報告結束
static inline void input_sync(struct input_dev *dev)
{
input_event(dev, EV_SYN, SYN_REPORT, 0);
}
3.1 按鍵中斷函數buttons_irq()
/* 設置按鍵中斷函數 */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms後啓動定時器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies + HZ / 100);
return IRQ_RETVAL(IRQ_HANDLED);
}
3.2 定時器中斷函數buttons_timer_function()
/* 定時器中斷服務函數:防抖動
* 採用輸入子系統,當有數據時則調用input_event
*/
static void buttons_timer_function(unsigned long data)
{
struct pin_desc *pindesc = irq_pd;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin); //讀取IO口電平
/* 最後一個參數:0-鬆開,1-按下 */
if(pinval){
/* 鬆開上報 並 上報一個event事件與同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_inputdev);
}else{
/* 按下上報 並 上報一個event事件與同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_inputdev);
}
}
4、出口函數buttons_exit()
編寫
/* 出口函數 */
static void buttons_exit(void)
{
int i;
/* 釋放中斷 */
for (i = 0; i < 4; i++) {
free_irq(pins_desc[i].irq, &pins_desc[i]));
}
/* 從系統的定時器管理隊列中摘除一個定時器對象 */
del_timer(&buttons_timer);
/* 註銷輸入設備結構體 */
input_unregister_device(buttons_inputdev);
/* 釋放分配的空間 */
input_free_device(buttons_inputdev);
}
5、完整buttons.c文件
#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 <asm/io.h>
#include <linux/gpio_keys.h>
#include <asm/arch/regs-gpio.h>
/* 按鍵信息 */
struct pin_desc{
int irq;
char *name;
unsigned int pin;
unsigned int key_val;
};
/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
{IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
{IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
{IRQ_EINT11,"S4", S3C2410_GPG3, KEY_ENTER},
{IRQ_EINT19,"S5", S3C2410_GPG11, KEY_LEFTSHIFT},
};
static struct input_dev *buttons_inputdev;
static struct pin_desc *irq_pd;
static struct timer_list buttons_timer;
/* 設置按鍵中斷函數 */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms後啓動定時器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies + HZ / 100);
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定時器中斷服務函數:防抖動
* 採用輸入子系統,當有數據時則調用input_event
*/
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
if (!pindesc)
return;
pinval = s3c2410_gpio_getpin(pindesc->pin); //讀取IO口電平
/* 最後一個參數:0-鬆開,1-按下 */
if(pinval) {
/* 鬆開上報 並 上報一個event事件與同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 0);
input_sync(buttons_inputdev);
}else {
/* 按下上報 並 上報一個event事件與同步事件 */
input_event(buttons_inputdev, EV_KEY, pindesc->key_val, 1);
input_sync(buttons_inputdev);
}
}
/* 入口函數 */
static int buttons_init(void)
{
int error, i;
i = 0;
/* 1、分配一個input_dev結構體 */
buttons_inputdev = input_allocate_device();
if (!buttons_inputdev)
return -ENOMEM;
/* 2、設置 */
/* 2.1 設置可以產生按鍵類事件 */
set_bit(EV_KEY, buttons_inputdev->evbit);
/* 2.2 設置這類操作裏產生哪些事件:L, S,ENTER,SHIFT */
set_bit(KEY_L, buttons_inputdev->keybit);
set_bit(KEY_S, buttons_inputdev->keybit);
set_bit(KEY_ENTER, buttons_inputdev->keybit);
set_bit(KEY_LEFTSHIFT, buttons_inputdev->keybit);
/* 3、註冊: 會建立連接 */
error = input_register_device(buttons_inputdev);
if (error) {
printk(KERN_ERR "Unable to register gpio-keys input device\n");
goto fail;
}
/* 4、硬件相關操作 */
/* 4.1 初始化定時器相關操作 */
init_timer(&buttons_timer); //定時器初始化
buttons_timer.function = buttons_timer_function; //定時器處理函數
add_timer(&buttons_timer);
/* 4.2 註冊中斷 */
for (i = 0; i < 4; i++) {
error = request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
if (error) {
printk(KERN_ERR "buttons: unable to claim irq error %d\n", error);
goto fail;
}
}
return error;
fail:
for (i = i - 1; i >= 0; i--)
free_irq(pins_desc[i].irq, &pins_desc[i]);
input_free_device(buttons_inputdev);
return error;
}
/* 出口函數 */
static void buttons_exit(void)
{
int i;
/* 釋放中斷 */
for (i = 0; i < 4; i++) {
free_irq(pins_desc[i].irq, &pins_desc[i]);
}
/* 從系統的定時器管理隊列中摘除一個定時器對象 */
del_timer(&buttons_timer);
/* 註銷輸入設備結構體 */
input_unregister_device(buttons_inputdev);
/* 釋放分配的空間 */
input_free_device(buttons_inputdev);
}
/* 修飾 */
module_init(buttons_init);
module_exit(buttons_exit);
/* 協議 */
MODULE_LICENSE("GPL");
三、建立連接流程梳理
四、編譯與燒寫分析
1、分析設備號,查看evdev_handler是否支持buttons_inputdev
編譯成.ko
文件並加載後查看其屬性:
-
加載前:
-
加載後:
可以知道其主設備號爲13,次設備號爲65,設備名字爲event1
這個時候查看evdev.c的源碼分析如下圖: -
建立連接時,會調用
.connect()
函數創建設備節點,分配主次設備號,其中主設備號:13,次設備號:從64開始遞加分配
根據buttons.ko
的主次設備號與名字,可知二者成功連接。
2、測試
-
第一次測試的時候出現如下問題:按下按鍵後亂碼
-
採用hexdump
查看得到如下結果:
通過分析可知,測試結果是正確的,可是爲什麼會出現亂碼呢?
通過老師介紹知道,有Qt進程在運作,在測試時需要殺掉,但是實際在我開發版上的查看進程時發現並沒有Qt程序在運行。 -
第二次測試
有兩種測試方法:
①、採用exec命令:exec 0</dev/tty1
②、採用cat /dev/tty1
此時需要按下回車按鍵後纔可以看到ls
五、改進:添加重複類事件
在之前的測試中發現,長按代表l的按鍵,l只輸出一次,需要添加重複類事件
測試可以發現:支持按鍵長按