一、基本概念
按鍵、鼠標(biāo)、鍵盤、觸摸屏等都屬于輸入(input)設(shè)備,Linux 內(nèi)核為此專門做了一個叫做?input子系統(tǒng)的框架來處理輸入事件。本質(zhì)屬于字符設(shè)備。
1. input子系統(tǒng)結(jié)構(gòu)如下:
?input 子系統(tǒng)分為 input 驅(qū)動層、input 核心層、input 事件處理層,最終給用戶空間提供可訪問的設(shè)備節(jié)點(diǎn)。
(1)驅(qū)動層
輸入設(shè)備的具體驅(qū)動程序,比如按鍵驅(qū)動程序,向內(nèi)核層報告輸入內(nèi)容。
(2)核心層
a.承上啟下,為驅(qū)動層提供輸入設(shè)備注冊和操作接口;
b.通知事件層對輸入事件進(jìn)行處理。
?(3)事件層
? ?主要和用戶空間進(jìn)行交互。
2. input 子系統(tǒng)的所有設(shè)備主設(shè)備號都為 13,在drivers/input/input.c文件(核心層)中可以看到,?在使用 input 子系統(tǒng)處理輸入設(shè)備的時候就不需要去注冊字符設(shè)備了,我們只需要向系統(tǒng)注冊一個 input_device 即可。
二、input驅(qū)動編寫流程
1.注冊 input_dev
input_dev 結(jié)構(gòu)體表示 input設(shè)備,此結(jié)構(gòu)體定義在 include/linux/input.h 文件中,結(jié)構(gòu)體中包含了各種事件輸入類型,如evbit[BITS_TO_LONGS(EV_CNT)]存放著不同事件對應(yīng)的值,可選的輸入事件類型定義在input/uapi/linux/input.h?文件中,比如常見的輸入事件類型有同步事件、按鍵事件、重復(fù)事件等。
注冊過程:
a.申請input_dev結(jié)構(gòu)體變量 struct input_dev *input_allocate_device(void)?
b.初始化input_dev的事件類型以及事件值。
c.向Linux系統(tǒng)注冊input_dev設(shè)備 input_register_device(struct input_dev *dev)
d.卸載驅(qū)動的時候要注銷該設(shè)備并釋放前面申請的input_dev。
void input_unregister_device(struct input_dev *dev)
void input_free_device(struct input_dev *dev)?
2.上報輸入事件
首先是 input_event 函數(shù),此函數(shù)用于上報指定的事件以及對應(yīng)的值
void input_event(
struct input_dev *dev, //需要上報的 input_dev
unsigned int type, //上報的事件類型,比如 EV_KEY
unsigned int code, //事件碼,也就是我們注冊的按鍵值,比如 KEY_0、KEY_1 等等
int value //事件值,比如 1 表示按鍵按下,0 表示按鍵松開
)
input_event 函數(shù)可以上報所有的事件類型和事件值,Linux 內(nèi)核也提供了其他的針對具體事件的上報函數(shù),這些函數(shù)其實(shí)都用到了 input_event 函數(shù)。
當(dāng)我們上報事件以后還需要使用 input_sync 函數(shù)來告訴 Linux 內(nèi)核 input 子系統(tǒng)上報結(jié)束,input_sync 函數(shù)本質(zhì)是上報一個同步事件。
三、實(shí)驗(yàn)內(nèi)容
利用input子系統(tǒng)進(jìn)行按鍵輸入實(shí)驗(yàn)。
1.思路
input子系統(tǒng)在input.h文件中已經(jīng)注冊了字符設(shè)備,所以我們在寫驅(qū)動的時候不需要再注冊字符設(shè)備了,我們需要做的是從設(shè)備樹中獲取到按鍵的節(jié)點(diǎn)以及gpio、然后初始化gpio為中斷模式并申請中斷、初始化定時器(按鍵消抖使用),完成以上操作后,我們再初始化input_dev結(jié)構(gòu)體變量、注冊input_dev、設(shè)置事件和事件值、注冊inpu_dev設(shè)備、上報事件。
2.代碼
#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/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/input.h>
#define IMX6UIRQ_NAME "imx6uirq"
#define IMX6UIRQ_CNT 1
#define KEY_NUM 1
#define KEY0_VALUE 0X01 /* KEY0 按鍵值 */
#define INVAKEY 0xff
#define KEYINPUT_NAME "keyinput"
/*cmd*/
/*
_IO(type,nr) //沒有參數(shù)的命令
_IOR(type,nr,size) //該命令是從驅(qū)動讀取數(shù)據(jù)
_IOW(type,nr,size) //該命令是從驅(qū)動寫入數(shù)據(jù)
_IOWR(type,nr,size) //雙向數(shù)據(jù)傳輸
*/
// #define CLOSE_CMD _IO(0xef, 1)
// #define OPEN_CMD _IO(0xef, 2)
// #define SETPERIOD_CMD _IOW(0xef, 3, int)
struct keydevice_dev
{
int gpio; // IO
char name[10]; // IO name
int irqnum; //中斷號
unsigned char value; /* 按鍵對應(yīng)的鍵值 */
irqreturn_t (*handler)(int, void *); /* 中斷服務(wù)函數(shù) */
};
struct imx6uirq_dev
{
dev_t devid; /*設(shè)備號*/
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
int minor;
struct timer_list timer; //
int timeperiod; /* 定時周期,單位為 ms */
spinlock_t lock; //自旋鎖
struct keydevice_dev keydecs[KEY_NUM];
atomic_t key_value; /* 有效的按鍵鍵值 */
atomic_t release_key; /* 標(biāo)記是否完成一次完成的按鍵*/
unsigned char current_keynum; /* 當(dāng)前的按鍵號 */
struct input_dev *inputdev; /* input 結(jié)構(gòu)體 */
};
struct imx6uirq_dev imx6uirq;
/*
* @description : 關(guān)閉/釋放設(shè)備
* @param - filp : 要關(guān)閉的設(shè)備文件(文件描述符)
* @return : 0 成功;其他 失敗
*/
void timer_function(unsigned long arg)
{
struct keydevice_dev *keydecs;
struct imx6uirq_dev *dev =(struct imx6uirq_dev*)arg;
int ret = 0;
unsigned char num;
unsigned char value;
num = dev->current_keynum;
keydecs = &dev->keydecs[num];
value = gpio_get_value(keydecs->gpio);/* 讀取 IO 值 */
if(value == 0) /*按鍵按下*/
{
// printk("KEY0_PUSH\r\n");
/*上報按鍵值*/
input_report_key(dev->inputdev,keydecs->value,1);
input_sync(dev->inputdev);
}
else if(value==1)//釋放
{
// printk("KEY0_RELEASE\r\n");
/*上報按鍵值*/
input_report_key(dev->inputdev,keydecs->value,0);
input_sync(dev->inputdev);
}
}
/* @description : 中斷服務(wù)函數(shù),開啟定時器,延時 10ms,
* 定時器用于按鍵消抖。
* @param - irq : 中斷號
* @param - dev_id : 設(shè)備結(jié)構(gòu)。
* @return : 中斷執(zhí)行結(jié)果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->current_keynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&imx6uirq.timer,jiffies+msecs_to_jiffies(10));//消抖
// printk("irq handler\r\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
static int keyirq_init(void)
{
int ret = 0;
int i = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd == NULL)
{
ret = -EINVAL;
goto fail_findnd;
printk("find node failed");
}
/* 提取 GPIO */
for (i = 0; i < KEY_NUM; i++)
{
imx6uirq.keydecs[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpios", i);
if (imx6uirq.keydecs[i].gpio < 0)
{
printk("get gpio %d failed\r\n", i);
}
printk("imx6uirq.keydecs[%d].gpio = %d",i,imx6uirq.keydecs[i].gpio);
}
/* 初始化 key 所使用的 IO,并且設(shè)置成中斷模式 */
for (i = 0; i < KEY_NUM; i++)
{
memset(imx6uirq.keydecs[i].name, 0, sizeof(imx6uirq.keydecs[i].name)); //給數(shù)組清0,按字節(jié)賦值
sprintf(imx6uirq.keydecs[i].name, "KEY%d", i); //給數(shù)組賦值
gpio_request(imx6uirq.keydecs[i].gpio, imx6uirq.keydecs[i].name); //申請IO
gpio_direction_input(imx6uirq.keydecs[i].gpio); //設(shè)置為輸入模式
imx6uirq.keydecs[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); //獲取中斷號
printk("gpio %d irqnum=%d\r\n", imx6uirq.keydecs[i].gpio, imx6uirq.keydecs[i].irqnum);
}
/* 申請中斷 */
imx6uirq.keydecs[0].handler = key0_handler;
imx6uirq.keydecs[0].value = KEY_0;
/*根據(jù)按鍵的個數(shù)申請中斷*/
for (i = 0; i < KEY_NUM; i++)
{
ret = request_irq(imx6uirq.keydecs[i].irqnum, imx6uirq.keydecs[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
imx6uirq.keydecs[i].name, &imx6uirq);
if (ret < 0)
{
printk("irq %d request failed", imx6uirq.keydecs[i].irqnum);
ret = -EINVAL;
goto fail_request_irq;
}
}
/* 創(chuàng)建定時器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
/*申請input_dev*/
imx6uirq.inputdev = input_allocate_device();
imx6uirq.inputdev->name = KEYINPUT_NAME;
__set_bit(EV_KEY,imx6uirq.inputdev->evbit);/*按鍵事件*/
__set_bit(EV_REP,imx6uirq.inputdev->evbit);/*重復(fù)事件*/
__set_bit(KEY_0,imx6uirq.inputdev->keybit);
/* 初始化 input_dev,設(shè)置產(chǎn)生哪些按鍵 */
// imx6uirq.inputdev->evbit[0]=BIT_MASK(EV_KEY)|BIT_MASK(EV_REP);
// input_set_capability(imx6uirq.inputdev,EV_KEY,KEY_0);
/* 注冊輸入設(shè)備 */
ret = input_register_device(imx6uirq.inputdev);
if(ret){
printk("register failed\r\n");
return ret;
}
return 0;
fail_findnd:
fail_request_irq:
return ret;
}
/*驅(qū)動入口函數(shù)*/
static int __init imx6uirq_init(void)
{
keyirq_init();
return 0;
}
/*驅(qū)動出口函數(shù)*/
static void __exit imx6uirq_exit(void)
{
int i = 0;
/* 刪除定時器 */
del_timer_sync(&imx6uirq.timer);
/* 釋放中斷 */
for (i = 0; i < KEY_NUM; i++)
{
free_irq(imx6uirq.keydecs[i].irqnum, &imx6uirq);
}
input_unregister_device(imx6uirq.inputdev);
input_free_device(imx6uirq.inputdev);
printk("imx6uirq_exit !!!\r\n");
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dongdong");
3.代碼分析?
4.編寫測試APP
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "sys/ioctl.h"
6 #include "fcntl.h"
7 #include "stdlib.h"
8 #include "string.h"
9 #include <poll.h>
10 #include <sys/select.h>
11 #include <signal.h>
12 #include <fcntl.h>
13 #include <linux/input.h>
14
15 /* 定義一個 input_event 變量,存放輸入事件信息 */
16 static struct input_event inputevent;
17 /*
18 * @description : main主程序
19 * @param - argc : argv數(shù)組元素個數(shù)
20 * @param - argv : 具體參數(shù)
21 * @use: ./timerAPP /dev/gpioled
22 * @return : 0 成功;其他 失敗
23 */
24 int main(int argc, char *argv[])
25 {
26
27 char *filename;
28 int fd;
29 int ret = 0;
30
31 /*打開文件*/
32 filename = argv[1];
33
34 if (argc != 2) //檢查輸入?yún)?shù)個數(shù)
35 {
36 printf("useage error\r\n");
37 return -1;
38 }
39
40 fd = open(filename, O_RDWR);
41 if (fd < 0)
42 {
43 printf("can't open file %s\r\n", filename);
44 return -1;
45 }
46 /* 循環(huán)讀取按鍵值數(shù)據(jù)! */
47 while (1)
48 {
49 ret = read(fd, &inputevent,sizeof(inputevent));
50 if(ret<0)
51 {
52 printf("讀取數(shù)據(jù)失敗\r\n");
53 }
54 else{
55 switch(inputevent.type)
56 {
57 case EV_KEY:
58 if(inputevent.code < BTN_MISC)
59 {
60 printf("key press\r\n");
61 printf("key %d %s\r\n",inputevent.code,inputevent.value ? "press":"release");
62 }
63 else
64 {
65 printf("button %d %s\r\n",inputevent.code,inputevent.value?"press":"release");
66 }
67 break;
68 /* 其他類型的事件,自行處理 */
69 case EV_REL:
70 break;
71 case EV_ABS:
72 break;
73 case EV_MSC:
74 break;
75 case EV_SW:
76 break;
77
78 }
79
80 }
81 }
82 ret = close(fd);
83 if (ret < 0)
84 {
85 printf("file %s close failed!\r\n", argv[1]);
86 return -1;
87 }
88 return 0;
89
90 }
5.實(shí)驗(yàn)結(jié)果
按下按鍵與松開按鍵
?從上圖實(shí)驗(yàn)結(jié)果可以看出inpu_dev結(jié)構(gòu)體的成員變量的值,從左到右依次是:
此事件發(fā)生的時間(s、us,均為32位)、事件類型(16位)、事件編碼(16位)、按鍵值(32位)
?
?四、也可以用Linux自帶的按鍵驅(qū)動
1.make menuconfig配置
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
-> Keyboards (INPUT_KEYBOARD [=y])
->GPIO Buttons?
?2.修改設(shè)備樹文件
?可以參考Linux內(nèi)核文檔(Documentation/devicetree/bindings/input/gpio-keys.txt)
?
?參考上述文件修改開發(fā)板按鍵為回車鍵為LCD實(shí)驗(yàn)作準(zhǔn)備。文章來源:http://www.zghlxwxcb.cn/news/detail-686260.html
1 gpio-keys {
2 compatible = "gpio-keys";
3 #address-cells = <1>;
4 #size-cells = <0>;
5 autorepeat;
6 key0 {
7 label = "GPIO Key Enter";
8 linux,code = <KEY_ENTER>;
9 gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
10 };
11 };
3.最后,實(shí)驗(yàn)結(jié)果
文章來源地址http://www.zghlxwxcb.cn/news/detail-686260.html
到了這里,關(guān)于Linux Input子系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!