按键input驱动程序
#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/input.h>
#define KEYINPUT_CNT 1 /* 设备号个数 */
#define KEYINPUT_NAME "keyinput" /* 名字 */
#define KEY_NUM 1 /* 按键数量 */
/* 中断 IO 描述结构体 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中断号 */
unsigned char value; /* 按键对应的键值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};
/* key_dev 设备结构体 */
struct key_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
struct timer_list timer; /* 定义一个定时器 */
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
unsigned char curkeynum; /* 当前的按键号 */
struct input_dev *inputdev; /* input 结构体 */
};
struct key_dev keydev; /* key 设备 */
static irqreturn_t key0_irq_handler(int irqe, void *dev_id)
{
struct key_dev *dev = (struct key_dev *)dev_id;
// printk("key irq handler!\n");
dev->curkeynum = 0;
// 这里用定时器防抖
// mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_HANDLED;
}
/* 定时器回调函数 */
void timer_function(struct timer_list* timer)//回调函数
{
unsigned char num;
struct irq_keydesc *keydesc;
// printk("timer %lu\r\n", timer->expires);
num = keydev.curkeynum;
keydesc = &keydev.irqkeydesc[num];
if (gpio_get_value(keydesc->gpio) == 0) { /* key 按下 */
/* 上报按键值 */
//input_event(keydev.inputdev, EV_KEY, keydesc->value, 1);
input_report_key(keydev.inputdev, keydesc->value, 1);/*1,按下*/
input_sync(keydev.inputdev);
} else { /* 释放 */
//input_event(keydev.inputdev, EV_KEY, keydesc->value, 0);
input_report_key(keydev.inputdev, keydesc->value, 0);
input_sync(keydev.inputdev);
}
}
static int keyio_init(void)
{
int ret = 0;
unsigned char i = 0;
keydev.nd = of_find_node_by_path("/my-key");
if (keydev.nd== NULL) {
printk("my_key node can not found!\r\n");
return -EINVAL;
}
/* 提取 GPIO */
for (i=0; i<KEY_NUM; i++) {
keydev.irqkeydesc[i].gpio = of_get_named_gpio(keydev.nd, "key-gpios", i);
if (keydev.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
return -EINVAL;
}
}
/* 初始化 key 所使用的 IO,并且设置成中断模式 */
for (i=0; i<KEY_NUM; i++) {
memset(keydev.irqkeydesc[i].name, 0, sizeof(keydev.irqkeydesc[i].name));
sprintf(keydev.irqkeydesc[i].name, "KEY%d", i);
gpio_request(keydev.irqkeydesc[i].gpio, keydev.irqkeydesc[i].name);
gpio_direction_input(keydev.irqkeydesc[i].gpio);
keydev.irqkeydesc[i].irqnum = gpio_to_irq(keydev.irqkeydesc[i].gpio);
printk("key%d:gpio=%d, irqnum=%d\r\n", i, keydev.irqkeydesc[i].gpio, keydev.irqkeydesc[i].irqnum);
}
/* 初始化 timer,设置定时器处理函数,还未设置周期,所有不会激活定时器 */
timer_setup(&keydev.timer, timer_function, 0);
/* 申请 input_dev */
keydev.inputdev = input_allocate_device();
keydev.inputdev->name = KEYINPUT_NAME;
#if 0
/* 初始化 input_dev,设置产生哪些事件 */
__set_bit(EV_KEY, keydev.inputdev->evbit); /*按键事件 */
__set_bit(EV_REP, keydev.inputdev->evbit); /* 重复事件 */
/* 初始化 input_dev,设置产生哪些按键 */
__set_bit(KEY_0, keydev.inputdev->keybit);
#endif
#if 0
keydev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
keydev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif
keydev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(keydev.inputdev, EV_KEY, KEY_0);
/* 注册输入设备 */
ret = input_register_device(keydev.inputdev);
if (ret) {
printk("register input device failed!\r\n");
return ret;
}
/* 申请中断 */
keydev.irqkeydesc[0].handler = key0_irq_handler;
keydev.irqkeydesc[0].value = KEY_0;
for (i=0; i<KEY_NUM; i++) {
ret = request_irq(keydev.irqkeydesc[i].irqnum, keydev.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, keydev.irqkeydesc[i].name, &keydev);
if(ret < 0){
printk("irq %d request failed!\r\n", keydev.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
return 0;
}
static int __init mykey_init(void)
{
int ret;
/* 获取设备树中的属性数据 */
ret = keyio_init(); /* 初始化按键 IO */
if (ret < 0) {
return ret;
}
return 0;
}
static void __exit mykey_exit(void)
{
unsigned int i = 0;
/* 删除定时器 */
del_timer_sync(&keydev.timer);
/* 注销字符设备 */
cdev_del(&keydev.cdev);/* 删除 cdev */
unregister_chrdev_region(keydev.devid, KEYINPUT_CNT);
device_destroy(keydev.class, keydev.devid);
class_destroy(keydev.class);
/* 释放中断 */
for (i=0; i<KEY_NUM; i++) {
free_irq(keydev.irqkeydesc[i].irqnum, &keydev);
gpio_free(keydev.irqkeydesc[i].gpio);
}
/* 释放 input_dev */
input_unregister_device(keydev.inputdev);
input_free_device(keydev.inputdev);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wangxinchen");
测试 APP
#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>
// ./keyinputApp /dev/input/eventx
static struct input_event inputevent;
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;
}
测试
[root@buildroot: /lib/modules/5.2/key_input]$ls /dev/input
ls: /dev/input: No such file or directory
[root@buildroot: /lib/modules/5.2/key_input]$insmod keyinpuDriver.ko
[ 27.602575] keyinpuDriver: loading out-of-tree module taints kernel.
[ 27.610443] gpio[131] label = KEY0
[ 27.613944] key0:gpio=131, irqnum=57
[ 27.618216] input: keyinput as /devices/virtual/input/input0
[root@buildroot: /lib/modules/5.2/key_input]$ls /dev/input
event0
[root@buildroot: /lib/modules/5.2/key_input]$hexdump /dev/input/event0
0000000 002e 0000 f8c3 000c 0001 000b 0001 0000
0000010 002e 0000 f8c3 000c 0000 0000 0000 0000
0000020 002f 0000 007e 0000 0001 000b 0000 0000
0000030 002f 0000 007e 0000 0000 0000 0000 0000
0000040 002f 0000 7644 0009 0001 000b 0001 0000
0000050 002f 0000 7644 0009 0000 0000 0000 0000
0000060 002f 0000 aaa0 000c 0001 000b 0000 0000
0000070 002f 0000 aaa0 000c 0000 0000 0000 0000
0000080 0030 0000 ccd5 0005 0001 000b 0001 0000
0000090 0030 0000 ccd5 0005 0000 0000 0000 0000
00000a0 0030 0000 8be8 0008 0001 000b 0000 0000
00000b0 0030 0000 8be8 0008 0000 0000 0000 0000
00000c0 0031 0000 0dad 0003 0001 000b 0001 0000
00000d0 0031 0000 0dad 0003 0000 0000 0000 0000
00000e0 0031 0000 ccc8 0005 0001 000b 0000 0000
00000f0 0031 0000 ccc8 0005 0000 0000 0000 0000
^C
[root@buildroot: /lib/modules/5.2/key_input]$cd test/
[root@buildroot: /lib/modules/5.2/key_input/test]$
[root@buildroot: /lib/modules/5.2/key_input/test]$./
Makefile keyinputApp keyinputApp.c keyinputApp.o
[root@buildroot: /lib/modules/5.2/key_input/test]$./keyinputApp /dev/input/event0
key 11 press
key 11 release
key 11 press
key 11 release
key 11 press
key 11 release
key 11 press
key 11 release
分析三种设置事件和事件值的方法
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(long))
#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
#define BITS_TO_LONGS(nr) (((n) + (32) - 1) / (32))
#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
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
...
#define KEY_MAX 0x2ff
#define KEY_CNT (KEY_MAX+1)
BITS_TO_LONGS(), BIT_WORD(), BIT_MASK()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These three macros from bitops.h help some bitfield computations::
BITS_TO_LONGS(x) - returns the length of a bitfield array in longs for
x bits
BIT_WORD(x) - returns the index in the array in longs for bit x
BIT_MASK(x) - returns the index in a long for bit x
/*********第一种设置事件和事件值的方法***********/
__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);
/************************************************/
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
evbit[BITS_TO_LONGS(EV_CNT)];
-> evbit[(((0x20) + (32) - 1) / (32))]
-> evbit[1] 这里用1个unsigned long保存ev信息
keybit[BITS_TO_LONGS(KEY_CNT)];
-> keybit[(((0x300) + (32) - 1) / (32))]
-> keybit[24] 这里用24个unsigned long保存key信息
Linux自带按键驱动
Linux支持配置
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
-> Keyboards (INPUT_KEYBOARD [=y])
->GPIO Buttons
设备树:
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
/ {
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
key0 {
label = "GPIO Key Enter";
linux,code = <KEY_ENTER>;
gpios = <&pio 4 3 GPIO_ACTIVE_LOW>;
};
};
}
测试:
[root@buildroot: ~]$hexdump /dev/input/event0
0000000 0041 0000 2867 0009 0001 001c 0001 0000
0000010 0041 0000 2867 0009 0000 0000 0000 0000
0000020 0041 0000 726b 000b 0001 001c 0000 0000
0000030 0041 0000 726b 000b 0000 0000 0000 0000
0000040 0042 0000 8c85 0008 0001 001c 0001 0000
0000050 0042 0000 8c85 0008 0000 0000 0000 0000
0000060 0042 0000 fd2d 000a 0001 001c 0000 0000
0000070 0042 0000 fd2d 000a 0000 0000 0000 0000
0000080 0043 0000 bbab 0004 0001 001c 0001 0000
0000090 0043 0000 bbab 0004 0000 0000 0000 0000
00000a0 0043 0000 2c92 0007 0001 001c 0000 0000
00000b0 0043 0000 2c92 0007 0000 0000 0000 0000
00000c0 0044 0000 27ad 0000 0001 001c 0001 0000
00000d0 0044 0000 27ad 0000 0000 0000 0000 0000
00000e0 0044 0000 e6d4 0002 0001 001c 0000 0000
00000f0 0044 0000 e6d4 0002 0000 0000 0000 0000
......
如果连续输出数据,修改GPIO_ACTIVE_LOW。触发电平的问题,导致重复触发。