第二期驅動篇——2.2 輸入子系統—按鍵編寫

輸入子系統——按鍵編寫

  • 硬件平臺:韋東山嵌入式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工具鏈


一、前言

對於我們之前寫按鍵驅動,步驟如下:

  1. 確定主設備號(可自己指定,也可以有系統指定)
  2. 構造file_operation結構體,並把編寫的read、write、open函數的地址保存在其中
  3. register_chrdrv()註冊驅動設備
  4. 編寫入口與出口函數

而對於上節中分析的輸入子系統,編寫驅動時,步驟如下:

  • 分配一個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只輸出一次,需要添加重複類事件
在這裏插入圖片描述
測試可以發現:支持按鍵長按
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章