天天看點

第二期驅動篇——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工具鍊
  • 源碼倉庫: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、測試
    • 五、改進:添加重複類事件

一、前言

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

  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

檔案的入口函數可以得到下圖

對于其中的注冊平台裝置函數我們先不關心。

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

配置設定

input_dev

結構體方法如下:

static struct input_dev *buttons_inputdev; 

buttons_inputdev = input_allocate_device();
if (!buttons_inputdev)
	return -ENOMEM;
           

2.2 設定事件

追蹤

input_dev

結構體的原型:可以知道需要設定為哪一類事件,這類事件的哪些事件。

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

參考

drivers/input/keyboard/gpio_keys.c

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

這裡我們直接采用

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

檔案的中斷服務函數可以知道:需要在中斷函數中進行下列兩個事件上報

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

追蹤

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");
           

三、建立連接配接流程梳理

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

四、編譯與燒寫分析

1、分析裝置号,檢視evdev_handler是否支援buttons_inputdev

編譯成

.ko

檔案并加載後檢視其屬性:

  • 加載前:
    第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫
  • 加載後:

    可以知道其主裝置号為13,次裝置号為65,裝置名字為event1

    第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫
    這個時候檢視evdev.c的源碼分析如下圖:
  • 建立連接配接時,會調用

    .connect()

    函數建立裝置節點,配置設定主次裝置号,其中主裝置号:13,次裝置号:從64開始遞加配置設定
第二期驅動篇——2.2 輸入子系統—按鍵編寫 輸入子系統——按鍵編寫

根據

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隻輸出一次,需要添加重複類事件

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

測試可以發現:支援按鍵長按

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

繼續閱讀