Linux驱动之INPUT设备驱动

目录

一、开发环境

二、编写按键input设备的注册与事件上报

        2.1 修改设备树文件

                1 添加 pinctrl 节点

                2、添加 KEY 设备节点

                3、检查 PIN 是否被其他外设使用

        2.2 驱动程序编写

        2.3 测试APP编写

        2.4 运行测试

三、Linux内核自带按键input设备驱动

        3.1 自带按键驱动程序源码简析

        3.2 自带按键驱动程序的使用

        3.3 运行测试


        上一章已经了解了input子系统的大体框架和input设备的注册以及对应事件的上报流程,现在就写一个简单的input设备驱动实验来更加深入的理解input子系统。

        本章将分别采用以下两种方法来进行按键input设备驱动的实验:

  • 1、编写按键input设备的注册与事件上报

  • 2、Linux内核自带按键input设备驱动

一、开发环境

  • CPU:IMX6ULL

  • 内核版本:Linux-5.19

二、编写按键input设备的注册与事件上报

2.1 修改设备树文件

1 添加 pinctrl 节点

        I.MX6U-ALPHA开发板上的 KEY 使用了 UART1_CTS_B这个 PIN,打开 imx6ul-14x14-evk.dtsi ,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点,节点内容如下所示:

pinctrl_key: keygrp {fsl,pins = <MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* key0 */>;
};

        第 3 行,将 GPIO_IO18 这个 PIN 复用为 GPIO1_IO18。

2、添加 KEY 设备节点

        在根节点“/”下创建 KEY 节点,节点名为“key”,节点内容如下:

key {#address-cells = <1>;#size-cells = <1>;compatible = "imx6ull-key";pinctrl-names = "default";pinctrl-0 = <&pinctrl_key>;key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;status = "okay";
};

        第 6 行, pinctrl-0 属性设置 KEY 所使用的 PIN 对应的 pinctrl 节点。

        第 7 行, key-gpio 属性指定了 KEY 所使用的 GPIO。

3、检查 PIN 是否被其他外设使用

        本次实验中按键使用的 PIN 为 UART1_CTS_B,因此先检查 PIN 为 UART1_CTS_B 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO1_IO18这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。

        设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-toto.dtb 文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如下所示:

/ # ls /proc/device-tree/
#address-cells      clock-osc           pmu
#size-cells         compatible          regulator-can-3v3
aliases             cpus                regulator-peri-3v3
backlight-display   dts_led             regulator-sd1-vmmc
beep                key                 soc
chosen              memory@80000000     sound-wm8960
clock-cli           model               spi4
clock-di0           name                timer
clock-di1           panel

2.2 驱动程序编写

        设备树准备好以后就可以编写驱动程序了,在 input_key.c 里面输入如下内容:

/** Copyright © toto Co., Ltd. 1998-2029. All rights reserved.* @Description: * @Version: 1.0* @Autor: Seven* @Date: 2023-09-17 13:19:32* @LastEditors: Seven* @LastEditTime: 2023-09-17 16:57:23*/
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/timer.h>#define KEYINPUT_CNT    1           /* 设备数量 */
#define KEYINPUT_NAME   "inputkey"  /* 设备名字 */
#define KEY_VALUE       0x1         /* 按键值 */
#define KEY_INVALID     0xFF        /* 无效的按键值 */
#define KEY_NUM         1           /* 按键数量 *//* 定义按键中断操作结构体 */
struct irq_keydesc {int gpio;           /* 中断使用的gpio */int irqnum;         /* 中断号 */unsigned char value;/* 按键对应的键值 */char name[10];      /* 中断名 */irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};/* inputkey设备信息 */
struct inputkey_dev {dev_t devid;struct cdev cdev;struct class *class;struct device *device;struct device_node *dev_nd;struct timer_list timer;struct irq_keydesc irq_desc[KEY_NUM];unsigned char cur_keynum;struct input_dev *inputdev;
};/* 定义 key 输入设备 */
struct inputkey_dev inputdev;/** @Brief   中断服务函数,开启定时器,*          延时10ms用于按键消抖* @Param   irq:中断号* @Param   dev_id:设备结构* @Note    NOne* @RetVal  中断执行结果*/
static irqreturn_t key_handler(int irq, void *dev_id)
{struct inputkey_dev *dev = (struct inputkey_dev *)dev_id;dev->cur_keynum = 0;// dev->timer.data = (volatile long)dev_id;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));return IRQ_RETVAL(IRQ_HANDLED);
}/** @Brief   定时器服务函数,用于按键消抖,*          定时器到再次读取按键值,按键还处于按下则按键有效* @Param   t:设备结构体* @Note    NOne* @RetVal  NOne*/
void timer_function(struct timer_list *t)
{unsigned char value, num;struct irq_keydesc *keydesc;struct inputkey_dev *dev = from_timer(dev, t, timer);num = dev->cur_keynum;keydesc = &dev->irq_desc[num];/* 读取IO值 */value = gpio_get_value(keydesc->gpio);/* 按键按下 */if(value == 0) {/* 上报按键值 */input_report_key(dev->inputdev, keydesc->value, 1);input_sync(dev->inputdev);} else { /* 按键松开 */input_report_key(dev->inputdev, keydesc->value, 0);input_sync(dev->inputdev);}
}/** @Brief   按键IO初始化* @Param   None* @Note    NOne* @RetVal  NOne*/
static int keyio_init(void)
{unsigned char i = 0;char name[10];int ret = 0;inputdev.dev_nd = of_find_node_by_path("/key");if (!inputdev.dev_nd) {printk(KERN_ERR "key node not found\r");return -EINVAL;}/* 提取GPIO */for (i = 0; i < KEY_NUM; i++) {inputdev.irq_desc[i].gpio = of_get_named_gpio(inputdev.dev_nd, "key-gpio", i);if (inputdev.irq_desc[i].gpio < 0) {printk(KERN_ERR "can't get key:%d\n", i);}}/* 初始化key所使用的IO, 并设置中断模式 */for (i = 0; i < KEY_NUM; i++) {memset(inputdev.irq_desc[i].name, 0, sizeof(name));sprintf(inputdev.irq_desc[i].name, "KEY%d", i);gpio_request(inputdev.irq_desc[i].gpio, inputdev.irq_desc[i].name);gpio_direction_input(inputdev.irq_desc[i].gpio);inputdev.irq_desc[i].irqnum = irq_of_parse_and_map(inputdev.dev_nd, i);}/* 申请中断 */inputdev.irq_desc[0].handler = key_handler;inputdev.irq_desc[0].value = KEY_0;for (i = 0; i < KEY_NUM; i++) {ret = request_irq(inputdev.irq_desc[i].irqnum,inputdev.irq_desc[i].handler,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,inputdev.irq_desc[i].name, &inputdev);if (ret < 0) {printk(KERN_ERR "irq %d request failed\n", inputdev.irq_desc[i].irqnum);return -EFAULT;}}/* 6.初始化timer */inputdev.timer.expires = jiffies + msecs_to_jiffies(10);timer_setup(&inputdev.timer, timer_function, 0);/* 申请input_dev */inputdev.inputdev = input_allocate_device();inputdev.inputdev->name = KEYINPUT_NAME;inputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);input_set_capability(inputdev.inputdev, EV_KEY, KEY_0);/* 注册input设备*/ret = input_register_device(inputdev.inputdev);if (ret) {printk(KERN_ERR "register input device failed\n");return ret;}return 0;
}/** @Brief   驱动入口函数* @Param   None* @Note    NOne* @RetVal  NOne*/
static int __init inputkey_init(void)
{keyio_init();return 0;
}/** @Brief   驱动出口函数* @Param   None* @Note    NOne* @RetVal  NOne*/
static void __exit inputkey_exit(void)
{unsigned int i = 0;/* 删除定时器 */del_timer_sync(&inputdev.timer);/* 释放中断 */for (i = 0; i < KEY_NUM; i++) {free_irq(inputdev.irq_desc[i].irqnum, &inputdev);}/* 释放IO */for (i = 0; i < KEY_NUM; i++) {gpio_free(inputdev.irq_desc[i].gpio);}/* 释放input设备 */input_unregister_device(inputdev.inputdev);input_free_device(inputdev.inputdev);
}module_init(inputkey_init);
module_exit(inputkey_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");

2.3 测试APP编写

        input_key_app.c 测试程序具体代码如下:

/** Copyright © toto Co., Ltd. 1998-2029. All rights reserved.* @Description: * @Version: 1.0* @Autor: Seven* @Date: 2023-09-17 16:10:12* @LastEditors: Seven* @LastEditTime: 2023-09-17 21:12:01*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/input.h>/* 定义一个input_event变量,存放输入事件信息 */
static struct input_event inputevt;/** @Brief   main 主程序* @Param   argc:* @Param   argv:* @Note    NOne* @RetVal  0-成功;其他-失败*/
int main(int argc, char *argv[])
{int fd, err;char *filename;if (argc != 2) {printf("argc != 3\n");return -1;}filename = argv[1];/*打开驱动文件*/fd = open(filename, O_RDWR);if (fd < 0) {printf("open filename:%d failed\n", filename);return -1;}while (1) {err = read(fd, &inputevt, sizeof(inputevt));if (err <= 0) {printf("read inputevt failed\n");continue;}switch (inputevt.type){case EV_KEY:printf("key %d %s\n", inputevt.code, inputevt.value ? "down" : "up");break;case EV_REL:break;case EV_ABS:break;case EV_MSC:break;case EV_SW:break;default:break;}}/*关闭文件*/close(fd);return 0;
}

2.4 运行测试

        开发板上电,将input_key.ko 和 input_key_app 这两个文件拷贝到 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 目录中,在加载input_key.ko之前,先来看一下/dev/input 目录下都有哪些文件,结果如下所示:

/ # ls -al /dev/input/
total 0
drwxr-xr-x    2 0        0               80 Jan 19 19:30 .
drwxr-xr-x    6 0        0             2880 Jan 19 19:25 ..
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0

        从上面可以看出,当前/dev/input 目录只有 event0 这一个文件。接下来输入如下命令加载 input_key.ko 这个驱动模块。

/ # insmod /lib/modules/5.19.0-g794a2f7be62d-dirty/input_key.ko 
[  310.665956] input_key: loading out-of-tree module taints kernel.
[  310.678432] input: inputkey as /devices/virtual/input/input2

        当驱动模块加载成功以后再来看一下/dev/input 目录下有哪些文件,结果如下所示:

/ # ls -al /dev/input/
total 0
drwxr-xr-x    2 0        0               80 Jan 19 19:30 .
drwxr-xr-x    6 0        0             2880 Jan 19 19:25 ..
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0
crw-------    1 0        0          13,  65 Jan 19 19:30 event1

        从上面可以看出,多了一个 event1 文件,因此/dev/input/event1 就是注册的驱动所对应的设备文件。 input_key_app 就是通过读取/dev/input/event1 这个文件来获取输入事件信息的,输入如下测试命令:

./input_key_app /dev/input/event1

        然后按下开发板上的 KEY 按键,结果如下所示:

/home/app # ./input_key_app /dev/input/event1
key 11 down
key 11 up
key 11 down
key 11 up
key 11 down
key 11 up
key 11 down
key 11 up

        从上面可以看出,当我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在 Linux 内核中 KEY_0 为 11。

        另外,我们也可以不用 input_key_app 来测试驱动,可以直接使用 hexdump 命令来查看/dev/input/event1 文件内容,输入如下命令:

hexdump /dev/input/event1

        然后按下按键,终端输出如下所示信息:

/ # hexdump /dev/input/event1
0000000 079e 0019 8806 0004 0001 000b 0001 0000
0000010 079e 0019 8806 0004 0000 0000 0000 0000
0000020 079e 0019 6be6 0007 0001 000b 0000 0000
0000030 079e 0019 6be6 0007 0000 0000 0000 0000
0000040 079f 0019 2861 0003 0001 000b 0001 0000
0000050 079f 0019 2861 0003 0000 0000 0000 0000
0000060 079f 0019 d5a2 0004 0001 000b 0000 0000
0000070 079f 0019 d5a2 0004 0000 0000 0000 0000
0000080 07a0 0019 69a9 0000 0001 000b 0001 0000
0000090 07a0 0019 69a9 0000 0000 0000 0000 0000
00000a0 07a0 0019 14f8 0002 0001 000b 0000 0000
00000b0 07a0 0019 14f8 0002 0000 0000 0000 0000

        上面就是 input_event 类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:

/*****************input_event 类型********************/
/* 编号 */    /* tv_sec */    /* tv_usec */    /* type */    /* code */    /* value */0000000       079e 0019       8806 0004        0001         000b           0001 0000
0000010       079e 0019       8806 0004        0000         0000           0000 0000
0000020       079e 0019       6be6 0007        0001         000b           0000 0000
0000030       079e 0019       6be6 0007        0000         0000           0000 0000

        type 为事件类型,查看示例代码 58.1.2.3 可知, EV_KEY 事件值为 1, EV_SYN 事件值为0。因此第 1 行表示 EV_KEY 事件,第 2 行表示 EV_SYN 事件。

        code 为事件编码,也就是按键号,查看示例代码 58.1.2.4 可以, KEY_0 这个按键编号为 11,对应的十六进制为 0xb,因此第1 行表示 KEY_0 这个按键事件,最后的 value 就是按键值,为 1 表示按下,为 0 的话表示松开。

综上所述,"hexdump /dev/input/event1" 中的原始事件值含义如下:

  • 第 1 行,按键(KEY_0)按下事件。

  • 第 2 行, EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个 EV_SYN 事件。

  • 第 3 行,按键(KEY_0)松开事件。

  • 第 4 行, EV_SYN 同步事件,和第 2 行一样。

三、Linux内核自带按键input设备驱动

3.1 自带按键驱动程序源码简析

        Linux 内核也自带了 KEY 驱动,如果要使用内核自带的 KEY 驱动的话需要配置 Linux 内核,不过 Linux 内核一般默认已经使能了 KEY 驱动,但是我们还是要检查一下。按照如下路径找到相应的配置选项:

-> Device Drivers-> Input device support-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])-> Keyboards (INPUT_KEYBOARD [=y])->GPIO Buttons

选中“GPIO Buttons”选项,将其编译进 Linux 内核中【默认是选中的】,如下图所示:

 

bbab636c0f0b06145393dc015436f532.png

        选中以后就会在.config 文件中出现“CONFIG_KEYBOARD_GPIO=y”这一行, Linux 内核就会根据这一行来将 KEY 驱动文件编译进 Linux 内核。

        Linux 内核自带的 KEY 驱动文件为drivers/input/keyboard/gpio_keys.c, gpio_keys.c 采用了 platform 驱动框架,在 KEY 驱动上使用了 input 子系统实现。在 gpio_keys.c 文件中找到如下所示内容:

static const struct of_device_id gpio_keys_of_match[] = {{ .compatible = "gpio-keys", },{ },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);static int gpio_keys_probe(struct platform_device *pdev)
......
static struct platform_driver gpio_keys_device_driver = {.probe      = gpio_keys_probe,.shutdown   = gpio_keys_shutdown,.driver     = {.name   = "gpio-keys",.pm = &gpio_keys_pm_ops,.of_match_table = gpio_keys_of_match,.dev_groups = gpio_keys_groups,}
};static int __init gpio_keys_init(void)
{return platform_driver_register(&gpio_keys_device_driver);
}static void __exit gpio_keys_exit(void)
{platform_driver_unregister(&gpio_keys_device_driver);
}late_initcall(gpio_keys_init);
module_exit(gpio_keys_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Phil Blundell <pb@handhelds.org>");
MODULE_DESCRIPTION("Keyboard driver for GPIOs");
MODULE_ALIAS("platform:gpio-keys");

        从上面的代码可以看出,这就是一个标准的 platform 驱动框架,如果要使用设备树来描述 KEY 设备信息的话,设备节点的 compatible 属性值要设置为“gpio-keys”。当设备和驱动匹配以后 gpio_keys_probe 函数就会执行, gpio_keys_probe 函数内容如下:

static int gpio_keys_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);struct fwnode_handle *child = NULL;struct gpio_keys_drvdata *ddata;struct input_dev *input;int i, error;int wakeup = 0;if (!pdata) {pdata = gpio_keys_get_devtree_pdata(dev);if (IS_ERR(pdata))return PTR_ERR(pdata);}......input = devm_input_allocate_device(dev);if (!input) {dev_err(dev, "failed to allocate input device\n");return -ENOMEM;}ddata->pdata = pdata;ddata->input = input;mutex_init(&ddata->disable_lock);platform_set_drvdata(pdev, ddata);input_set_drvdata(input, ddata);input->name = pdata->name ? : pdev->name;input->phys = "gpio-keys/input0";input->dev.parent = dev;input->open = gpio_keys_open;input->close = gpio_keys_close;input->id.bustype = BUS_HOST;input->id.vendor = 0x0001;input->id.product = 0x0001;input->id.version = 0x0100;input->keycode = ddata->keymap;input->keycodesize = sizeof(ddata->keymap[0]);input->keycodemax = pdata->nbuttons;/* Enable auto repeat feature of Linux input subsystem */if (pdata->rep)__set_bit(EV_REP, input->evbit);for (i = 0; i < pdata->nbuttons; i++) {const struct gpio_keys_button *button = &pdata->buttons[i];if (!dev_get_platdata(dev)) {child = device_get_next_child_node(dev, child);if (!child) {dev_err(dev,"missing child device node for entry %d\n",i);return -EINVAL;}}error = gpio_keys_setup_key(pdev, input, ddata,button, i, child);if (error) {fwnode_handle_put(child);return error;}if (button->wakeup)wakeup = 1;}fwnode_handle_put(child);error = input_register_device(input);if (error) {dev_err(dev, "Unable to register input device, error: %d\n",error);return error;}device_init_wakeup(dev, wakeup);return 0;
}

大致可以总结如下:

  • 调用 gpio_keys_get_devtree_pdata 函数从设备树中获取到 KEY 相关的设备节点信息。

  • 调用 devm_input_allocate_device 函数申请 input_dev。

  • 初始化 input_dev。

  • 设置 input_dev 事件,这里设置了 EV_REP 事件。

  • 调用 gpio_keys_setup_key 函数继续设置 KEY,此函数会设置 input_dev 的EV_KEY 事件已经事件码(也就是 KEY 模拟为哪个按键)。

  • 调用 input_register_device 函数向 Linux 系统注册 input_dev。

接下来再来看一下 gpio_keys_setup_key 函数,此函数内容如下:

static int gpio_keys_setup_key(struct platform_device *pdev,struct input_dev *input,struct gpio_keys_drvdata *ddata,const struct gpio_keys_button *button,int idx,struct fwnode_handle *child)
{const char *desc = button->desc ? button->desc : "gpio_keys";struct device *dev = &pdev->dev;struct gpio_button_data *bdata = &ddata->data[idx];irq_handler_t isr;unsigned long irqflags;int irq;int error;bdata->input = input;bdata->button = button;spin_lock_init(&bdata->lock);if (child) {bdata->gpiod = devm_fwnode_gpiod_get(dev, child,NULL, GPIOD_IN, desc);if (IS_ERR(bdata->gpiod)) {error = PTR_ERR(bdata->gpiod);if (error == -ENOENT) {/** GPIO is optional, we may be dealing with* purely interrupt-driven setup.*/bdata->gpiod = NULL;} else {if (error != -EPROBE_DEFER)dev_err(dev, "failed to get gpio: %d\n",error);return error;}}} else if (gpio_is_valid(button->gpio)) {/** Legacy GPIO number, so request the GPIO here and* convert it to descriptor.*/unsigned flags = GPIOF_IN;if (button->active_low)flags |= GPIOF_ACTIVE_LOW;error = devm_gpio_request_one(dev, button->gpio, flags, desc);if (error < 0) {dev_err(dev, "Failed to request GPIO %d, error %d\n",button->gpio, error);return error;}bdata->gpiod = gpio_to_desc(button->gpio);if (!bdata->gpiod)return -EINVAL;.......INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);hrtimer_init(&bdata->debounce_timer,CLOCK_REALTIME, HRTIMER_MODE_REL);bdata->debounce_timer.function = gpio_keys_debounce_timer;isr = gpio_keys_gpio_isr;irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;......bdata->irq = button->irq;if (button->type && button->type != EV_KEY) {dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");return -EINVAL;}bdata->release_delay = button->debounce_interval;hrtimer_init(&bdata->release_timer,CLOCK_REALTIME, HRTIMER_MODE_REL_HARD);bdata->release_timer.function = gpio_keys_irq_timer;isr = gpio_keys_irq_isr;irqflags = 0;/** For IRQ buttons, there is no interrupt for release.* So we don't need to reconfigure the trigger type for wakeup.*/}bdata->code = &ddata->keymap[idx];*bdata->code = button->code;input_set_capability(input, button->type ?: EV_KEY, *bdata->code);......return 0;
}

        调用 input_set_capability 函数设置 EV_KEY 事件以及 KEY 的按键类型,也就是 KEY 作为哪个按键?我们会在设备树里面设置指定的 KEY 作为哪个按键。

        一切都准备就绪以后剩下的就是等待按键按下,然后向 Linux 内核上报事件,事件上报是在 gpio_keys_irq_isr 函数中完成的,此函数内容如下:

static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{struct gpio_button_data *bdata = dev_id;struct input_dev *input = bdata->input;unsigned long flags;BUG_ON(irq != bdata->irq);spin_lock_irqsave(&bdata->lock, flags);if (!bdata->key_pressed) {if (bdata->button->wakeup)pm_wakeup_event(bdata->input->dev.parent, 0);input_event(input, EV_KEY, *bdata->code, 1);input_sync(input);if (!bdata->release_delay) {input_event(input, EV_KEY, *bdata->code, 0);input_sync(input);goto out;}bdata->key_pressed = true;}if (bdata->release_delay)hrtimer_start(&bdata->release_timer,ms_to_ktime(bdata->release_delay),HRTIMER_MODE_REL_HARD);
out:spin_unlock_irqrestore(&bdata->lock, flags);return IRQ_HANDLED;
}

        gpio_keys_irq_isr 是按键中断处理函数,调用 "input_event" 向 Linux 系统上报 EV_KEY 事件,表示按键按下。调用 "input_sync" 使用 input_sync 函数向系统上报 EV_REP 同步事件。

        综上所述, Linux 内核自带的 gpio_keys.c 驱动文件思路和前面编写的 input_key.c 驱动文件基本一致。都是申请和初始化 input_dev,设置事件,向 Linux 内核注册 input_dev。最终在按键中断服务函数或者消抖定时器中断服务函数中上报事件和按键值。

3.2 自带按键驱动程序的使用

        要使用 Linux 内核自带的按键驱动程序很简单,只需要根据 Documentation/devicetree/bindings/input/gpio-keys.yaml 这个文件在设备树中添加指定的设备节点即可,节点要求如下:

  • ①、节点名字为“gpio-keys”。

  • ②、 gpio-keys 节点的 compatible 属性值一定要设置为“gpio-keys”。

  • ③、所有的 KEY 都是 gpio-keys 的子节点,每个子节点可以用如下属性描述:
    • gpios: KEY 所连接的 GPIO 信息

    • interrupts: KEY 所使用 GPIO 中断信息,不是必须的,可以不写

    • label: KEY 名字

    • linux,code: KEY 要模拟的按键。

  • ④、如果按键要支持连按的话要加入 autorepeat。

        打开 imx6ul-14x14-evk.dtsi ,根据上面的要求创建对应的设备节点,设备节点内容如下所示:

gpio-keys {compatible = "gpio-keys";#address-cells = <1>;#size-cells = <0>;autorepeat;key0 {labal = "GPIO Key Enter";linux,code = <KEY_ENTER>;gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;};
};
  • autorepeat 表示按键支持连按。

  • ALPHA 开发板 KEY 按键信息,名字设置为“GPIO Key Enter”,这里我们将开发板上的 KEY 按键设置为“EKY_ENTER”这个按键键值为28,也就是回车键,效果和键盘上的回车键一样。

  • 设置 KEY 所使用的 IO 为 GPIO1_IO18,一定要检查一下设备树看看此 GPIO 有没有被用到其他外设上,如果有的话要删除掉相关代码!

3.3 运行测试

        重新编译设备树,然后用新编译出来的 设备树dtb 启动 Linux 系统,系统启动以后查看/dev/input 目录,看看都有哪些文件,结果如下所示:

/ # ls -al /dev/input/
total 0
drwxr-xr-x    2 0        0               80 Jan 20 00:34 .
drwxr-xr-x    6 0        0             2880 Jan 20 00:34 ..
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0
crw-rw----    1 0        0          13,  65 Jan 20 00:34 event1

从上面可以看出存在 event1 这个文件,这个文件就是 KEY 对应的设备文件,使用hexdump 命令来查看/dev/input/event1 文件,输入如下命令:

hexdump /dev/input/event1

然后按下 ALPHA 开发板上的按键,终端输出如下所示内容:

/ # hexdump /dev/input/event1
0000000 157f 0019 80d4 000d 0001 001c 0001 0000
0000010 157f 0019 80d4 000d 0000 0000 0000 0000
0000020 1580 0019 001c 0001 0001 001c 0000 0000
0000030 1580 0019 001c 0001 0000 0000 0000 0000
0000040 1580 0019 f8a0 000b 0001 001c 0001 0000
0000050 1580 0019 f8a0 000b 0000 0000 0000 0000
0000060 1580 0019 0aef 000f 0001 001c 0000 0000
0000070 1580 0019 0aef 000f 0000 0000 0000 0000
0000080 1581 0019 7e19 0009 0001 001c 0001 0000
0000090 1581 0019 7e19 0009 0000 0000 0000 0000
00000a0 1581 0019 30e4 000c 0001 001c 0000 0000
00000b0 1581 0019 30e4 000c 0000 0000 0000 0000

        按下 KEY 按键以后会在终端上输出如上所示的信息,表示 Linux 内核的按键驱动工作正常。至于上述中内容的含义就参照前面2.4节中的介绍,进行分析。


         关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/135162.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#实现钉钉自定义机器人发送群消息帮助类

一、自定义机器人发送群消息使用场景 在企业中,针对一些关键指标内容(如每天的生产产量、每天的设备报警信息等信息),需要同时给多人分享,此时就可以将需要查看这些数据的人员都拉到一个群中,让群里的机器人将这些关键指标内容推送到群里即可【(目前已实现在钉钉群里创建…

Web 器学习笔记(基础)

Filter 过滤器 概念&#xff1a;表示过滤器&#xff0c;是 JavaWeb 三大组件&#xff08;Servlet、Filter、Listener&#xff09;之一 作用&#xff1a;顾名思义可以过滤资源的请求&#xff0c;并实现特殊的需求 Filter 接口及它核心的 doFilter() 方法&#xff08;执行前就是…

Excel 公式函数:学习基本示例

数据准备 对于本教程&#xff0c;我们将使用以下数据集。 家居用品预算 S / N项目数量价格小计价格适中吗&#xff1f;1芒果96002橘子312003番茄125004食用油565005汤力水133900 房屋建筑项目时间表 S/NITEM开始日期结束日期持续时间&#xff08;天&#xff09;1调查土地0…

000_差分信号

1.什么是差分信号 差分信号又叫做差模信号&#xff0c;使用差分信号传输时&#xff0c;需要2根信号线&#xff0c;这2根信号线的振幅相等&#xff0c;相位相反&#xff0c;通过2根信号线的电压差值来表示逻辑0和逻辑1。 差分信号表示逻辑值如下图&#xff1a; 2.差分信号的特…

IDEA2023.2.1中创建第一个Tomcat的web项目

首先&#xff0c;创建一个普通的java项目。点击【file】-【new】-【project】 创建一个TomcatDemo项目 创建如下图 添加web部门。点击【file】-【project structure】 选择【modules】-选中项目“TomcatDemo” 点击项目名上的加号【】&#xff0c;添加【web】模块 我们就会发现…

【Vue】快速入门案例与工作流程的讲解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Vue快速入门》。&#x1f…

Springboot 实践(18)Nacos配置中心参数自动刷新测试

前文讲解了Nacos 2.2.3配置中心的服务端的下载安装&#xff0c;和springboot整合nacos的客户端。Springboot整合nacos关键在于使用的jar版本要匹配&#xff0c;文中使用版本如下&#xff1a; ☆ springboot版本: 2.1.5.RELEASE ☆ spring cloud版本 Greenwich.RELEASE ☆ sp…

学信息系统项目管理师第4版系列09_配置管理

1. 配置管理 1.1. 应用技术的和管理的指导和监控方法以标识和说明配置项的功能和物理特征&#xff0c;控制这些特征的变更&#xff0c;记录和报告变更处理和实现状态并验证与规定的需求的遵循性 1.1.1. GB/T 11457《信息技术软件工程术语》 2. 配置项 2.1. Configuration I…

Scapy 解析 pcap 文件从HTTP流量中提取图片

Scapy 解析 pcap 文件从HTTP流量中提取图片 前言一、网络环境示例二、嗅探流量示例三、pcap 文件处理最后参考 ​ 作者&#xff1a;高玉涵 ​ 时间&#xff1a;2023.9.17 10:25 ​ 环境&#xff1a;Linux kali 5.15.0-kali3-amd64&#xff0c;Python 3.11.4&#xff0c;scapy…

线性代数的本质(二)——线性变换与矩阵

文章目录 线性变换与矩阵线性变换与二阶方阵常见的线性变换复合变换与矩阵乘法矩阵的定义列空间与基矩阵的秩逆变换与逆矩阵 线性变换与矩阵 线性变换与二阶方阵 本节从二维平面出发学习线性代数。通常选用平面坐标系 O x y Oxy Oxy &#xff0c;基向量为 i , j \mathbf i,…

什么是无人机全自动飞行系统?概念、构成、作用深度解析

无人机的工业化应用深入催生出新的痛点&#xff0c;无人机应用飞手培养难、成本高、技术参差不齐&#xff0c;以及应急响应和采集作业价值等没有得到充分释放&#xff0c;由此无人机自动飞行系统、无人机自动机场横空出世&#xff0c;因其无人化、自动化、无人机值守的应用特性…

【项目经验】:elementui多选表格默认选中

一.需求 在页面刚打开就默认选中指定项。 二.方法Table Methods toggleRowSelection用于多选表格&#xff0c;切换某一行的选中状态&#xff0c;如果使用了第二个参数&#xff0c;则是设置这一行选中与否&#xff08;selected 为 true 则选中&#xff09;row, selected 详细…

SSLRec:统一的自监督推荐算法库

论文链接&#xff1a; https://arxiv.org/pdf/2308.05697.pdf 论文代码&#xff1a; https://github.com/HKUDS/SSLRec TLDR 我们搭建了 SSLRec&#xff0c;一个统一的自监督推荐算法库。SSLRec 提供了一个标准化、灵活和全面的框架&#xff0c;用于整合不同场景下的推荐算法&a…

Vue2+Vue3

文章目录 Vue快速上手Vue是什么第一个Vue程序插值表达式Vue核心特性&#xff1a;响应式 Vue指令v-htmlv-show 与 v-ifv-else 与 v-else-ifv-onv-bindv-forv-model指令修饰符 计算属性watch侦听器&#xff08;监视器&#xff09;watch——简写watch——完整写法 Vue生命周期 和 …

网页的快捷方式打开自动全屏--Chrome、Firefox 浏览器相关设置

Firefox 的全屏方式与 Chrome 不同&#xff0c;Chrome 自带全屏模式以及APP模式&#xff0c;通过简单的参数即可设置&#xff0c;而Firefox暂时么有这个功能&#xff0c;Firefox 的全屏功能可以通过全屏插件实现。 全屏模式下&#xff0c;按 F11 不会退出全屏&#xff0c;鼠标…

GDB之(任意门)跳到任意位置(十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Pytorch-CNN-Mnist

文章目录 model.pymain.py网络设置注意事项及改进运行截图 model.py import torch.nn as nn class CNN_cls(nn.Module):def __init__(self,in_dim28*28):super(CNN_cls,self).__init__()self.conv1 nn.Conv2d(1,32,1,1)self.pool1 nn.MaxPool2d(2,2)self.conv2 nn.Conv2d(3…

Web 第一步:HTTP 协议(基础)

这里是JavaWeb的开头部分&#xff01;那么先解释一下吧&#xff1a; Web&#xff1a;全球广域网&#xff0c;也称为万维网&#xff08;www&#xff09;&#xff0c;能够通过浏览器访问的网站。 JavaWeb&#xff1a;是用Java技术来解决相关 Web 互联网领域的技术栈。 &#xf…

vite和webpack的区别

vite和webpack的区别 1、前言2、Webpack2.1 Webpack简述2.2 Webpack常用插件 3、Vite3.1 Vite简述3.2 Vite插件推荐 4、区别4.1 开发模式不同4.2 打包效率不同4.3 插件生态不同4.4 配置复杂度不同4.5 热更新机制不同 5、总结 1、前言 Webpack和Vite是现代前端开发中非常重要的…

深度解析shell脚本的命令的原理之ls

ls是一个常用的Unix和Linux命令&#xff0c;它的功能是列出目录内容。当运行ls命令时&#xff0c;操作系统会执行一系列步骤&#xff0c;以获取和显示指定目录中的文件和子目录。以下是对这个命令的深度解析&#xff1a; 解析参数和选项&#xff1a;首先&#xff0c;ls命令会解…