i.MX6ULL驱动开发 | 21 - 按键驱动使用 input 子系统上报事件
Posted Mculover666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了i.MX6ULL驱动开发 | 21 - 按键驱动使用 input 子系统上报事件相关的知识,希望对你有一定的参考价值。
本系列文章所编写的驱动源码仓库,欢迎Star:https://github.com/Mculover666/linux_driver_study。
一、编写驱动
1. 编写模块
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
struct key_dev
struct device_node *node; /*!< 设备树节点 */
int gpio; /*!< key使用的gpio编号 */
int debounce_interval; /*!< 去抖时长 */
struct timer_list timer; /*!< 用于软件消抖 */
struct input_dev *input; /*!< 输入设备 */
int irq; /*!< 中断号 */
;
static struct key_dev key;
static irqreturn_t key0_handler(int irq, void *dev_id)
struct key_dev *dev = (struct key_dev *)dev_id;
// 启动软件定时器, 消抖时间后再去读取
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->debounce_interval));
dev->timer.data = (volatile long)dev_id;
return IRQ_RETVAL(IRQ_HANDLED);
static void timer_handler(unsigned long arg)
int val;
struct key_dev *dev = (struct key_dev *)arg;
val = gpio_get_value(dev->gpio);
if (val == 0)
else
static int key_init(void)
int ret;
uint32_t debounce_interval = 0;
// 获取设备树节点
key.node = of_find_node_by_name(NULL, "key0");
if (!key.node)
printk("key0 node find fail!\\n");
return -1;
// 提取gpio
key.gpio = of_get_named_gpio(key.node, "key-gpio", 0);
if (key.gpio < 0)
printk("find key-gpio propname fail!\\n");
return -1;
// 初始化gpio
ret = gpio_request(key.gpio, "key-gpio");
if (ret < 0)
printk("gpio request fail!\\n");
return -1;
gpio_direction_input(key.gpio);
// 解析设备树,获取去抖时长
if (of_property_read_u32(key.node, "debounce-interval", &debounce_interval) < 0)
printk("find debounce-interval fail!\\n");
goto error;
// 设置去抖时长
if (debounce_interval)
ret = gpio_set_debounce(key.gpio, key.debounce_interval * 1000);
if (ret < 0)
printk("gpio_set_debounce not support, use soft timer!\\n");
// 设置软件定时器用于消抖
init_timer(&key.timer);
key.timer.function = timer_handler;
key.debounce_interval = debounce_interval;
// 获取中断号
key.irq = gpio_to_irq(key.gpio);
if (key.irq < 0)
printk("gpio_to_irq fail!\\n");
goto error;
// 申请中断
ret = request_irq(key.irq, key0_handler, IRQF_TRIGGER_FALLING, "key_irq", &key);
if (ret < 0)
printk("irq request fail, ret is %d!\\n", ret);
goto error;
return 0;
error:
gpio_free(key.gpio);
return -1;
static void key_deinit(void)
// 释放中断
free_irq(key.irq, &key);
// 释放gpio
gpio_free(key.gpio);
// 删除定时器
del_timer(&key.timer);
static int __init key_module_init(void)
return key_init();
static void __exit key_module_exit(void)
key_deinit();
module_init(key_module_init);
module_exit(key_module_exit);
MODULE_AUTHOR("Mculover666");
MODULE_LICENSE("GPL");
2. 输入设备
引入头文件:
#include <linux/input.h>
(1)添加全局变量:
struct key_dev
struct device_node *node; /*!< 设备树节点 */
int gpio; /*!< key使用的gpio编号 */
int debounce_interval; /*!< 去抖时长 */
struct timer_list timer; /*!< 用于软件消抖 */
struct input_dev *input; /*!< 输入设备 */
int irq; /*!< 中断号 */
;
(2)分配/注册输入设备
在按键初始化函数中,编写以下代码。
// 分配输入设备
key.input = input_allocate_device();
if (!key.input)
printk("irq input_allocate_device fail!\\n");
goto error;
为了使用 setbit 函数,引入头文件:
#include <asm/bitops.h>
编写初始化输入设备的代码:
// 初始化输入设备
key.input->name = "keyinput";
set_bit(EV_KEY, key.input->evbit); // 按键事件
set_bit(EV_REP, key.input->evbit); // 重复事件
set_bit(KEY_0, key.input->keybit); // 设置产生哪些按键
注册输入设备:
// 注册输入设备
ret = input_register_device(key.input);
if (ret < 0)
printk("input_register_device fail!\\n");
goto error;
(3)注销/释放输入设备
在按键去初始化函数中,添加输入设备的注销与释放。
static void key_deinit(void)
// 注销输入设备
input_unregister_device(key.input);
// 释放输入设备
input_free_device(key.input);
// 释放中断
free_irq(key.irq, &key);
// 释放gpio
gpio_free(key.gpio);
// 删除定时器
del_timer(&key.timer);
3. 上报事件
在消抖定时器超时后,上报事件值:
static void timer_handler(unsigned long arg)
int val;
struct key_dev *dev = (struct key_dev *)arg;
static int i = 0;
// 上报输入事件
val = gpio_get_value(dev->gpio);
printk("--->report, i = %d\\n", i++);
if (val == 0)
input_report_key(dev->input, KEY_0, 1);
input_sync(dev->input);
input_report_key(dev->input, KEY_0, 0);
input_sync(dev->input);
在上报按键按下之后,要再上报按键松开,不然Linux内核就会一直以为按键处于按下状态!
4. 编译模块
KERNEL_DIR = /home/mculover666/imx6ull/kernel/linux-imx6ull
obj-m = input_key.o
build: kernel_module
kernel_module:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
二、编写应用程序
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <linux/input.h>
#include <string.h>
static struct input_event event;
int main(int argc, char *argv[])
int fd;
int ret;
if (argc != 2)
printf("usage: ./test_input_key [device]\\n");
return -1;
fd = open(argv[1], O_RDWR);
if (fd < 0)
printf("open file %s fail!", argv[1]);
return -1;
while (1)
memset(&event, 0, sizeof(event));
ret = read(fd, &event, sizeof(event));
switch (event.type)
case EV_SYN:
break;
case EV_KEY:
if (event.code < BTN_MISC) // 键盘键值
printf("key %d %s\\n", event.code, event.value ? "press" : "release");
else
printf("btn %d %s\\n", event.code, event.value ? "press" : "release");
break;
case EV_REL:
break;
case EV_ABS:
break;
case EV_MSC:
break;
case EV_SW:
break;
三、测试
1. 加载模块,查看输入设备节点
加载模块之前,查看设备节点,加载模块之后再次查看设备节点:
可以看到新增的设备为 /dev/event4
。
2. 运行应用程序
开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系以上是关于i.MX6ULL驱动开发 | 21 - 按键驱动使用 input 子系统上报事件的主要内容,如果未能解决你的问题,请参考以下文章
i.MX6ULL驱动开发 | 25 - 基于Linux自带的KEY驱动检测按键
i.MX6ULL驱动开发 | 18 - 使用中断方式检测按键
i.MX6ULL驱动开发 | 18 - 使用中断方式检测按键
i.MX6ULL驱动开发 | 20 - Linux input 子系统