【嵌入式Linux驅動開發】二十、一文快速上手 Linux INPUT 子系統,按鍵驅動的第三種姿勢

  交友亦有善惡分,竹蘭相投是真君。
  桃投李抱各進益,蘭金之友換真心。
  最忌賊友與暱友,大禍臨頭各自奔。
  諍友知己從來少,人生百歲逢幾人。


一、INPUT子系統

1.1、input子系統簡介

  按鍵、鼠標、鍵盤、觸摸屏等都屬於輸入(input)設備, Linux 內核爲此專門做了一個叫做 input子系統的框架來處理輸入事件。輸入設備本質上還是字符設備,只是在此基礎上套上了 input 框架,用戶只需要負責上報輸入事件,比如按鍵值、座標等信息, input 核心層負責處理這些事件。Input子系統結構圖如圖所示

在這裏插入圖片描述

  我們編寫驅動程序的時候只需要關注中間的驅動層、核心層和事件層。這三個層的分工如下:

  • 驅動層:輸入設備的具體驅動程序,比如按鍵驅動程序,向內核層報告輸入內容。
  • 核心層:承上啓下,爲驅動層提供輸入設備註冊和操作接口。通知事件層對輸入事件進行處理。
  • 事件層:主要和用戶空間進行交互。

  input 子系統的所有設備主設備號都爲 13,我們在使用 input 子系統處理輸入設備的時候就不需要去註冊字符設備了,我們只需要向系統註冊一個 input_device 即可。

1.2、input子系統編寫流程

  • ①、註冊 input_dev
  • ②、上報輸入事件

1.2.1、註冊 input_dev

  在使用 input 子系統的時候我們只需要註冊一個 input 設備即可,input_dev 結構體表示 input 設備。

1.2.1.1、input_dev 結構體介紹

input_dev結構體定義在 include/linux/input.h 文件中:

struct input_dev {
	const char *name;
	const char *phys;
	const char *uniq;
	struct input_id id;
	......
	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
	......
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; 	/* 事件類型的位圖 */
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; 	/* 按鍵值的位圖 */
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; 	/* 相對座標的位圖 */
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; 	/* 絕對座標的位圖 */
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; 	/* 雜項事件的位圖 */
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; 	/* LED相關的位圖 */
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];	/* sound有關的位圖 */
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; 	/* 壓力反饋的位圖 */
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; 	/* 開關狀態的位圖 */
	......
	bool devres_managed;
};

  evbit 表示輸入事件類型,可選的事件類型定義在 include/uapi/linux/input.h 文件中,事件類型如下:

#define EV_SYN 0x00 /* 同步事件 */
#define EV_KEY 0x01 /* 按鍵事件 */
#define EV_REL 0x02 /* 相對座標事件 */
#define EV_ABS 0x03 /* 絕對座標事件 */
#define EV_MSC 0x04 /* 雜項(其他)事件 */
#define EV_SW 0x05  /* 開關事件 */
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* sound(聲音) */
#define EV_REP 0x14 /* 重複事件 */
#define EV_FF 0x15  /* 壓力事件 */
#define EV_PWR 0x16 /* 電源事件 */
#define EV_FF_STATUS 0x17 /* 壓力狀態事件 */

  本節我們要使用到按鍵,那麼就需要註冊 EV_KEY 事件,如果要使用連按功能的話還需要註冊 EV_REP 事件。

  回到input_dev 結構體定義,evbit、 keybit、 relbit 等等都是存放不同事件對應的值,比如本節要使用按鍵事件,因此要用到 keybitkeybit 就是按鍵事件使用的位圖, Linux 內核定義了很多按鍵值,這些按鍵值定義在 include/uapi/linux/input.h 文件中,按鍵值如下:

#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
......
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7

  我們可以將開發板上的按鍵值設置爲上述定義中的任意一個,本節我們將設置爲KEY_0。

  注意,上述我們僅僅是介紹了input設備要註冊的input_dev 結構體的相關內容,這對後面input_dev 初始化有很大幫助,接下來我們正式介紹如何註冊input_dev 結構體。

1.2.1.2、申請 input_dev 結構體

  在編寫 input 設備驅動的時候我們需要先申請一個 input_dev 結構體變量,使用
input_allocate_device 函數來申請一個 input_dev,此函數原型如下所示:

//返回值: 申請到的 input_dev。
struct input_dev *input_allocate_device(void)

  如果要註銷的 input 設備的話需要使用 input_free_device 函數來釋放掉前面申請到的input_devinput_free_device 函數原型如下:

//dev:需要釋放的 input_dev。
void input_free_device(struct input_dev *dev)

1.2.1.3、初始化 input_dev 結構體

  申請好一個 input_dev 以後就需要初始化這個 input_dev,需要初始化的內容主要爲事件類型(evbit)和事件值(keybit)這兩種。 input_dev 初始化完成以後就需要向 Linux 內核註冊 input_dev 了,需要用到 input_register_device 函數,此函數原型如下:

//dev:要註冊的 input_dev 。
//返回值: 0, input_dev 註冊成功;負值, input_dev 註冊失敗。
int input_register_device(struct input_dev *dev)

  同樣的,註銷 input 驅動的時候也需要使用 input_unregister_device 函數來註銷掉前面註冊的 input_dev, input_unregister_device 函數原型如下:

//dev:要註銷的 input_dev
void input_unregister_device(struct input_dev *dev)

1.2.1.4、總結 input_dev 註冊過程

  • ①、使用 input_allocate_device 函數申請一個 input_dev。
  • ②、初始化 input_dev 的事件類型以及事件值。
  • ③、使用 input_register_device 函數向 Linux 系統註冊前面初始化好的 input_dev。
  • ④、卸載 input驅動的時候需要先使用 input_unregister_device函數註銷掉註冊的 input_dev,然後使用 input_free_device 函數釋放掉前面申請的 input_dev。

input_dev 註冊流程參考代碼

struct input_dev *inputdev; /* input 結構體變量 */

/* 驅動入口函數 */
static int __init xxx_init(void)
{
	......
	inputdev = input_allocate_device(); /* 申請 input_dev */
	inputdev->name = "test_inputdev"; 	/* 設置 input_dev 名字 */
	
	/*********第一種設置事件和事件值的方法***********/
	__set_bit(EV_KEY, inputdev->evbit); /* 設置產生按鍵事件 */
	__set_bit(EV_REP, inputdev->evbit); /* 重複事件 */
	__set_bit(KEY_0,  inputdev->keybit); /*設置產生哪些按鍵值 */
	/************************************************/
	
	/*********第二種設置事件和事件值的方法***********/
	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
	/************************************************/
	
	/*********第三種設置事件和事件值的方法***********/
	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
	/************************************************/
	
	/* 註冊 input_dev */
	input_register_device(inputdev);
	
	return 0;
}

/* 驅動出口函數 */
static void __exit xxx_exit(void)
{
	input_unregister_device(inputdev); /* 註銷 input_dev */
	input_free_device(inputdev); /* 刪除 input_dev */
}

1.2.2、上報輸入事件

  向 Linux 內核註冊好 input_dev 以後還不能高枕無憂的使用 input 設備, input 設備都是具有輸入功能的,但是具體是什麼樣的輸入值 Linux 內核是不知道的,我們需要獲取到具體的輸入值,或者說是輸入事件,然後將輸入事件上報給 Linux 內核。
  比如按鍵,我們需要在按鍵中斷處理函數,或者消抖定時器中斷函數中將按鍵值上報給 Linux 內核,這樣 Linux 內核才能獲取到正確的輸入值。

  不同的事件,其上報事件的 API 函數不同,來看一下一些常用的事件上報 API 函數。

1.2.2.1、事件上報API函數

  首先是 input_event 函數,此函數用於上報指定的事件以及對應的值,函數原型如下:

//dev:需要上報的 input_dev。
//type: 上報的事件類型,比如 EV_KEY。
//code: 事件碼,也就是我們註冊的按鍵值,比如 KEY_0、 KEY_1 等等。
//value:事件值,比如 1 表示按鍵按下, 0 表示按鍵鬆開。
void input_event(struct input_dev *dev,
				unsigned int type,
				unsigned int code,
				int value)

  input_event 函數可以上報所有的事件類型和事件值, Linux 內核也提供了其他的針對具體事件的上報函數,這些函數其實都用到了 input_event 函數。比如上報按鍵所使用的input_report_key 函數,此函數內容如下:

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_KEY, code, !!value);
}

  可以看出, input_report_key 函數的本質就是 input_event 函數,如果要上報按鍵事件的話還是建議使用input_report_key 函數。同樣的還有一些其他的事件上報函數,這些函數如下所示:

void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

  當我們上報事件以後還需要使用 input_sync 函數來告訴 Linux 內核 input 子系統上報結束,input_sync 函數本質是上報一個同步事件,此函數原型如下所示:

//dev:需要上報同步事件的 input_dev
void input_sync(struct input_dev *dev)

事件上報參考代碼

/* 用於按鍵消抖的定時器服務函數 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	
	value = gpio_get_value(keydesc->gpio); /* 讀取 IO 值 */
	if(value == 0){ /* 按下按鍵 */
		/* 上報按鍵值 */
		input_report_key(inputdev, KEY_0, 1); /* 最後一個參數 1, 按下 */
		input_sync(inputdev); /* 同步事件 */
	} else { /* 按鍵鬆開 */
		input_report_key(inputdev, KEY_0, 0); /* 最後一個參數 0, 鬆開 */
		input_sync(inputdev); /* 同步事件 */
	}
}

1.2.2.2、input_event 結構體

  Linux 內核使用 input_event 這個結構體來表示所有的輸入事件, input_envent 結構體定義在include/uapi/linux/input.h 文件中,結構體內容如下:

struct input_event {
	struct timeval time; //時間
	__u16 type; //事件類型
	__u16 code; //事件碼
	__s32 value; //值
};

  依次來看一下 input_event 結構體中的各個成員變量:

  • time - 時間

  也就是此事件發生的時間,爲 timeval 結構體類型, timeval 結構體定義如下:

typedef long __kernel_long_t;
typedef __kernel_long_t __kernel_time_t;
typedef __kernel_long_t __kernel_suseconds_t;

struct timeval {
	__kernel_time_t tv_sec; /* 秒 */
	__kernel_suseconds_t tv_usec; /* 微秒 */
};

注意,tv_sectv_usec 這兩個成員變量都爲 long 類型,也就是 32位,這個一定要記住,後面我們分析 event 事件上報數據的時候要用到。

  • type - 事件類型

  比如 EV_KEY,表示此次事件爲按鍵事件,此成員變量爲 16 位。

  • code - 事件碼

  比如在 EV_KEY 事件中 code 就表示具體的按鍵碼,如: KEY_0、 KEY_1 等等這些按鍵。此成員變量爲 16 位。

  • value - 值

  比如 EV_KEY 事件中 value 就是按鍵值,表示按鍵有沒有被按下,如果爲 1 的話說明按鍵按下,如果爲 0 的話說明按鍵沒有被按下或者按鍵鬆開了。

  input_envent 這個結構體非常重要,因爲所有的輸入設備最終都是按照 input_event 結構體呈現給用戶的,用戶應用程序可以通過 input_event 來獲取到具體的輸入事件或相關的值,比如按鍵值等。理論知識便聊到這裏,接下來上代碼!

二、編寫程序

2.1、驅動程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: keyinput.c
作者	  	: 左忠凱
版本	   	: V1.0
描述	   	: Linux按鍵input子系統實驗
其他	   	: 無
論壇 	   	: www.openedv.com
日誌	   	: 初版V1.0 2019/8/21 左忠凱創建
***************************************************************/
#define KEYINPUT_CNT		1			/* 設備號個數 	*/
#define KEYINPUT_NAME		"keyinput"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按鍵值 	*/
#define INVAKEY				0XFF		/* 無效的按鍵值 */
#define KEY_NUM				1			/* 按鍵數量 	*/

/* 中斷IO描述結構體 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中斷號     */
	unsigned char value;					/* 按鍵對應的鍵值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中斷服務函數 */
};

/* keyinput設備結構體 */
struct keyinput_dev{
	dev_t devid;			/* 設備號 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 類 		*/
	struct device *device;	/* 設備 	 */
	struct device_node	*nd; /* 設備節點 */
	struct timer_list timer;/* 定義一個定時器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按鍵描述數組 */
	unsigned char curkeynum;				/* 當前的按鍵號 */
	struct input_dev *inputdev;		/* input結構體 */
};

struct keyinput_dev keyinputdev;	/* key input設備 */

/* @description		: 中斷服務函數,開啓定時器,延時10ms,
 *				  	  定時器用於按鍵消抖。
 * @param - irq 	: 中斷號 
 * @param - dev_id	: 設備結構。
 * @return 			: 中斷執行結果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定時 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定時器服務函數,用於按鍵消抖,定時器到了以後
 *				  再次讀取按鍵值,如果按鍵還是處於按下狀態就表示按鍵有效。
 * @param - arg	: 設備結構變量
 * @return 		: 無
 */
void timer_function(unsigned long arg)
{
	unsigned char value;
	unsigned char num;
	struct irq_keydesc *keydesc;
	struct keyinput_dev *dev = (struct keyinput_dev *)arg;

	num = dev->curkeynum;
	keydesc = &dev->irqkeydesc[num];
	value = gpio_get_value(keydesc->gpio); 	/* 讀取IO值 */
	if(value == 0){ 						/* 按下按鍵 */
		/* 上報按鍵值 */
		//input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
		input_report_key(dev->inputdev, keydesc->value, 1);/* 最後一個參數表示按下還是鬆開,1爲按下,0爲鬆開 */
		input_sync(dev->inputdev);
	} else { 									/* 按鍵鬆開 */
		//input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
		input_report_key(dev->inputdev, keydesc->value, 0);
		input_sync(dev->inputdev);
	}	
}

/*
 * @description	: 按鍵IO初始化
 * @param 		: 無
 * @return 		: 無
 */
static int keyio_init(void)
{
	unsigned char i = 0;
	char name[10];
	int ret = 0;
	
	keyinputdev.nd = of_find_node_by_path("/key");
	if (keyinputdev.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 

	/* 提取GPIO */
	for (i = 0; i < KEY_NUM; i++) {
		keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd ,"key-gpio", i);
		if (keyinputdev.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
		}
	}
	
	/* 初始化key所使用的IO,並且設置成中斷模式 */
	for (i = 0; i < KEY_NUM; i++) {
		memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(name));	/* 緩衝區清零 */
		sprintf(keyinputdev.irqkeydesc[i].name, "KEY%d", i);		/* 組合名字 */
		gpio_request(keyinputdev.irqkeydesc[i].gpio, name);
		gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);	
		keyinputdev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keyinputdev.nd, i);
	}
	/* 申請中斷 */
	keyinputdev.irqkeydesc[0].handler = key0_handler;
	keyinputdev.irqkeydesc[0].value = KEY_0;
	
	for (i = 0; i < KEY_NUM; i++) {
		ret = request_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev.irqkeydesc[i].handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, keyinputdev.irqkeydesc[i].name, &keyinputdev);
		if(ret < 0){
			printk("irq %d request failed!\r\n", keyinputdev.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	/* 創建定時器 */
	init_timer(&keyinputdev.timer);
	keyinputdev.timer.function = timer_function;

	/* 申請input_dev */
	keyinputdev.inputdev = input_allocate_device();
	keyinputdev.inputdev->name = KEYINPUT_NAME;
#if 0
	/* 初始化input_dev,設置產生哪些事件 */
	__set_bit(EV_KEY, keyinputdev.inputdev->evbit);	/* 設置產生按鍵事件          */
	__set_bit(EV_REP, keyinputdev.inputdev->evbit);	/* 重複事件,比如按下去不放開,就會一直輸出信息 		 */

	/* 初始化input_dev,設置產生哪些按鍵 */
	__set_bit(KEY_0, keyinputdev.inputdev->keybit);	
#endif

#if 0
	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif

	keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
	input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);

	/* 註冊輸入設備 */
	ret = input_register_device(keyinputdev.inputdev);
	if (ret) {
		printk("register input device failed!\r\n");
		return ret;
	}
	return 0;
}

/*
 * @description	: 驅動入口函數
 * @param 		: 無
 * @return 		: 無
 */
static int __init keyinput_init(void)
{
	keyio_init();
	return 0;
}

/*
 * @description	: 驅動出口函數
 * @param 		: 無
 * @return 		: 無
 */
static void __exit keyinput_exit(void)
{
	unsigned i = 0;
	/* 刪除定時器 */
	del_timer_sync(&keyinputdev.timer);	/* 刪除定時器 */
		
	/* 釋放中斷 */
	for (i = 0; i < KEY_NUM; i++) {
		free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);
	}
	/* 釋放input_dev */
	input_unregister_device(keyinputdev.inputdev);
	input_free_device(keyinputdev.inputdev);
}

module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

2.2、應用程序

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <linux/input.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: keyinputApp.c
作者	  	: 左忠凱
版本	   	: V1.0
描述	   	: input子系統測試APP。
其他	   	: 無
使用方法	 :./keyinputApp /dev/input/event1
論壇 	   	: www.openedv.com
日誌	   	: 初版V1.0 2019/8/26 左忠凱創建
***************************************************************/

/* 定義一個input_event變量,存放輸入事件信息 */
static struct input_event inputevent;

/*
 * @description		: main主程序
 * @param - argc 	: argv數組元素個數
 * @param - argv 	: 具體參數
 * @return 			: 0 成功;其他 失敗
 */
int main(int argc, char *argv[])
{
	int fd;
	int err = 0;
	char *filename;

	filename = argv[1];

	if(argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		err = read(fd, &inputevent, sizeof(inputevent));
		if (err > 0) { /* 讀取數據成功 */
			switch (inputevent.type) {


				case EV_KEY:
					if (inputevent.code < BTN_MISC) { /* 鍵盤鍵值 */
						printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
					} else {
						printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");
					}
					break;

				/* 其他類型的事件,自行處理 */
				case EV_REL:
					break;
				case EV_ABS:
					break;
				case EV_MSC:
					break;
				case EV_SW:
					break;
			}
		} else {
			printf("讀取數據失敗\r\n");
		}
	}
	return 0;
}

  當我們向 Linux 內核成功註冊 input_dev 設備以後,會在/dev/input 目錄下生成一個名爲“ eventX(X=0….n)”的文件,這個/dev/input/eventX 就是對應的 input 設備文件。我們讀取這個文件就可以獲取到輸入事件信息,比如按鍵值什麼的。使用 read函數讀取輸入設備文件,也就是/dev/input/eventX,讀取到的數據按照 input_event 結構體組織起來。獲取到輸入事件以後(input_event 結構體類型)使用 switch case 語句來判斷事件類型,本章實驗我們設置的事件類型爲 EV_KEY,因此只需要處理 EV_KEY 事件即可。比如獲取按鍵編號(KEY_0 的編號爲 11)、獲取按鍵狀態,按下還是鬆開的?

四、運行程序

  加載驅動模塊之前,我們先來看一下/dev/input目錄下都有哪些文件,如下圖:

在這裏插入圖片描述

  緊接着,加載編寫的按鍵input子系統驅動,再看下/dev/input目錄下內容:

在這裏插入圖片描述

  多了一個 event3 文件,因此/dev/input/event3 就是我們註冊的驅動所對應的設備文件。 keyinputApp 就是通過讀取/dev/input/event3 這個文件來獲取輸入事件信息的,輸入如下測試命令:

./keyinputApp /dev/input/event3

在這裏插入圖片描述
在這裏插入圖片描述

  可以看出,當我們按下或者釋放開發板上的按鍵以後都會在終端上輸出相應的內容,提示我們哪個按鍵按下或釋放了,在 Linux 內核中 KEY_0 爲 11。

另外,我們也可以不用 keyinputApp 來測試驅動,可以直接使用 hexdump 命令來查看/dev/input/event3 文件內容,輸入如下命令:

hexdump /dev/input/event3

   input_event 類型的原始事件數據值比較亂,這裏就不截圖了,我們來分析input_event事件數據即可。事件數據採用十六進制表示,數據的含義如下:

/* 編號 */ 	/* tv_sec */ 	/* tv_usec */ /* type */ /* code */ /* value */
0000000 	0c41 0000 		d7cd 000c 		0001 		000b 	0001 0000
0000010 	0c41 0000 		d7cd 000c 		0000 		0000 	0000 0000
0000020 	0c42 0000 		54bb 0000 		0001 		000b 	0000 0000
0000030 	0c42 0000 		54bb 0000 		0000 		0000 	0000 0000

  type 爲事件類型,EV_KEY 事件值爲 1, EV_SYN 事件值爲0。因此第 1 行表示 EV_KEY 事件,第 2 行表示 EV_SYN 事件。 code 爲事件編碼,也就是按鍵號,KEY_0 這個按鍵編號爲 11,對應的十六進制爲 0xb,因此第1 行表示 KEY_0 這個按鍵事件,最後的 value 就是按鍵值,爲 1 表示按下,爲 0 的話表示鬆開。

  綜上所述,示原始事件值含義如下:

  • 第 1 行,按鍵(KEY_0)按下事件。
  • 第 2 行, EV_SYN 同步事件,因爲每次上報按鍵事件以後都要同步的上報一個 EV_SYN 事件。
  • 第 3 行,按鍵(KEY_0)鬆開事件。
  • 第 4 行, EV_SYN 同步事件,和第 2 行一樣。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章