輸入子系統——按鍵編寫
- 硬體平台:韋東山嵌入式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工具鍊
- 源碼倉庫:https://gitee.com/d_1254436976/Embedded-Linux-Phase-2
目錄
- 輸入子系統——按鍵編寫
-
- 一、前言
- 二、buttons.c檔案編寫
-
- 1、大緻程式架構
- 2、入口函數`buttons_init()`編寫
-
- 2.1 配置設定一個input_dev結構體
- 2.2 設定事件
- 2.3 `input_register_device()`注冊裝置
- 2.4 硬體相關操作
- 3、 中斷相關函數編寫
-
- 3.1 按鍵中斷函數`buttons_irq()`
- 3.2 定時器中斷函數`buttons_timer_function()`
- 4、出口函數`buttons_exit()`編寫
- 5、完整buttons.c檔案
- 三、建立連接配接流程梳理
- 四、編譯與燒寫分析
-
- 1、分析裝置号,檢視evdev_handler是否支援buttons_inputdev
- 2、測試
- 五、改進:添加重複類事件
一、前言
對于我們之前寫按鍵驅動,步驟如下:
- 确定主裝置号(可自己指定,也可以有系統指定)
- 構造
結構體,并把編寫的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()
編寫
buttons_init()
2.1 配置設定一個input_dev結構體
分析
drivers/input/keyboard/gpio_keys.c
檔案的入口函數可以得到下圖
對于其中的注冊平台裝置函數我們先不關心。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSP9cWT6lkeNh3Zq1EM4wmYwhGWhxGZzwEMW1mY1RzRapnTtxkb5ckYplTeMZTTINGMShUYfRHelRHLwEzX39GZhh2css2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xyayFWbyVGdhd3LcV2Zh1Wa9M3clN2byBXLzN3btg3Pn5GcuUzNxMjNxUTM5AjNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
配置設定
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()
注冊裝置
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()
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()
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()
編寫
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
檔案并加載後檢視其屬性:
- 加載前:
第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫 -
加載後:
可以知道其主裝置号為13,次裝置号為65,裝置名字為event1
這個時候檢視evdev.c的源碼分析如下圖:第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫 - 建立連接配接時,會調用
函數建立裝置節點,配置設定主次裝置号,其中主裝置号:13,次裝置号:從64開始遞加配置設定.connect()
根據
buttons.ko
的主次裝置号與名字,可知二者成功連接配接。
2、測試
- 第一次測試的時候出現如下問題:按下按鍵後亂碼
- 采用
第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫
檢視得到如下結果:hexdump
第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫 通過分析可知,測試結果是正确的,可是為什麼會出現亂碼呢?
通過老師介紹知道,有Qt程序在運作,在測試時需要殺掉,但是實際在我開發版上的檢視程序時發現并沒有Qt程式在運作。
-
第二次測試
有兩種測試方法:
①、采用
exec指令:exec 0</dev/tty1
②、采用第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫
此時需要按下回車按鍵後才可以看到cat /dev/tty1
ls
第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫
五、改進:添加重複類事件
在之前的測試中發現,長按代表l的按鍵,l隻輸出一次,需要添加重複類事件
測試可以發現:支援按鍵長按