Linux 输入设备 自定义键盘 input输入子系统 gpio-keys按键驱动

27 篇文章 5 订阅

前言

设计板需要提供六个按键进行人机交互,起初准备使用CH455G键盘扫描芯片,设计键盘电路

在这里插入图片描述

随着旋转编码器Linux内核驱动的调试成功

Linux 输入设备调试详解(零基础开发)Rotary_Encoder旋转编码器模块(EC11)通用GPIO为例 挂载input输入子系统

对于更加简单,参考资料更加多的按键输入,可以尝试使用Linux内核驱动来解决,减少外围电路的设计负担,以及调试i2c的痛苦

有了之前调试旋转编码器的经验,这次也是信心十足,但整个过程还是花费了三天左右的时间,并且中间犯了一个常识性错误

查看include/uapi/linux/input.h

对于按键,先准备好input.h文件

include/uapi/linux/input.h

在这里插入图片描述
在这里插入图片描述

设备树添加

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

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

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

③、所有的KEY 都是gpio-keys 的子节点,每个子节点可以用如下属性描述自己:
gpios:KEY 所连接的GPIO 信息。
interrupts:KEY 所使用GPIO 中断信息,不是必须的,可以不写。
label:KEY 名字
linux,code:KEY 要模拟的按键,也就是示例代码58.1.2.4 中的这些按键。

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

在设备树中添加所需

	gpio-keys {
		compatible = "gpio-keys";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_gpio_keys>;
		autorepeat;

		key-up {
			label = "key-up";
			gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
			gpio-key,wakeup;
			linux,code = <KEY_UP>;							// 103
		};

		key-down {
			label = "key-down";
			gpios = <&gpio1 4 GPIO_ACTIVE_LOW>;
			gpio-key,wakeup;
			linux,code = <KEY_DOWN>;						// 108
		};

		key-left {
			label = "key-left";
			gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
			gpio-key,wakeup;
			linux,code = <KEY_LEFT>;						// 105
		};

		key-right {
			label = "key-right";
			// gpios = <&gpio5 30 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_CSI0_DAT12__GPIO5_IO30
			// gpios = <&gpio6 1 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_CSI0_DAT15__GPIO6_IO01
			gpios = <&gpio2 20 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_EIM_A18__GPIO2_IO20
			gpio-key,wakeup;	
			linux,code = <KEY_RIGHT>;						// 106
		};

		key-enter {
			label = "key-enter";
			// gpios = <&gpio5 31 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_CSI0_DAT13__GPIO5_IO31
			// gpios = <&gpio6 2 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_CSI0_DAT16__GPIO6_IO02
			gpios = <&gpio2 18 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_EIM_A20__GPIO2_IO18
			gpio-key,wakeup;	
			linux,code = <KEY_ENTER>;						// 28
		};

		key-esc {
			label = "key-esc";
			// gpios = <&gpio6 0 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_CSI0_DAT14__GPIO6_IO00
			// gpios = <&gpio6 3 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_CSI0_DAT17__GPIO6_IO03
			gpios = <&gpio2 17 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_EIM_A21__GPIO2_IO17	
			gpio-key,wakeup;
			linux,code = <KEY_ESC>;							// 1
		};
		/*EC11按键添加*/
		key-ec11 {
			label = "key-ec11";
			gpios = <&gpio2 22 GPIO_ACTIVE_LOW>;			// MX6QDL_PAD_EIM_A16__GPIO2_IO22	
			gpio-key,wakeup;
			linux,code = <KEY_1>;							// 2
		};

	};

注意:KEY 按键信息,名字设置为“key-enter”,对于这个按键linux,code = <KEY_ENTER>;,也就是回车键,效果和键盘上的回车键一样。KEY_ENTER的宏定义就在之前所说include/uapi/linux/input.h

添加pinctrl子系统信息

		pinctrl_gpio_keys: gpio_keysgrp {
			fsl,pins = <
				MX6QDL_PAD_EIM_D29__GPIO3_IO29 0x1b0b0
				MX6QDL_PAD_GPIO_4__GPIO1_IO04  0x1b0b0
				MX6QDL_PAD_GPIO_5__GPIO1_IO05  0x1b0b0

				/* 2021.07.19
				 * 添加三个key 右、确定、取消
				 */
				MX6QDL_PAD_EIM_A18__GPIO2_IO20  0x1b0b0
				MX6QDL_PAD_EIM_A20__GPIO2_IO18  0x1b0b0
				MX6QDL_PAD_EIM_A21__GPIO2_IO17  0x1b0b0

				/*
				 * 2021.07.21
				 * 添加 EC11按键
				 */
				MX6QDL_PAD_EIM_A16__GPIO2_IO22      			0x1b0b0		
			>;
		};

保存,编译dtb

make dtbs -32

在这里插入图片描述
在输出信息中可以看到,生成了我需要的imx6q-c-sabresd.dtb设备树文件

make menuconfig驱动

make menuconfig

搜索到了我们需要的普通GPIO的按键驱动
在这里插入图片描述

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

在这里插入图片描述
选中以后就会在.config 文件中出现“CONFIG_KEYBOARD_GPIO=y”这一行,Linux 内核就会根据这一行来将KEY 驱动文件编译进Linux 内核。Linux 内核自带的KEY 驱动文件为drivers/input/keyboard/gpiokeys.cgpio_keys.c 采用了platform 驱动框架,在KEY 驱动上使用了input 子系统实现。

nano drivers/input/keyboard/gpio_keys.c

在这里插入图片描述
找到了驱动,按照调试内核驱动的老规矩,首先我们在内核驱动中先不开启gpio-keys驱动,而是提取出来编译成ko模块进行测试

所以在make menuconfig 找到驱动的位置后,取消掉驱动,然后编译,生成zImage文件

现在就可以把dtb文件和zImage文件烧录进板子了

编译ko模块

在编译驱动之前,先给驱动加上各种printk打印信息
在这里插入图片描述
处理完驱动,然后编写makefile

#	                 ,%%%%%%%%,
#	               ,%%/\%%%%/\%%
#	              ,%%%\c''''J/%%%
#	    %.        %%%%/ o  o \%%%
#	    `%%.      %%%%       |%%%
#	     `%%      `%%%%(__Y__)%%'
#	     //        ;%%%%`\-/%%%'
#	     ((      /   `%%%%%%%'
#	      \\     .'           |
#	       \\   /        \  | |
#	        \\/          ) | |
#	         \          /_ | |__
#	         (____________))))))) 攻城狮 

# 调试驱动和应用程序用Makefile

# 编译模块

# 开发板Linux内核的实际路径 
# KDIR变量
KDIR:=/work/linux-4.1.15

#  获取当前目录
PWD:=$(shell pwd)

# obj-m表示将 chrdevbase.c这个文件 编译为 name.ko模块。
obj-m += gpio_keys.o

# 编译成模块
all:
	make -C $(KDIR) M=$(PWD) modules
clean:
	make -C $(KDIR) M=$(PWD) clean


大功告成,执行make,编译成ko模块
在这里插入图片描述
发送到板子

scp gpio_keys.ko root@192.168.0.232:/work

在这里插入图片描述
在这里插入图片描述

查看驱动运行状态

运行ko文件
在这里插入图片描述
没有任何错误,非常的完美,运气非常的不错

但是为了谨慎,还是要查看驱动的加载情况

查看/proc/bus/input/devices设备

查看/proc/bus/input/devices

nano /proc/bus/input/devices

可以看到我们gpio-keys
在这里插入图片描述

查看dev/input

在input节点下,可以看到我们的event2
在这里插入图片描述
从驱动加载情况上来看,驱动正常运行,没有问题

按键测试

这里测试按键的输入有好多种方法,这里列举几个

hexdump

hexdump /dev/input/event2

可以看到信号的输入,但是没有办法直观的判断是哪个按键的
在这里插入图片描述

应用程序

这里有一个简单的应用程序


//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//            佛祖保佑       永不宕机     永无BUG                 //


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
       struct input_event in_ev = {0};
       int fd = -1;

       /* 校验传参 */
       if (2 != argc) {
              fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
              exit(-1);
       }

       /* 打开文件 */
       if (0 > (fd = open(argv[1], O_RDONLY))) {
              perror("open error");
              exit(-1);
       }

       for ( ; ; ) {

              /* 循环读取数据 */
              if (sizeof(struct input_event) !=
                read(fd, &in_ev, sizeof(struct input_event))) {
                     
                     perror("read error");
                     exit(-1);
              }

              printf("type:%d code:%d value:%d\n",
                in_ev.type, in_ev.code, in_ev.value);
       }
}

执行交叉编译

$CC gpio_key_app.c -o gpio_key_app

在这里插入图片描述

通过网络发送到板子上

scp gpio_key_app root@192.168.0.232:/work

在这里插入图片描述
运行应用程序

 ./gpio_key_app /dev/input/event2

按动我们的按键,就可以看到按键的数据
在这里插入图片描述
为了可以更直观一些,可以使用这个应用程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#define NOKEY 0

int main()
{
	int keys_fd;	
        char ret[2]; 
	struct input_event t;
	char *dev;

    setvbuf(stdout, (char *)NULL, _IONBF, 0);//disable stdio out buffer;
//	dev = getenv("KEYPAD_DEV");
      
    keys_fd = open("/dev/input/keypad", O_RDONLY);
	if(keys_fd<=0)
	{
        printf("open %s device error!\n",dev);
		return 0;
	}

	while(1)
	{	
            if(read(keys_fd,&t,sizeof(t))==sizeof(t)) {
		    if(t.type==EV_KEY)
			if(t.value==0 || t.value==1)
			{
				//printf("%d \n", t.code);
				switch(t.code)
				{
					case 103:
			    		printf("key103 key-up %s\n",(t.value)?"Presse":"Released");
			    	break;
			    	
			    	case 108:
			    		printf("key108 key-down %s\n",(t.value)?"Pressed":"Released");
			    	break;
			    	
			    	case 106:
			    		printf("key106 key-right %s\n",(t.value)?"pressed":"Released");
			    	break;  

					case 105:
						printf("key105 key-left %s\n",(t.value)?"Released":"Pressed");
					break;
			    	
			    	case 28:
			    		printf("key28 key-enter %s\n",(t.value)?"Pressed":"Released");
			    	break;
			    	
			    	case 1:
			    		printf("key1 key-esc %s\n",(t.value)?"Released":"Pressed");
			        break;

					case 2:
			    		printf("key2 key-ec11 %s\n",(t.value)?"Released":"Pressed");
			        break;
	    	
			    	default:
			    		break;
			    }
			}
		}
	}	
	close(keys_fd);
	
        return 0;
}


编译

$CC key_app.c -o key_app

下载

 scp key_app root@192.168.0.232:/work

在这里插入图片描述
这个应用程序中已经调用了键盘的设备节点,所以直接打开就可以使用

 ./key_app

在这里插入图片描述

过程中问题及解决方法

在实际调试过程中显然没有这么顺利,过程中遇到了很多的问题,这里集中在一起进行记录

常见问题

①、是否使能Linux 内核KEY 驱动。

②、设备树中gpio-keys 节点是否创建成功。

③、在设备树中是否有其他外设也使用了KEY 按键对应的GPIO,但是我们并没有删除掉这些外设信息。检查Linux 启动log 信息,看看是否有类似下面这条信息:

gpio-keys gpio_keys:Failed to request GPIO 18, error -16

上述信息表示GPIO 18 申请失败,失败的原因就是有其他的外设正在使用此GPIO。

正常则是
在这里插入图片描述

按键硬件注意

对于按键的使用,这里一定要注意,需要拉高使用,拉高之后,电平才会稳定,按键的电平监测也会稳定,也不容易出现干扰
在这里插入图片描述
起初我就是没有意识到按键电平需要拉高的问题,板子上很多GPIO没有办法很好的按照理论拉高,还有就是因为使用了内核驱动,所以对于GPIO的内部操作是黑箱操作,不知道GPIO的电平究竟发生了什么

在对按键进行拉高之后,按键程序立刻有了输入

  • 11
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在 gpio-keyc的基础上改写的旋转按钮 ec11的驱动,已经经过了测试。可自定义左旋和右旋的键值。如果按键输入是第三个引脚,也支持按键操作 补存的头文件: gpio_ec11.h #ifndef _GPIO_EC11_H #define _GPIO_EC11_H struct device; struct gpio_desc; /** * struct gpio_ec11_button - configuration parameters * @leftcode: ec11 left direction input event code (KEY_*, SW_*) * @rightcode: ec11 right direction input event code (KEY_*, SW_*) * @gpio: %-1 if this key does not support gpio * @gpio: %-1 if this key does not support gpio * @active_low: %true indicates that button is considered * depressed when gpio is low * @desc: label that will be attached to button's gpio * @type: input event type (%EV_KEY, %EV_SW, %EV_ABS) * @wakeup: configure the button as a wake-up source * @debounce_interval: debounce ticks interval in msecs * @can_disable: %true indicates that userspace is allowed to * disable button via sysfs * @value: axis value for %EV_ABS * @irq: Irq number in case of interrupt keys * @gpiod: GPIO descriptor */ struct gpio_ec11_button { unsigned int code; unsigned int leftcode; /*记录左旋键值*/ unsigned int rightcode; /*记录右旋键值*/ int gpio; /*旋转编码器A引脚的gpio号*/ int subgpio; /*旋转编码器B引脚的gpio号*/ int active_low; const char *desc; unsigned int type; int wakeup; int debounce_interval; bool can_disable; int value; unsigned int irq; unsigned int irq_flags; struct gpio_desc *gpiod; }; /** * struct gpio_ec11_platform_data - platform data for gpio_ec11 driver * @buttons: pointer to array of &gpio;_keys_button structures * describing buttons attached to the device * @nbuttons: number of elements in @buttons array * @poll_interval: polling interval in msecs - for polling driver only * @rep: enable input subsystem auto repeat * @enable: platform hook for enabling the device * @disable: platform hook for disabling the device * @name: input device name */ struct gpio_ec11_platform_data { struct gpio_ec11_button *buttons; int nbuttons; unsigned int poll_interval; unsigned int rep:1; int (*enable)(
gpio-keys 是 Linux 内核中的一个模块,用于将 GPIO 按键映射为键盘上的按键,以便用户可以使用 GPIO 按键来与系统进行交互。在本文中,我将对 gpio-keys 模块的代码进行分析。 首先,我们需要了解的是 gpio-keys 模块的注册和注销过程。在模块初始化期间,我们需要调用 `gpio_keys_probe()` 函数来注册模块,该函数会注册一个 platform 设备,并将其与 gpio_keys_driver 结构体相关联。这个结构体包含了模块的名称、ID、设备树匹配以及一些回调函数。注册完成后,内核就会调用 `gpio_keys_irq()` 函数来设置 GPIO 中断并处理按键事件。 下面是 `gpio_keys_probe()` 函数的代码: ```c static int gpio_keys_probe(struct platform_device *pdev) { struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; struct gpio_keys_button *button; struct input_dev *input; int error, i; ... /* Allocate input device */ input = input_allocate_device(); if (!input) { dev_err(&pdev->dev, "Failed to allocate input device\n"); error = -ENOMEM; goto err_free_desc; } /* Set input device properties */ input->name = pdata->input_name ?: "gpio-keys"; input->dev.parent = &pdev->dev; set_bit(EV_KEY, input->evbit); for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { input_set_capability(input, EV_KEY, button->code); } /* Register input device */ error = input_register_device(input); if (error) { dev_err(&pdev->dev, "Failed to register input device\n"); goto err_free_dev; } /* Allocate and configure gpio_keys_device */ gkd = devm_kzalloc(&pdev->dev, sizeof(*gkd), GFP_KERNEL); if (!gkd) { error = -ENOMEM; goto err_free_dev; } gkd->pdev = pdev; gkd->input = input; gkd->n_buttons = pdata->nbuttons; gkd->buttons = pdata->buttons; /* Request and configure GPIOs */ for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { error = gpio_request(button->gpio, button->desc); if (error) { dev_err(&pdev->dev, "Failed to request gpio %d: %d\n", button->gpio, error); goto err_free_gpio; } error = gpio_direction_input(button->gpio); if (error) { dev_err(&pdev->dev, "Failed to configure gpio %d: %d\n", button->gpio, error); goto err_free_gpio; } } /* Register IRQ handlers */ for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { error = gpio_request(button->gpio, button->desc); if (error) { dev_err(&pdev->dev, "Failed to request gpio %d: %d\n", button->gpio, error); goto err_free_irq; } error = request_irq(gpio_to_irq(button->gpio), gpio_keys_irq, button->irqflags, button->desc, gkd); if (error) { dev_err(&pdev->dev, "Failed to register IRQ for gpio %d: %d\n", button->gpio, error); goto err_free_irq; } } /* Store private data */ platform_set_drvdata(pdev, gkd); return 0; err_free_irq: while (--i >= 0) { button--; free_irq(gpio_to_irq(button->gpio), gkd); } goto err_free_gpio; err_free_gpio: while (--i >= 0) { button--; gpio_free(button->gpio); } err_free_dev: input_free_device(input); err_free_desc: for (i = 0, button = pdata->buttons; i < pdata->nbuttons; i++, button++) { kfree(button->desc); } return error; } ``` 在 `gpio_keys_probe()` 函数中,我们首先为输入设备分配内存,然后设置输入设备的属性,如名称、上级设备、事件类型和按键能力。接下来,我们为每个按键分配 GPIO,并将其配置为输入模式。最后,我们为每个按键注册中断处理程序,并将私有数据存储在 platform 设备的私有数据区域中。 一旦模块已注册并初始化,内核就可以使用 `gpio_keys_irq()` 函数来处理按键事件。该函数会检查哪个按键被按下或释放,并将事件发送到输入子系统。 下面是 `gpio_keys_irq()` 函数的代码: ```c static irqreturn_t gpio_keys_irq(int irq, void *dev_id) { struct gpio_keys_device *gkd = dev_id; struct gpio_keys_button *button; struct input_dev *input = gkd->input; unsigned int state; int i; /* Check each button */ for (i = 0, button = gkd->buttons; i < gkd->n_buttons; i++, button++) { state = gpio_get_value(button->gpio); if (state != button->active_low) continue; /* Send event to input subsystem */ input_report_key(input, button->code, 1); input_sync(input); input_report_key(input, button->code, 0); input_sync(input); } return IRQ_HANDLED; } ``` 在 `gpio_keys_irq()` 函数中,我们遍历每个按键并检查其状态。如果按键被按下,则我们发送 “按下” 事件;如果按键被释放,则我们发送 “释放” 事件。最后,我们将事件同步到输入子系统。 总的来说,gpio-keys 模块是一个非常有用的内核模块,它允许用户通过 GPIO 按键与系统进行交互。通过分析其代码,我们可以更好地了解内核模块是如何工作的,并且可以更好地理解 linux 内核的编程模式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值