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

Posted “逛丢一只鞋”

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 输入设备 自定义键盘 input输入子系统 gpio-keys按键驱动相关的知识,希望对你有一定的参考价值。

前言

设计板需要提供六个按键进行人机交互,起初准备使用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的电平究竟发生了什么

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

以上是关于Linux 输入设备 自定义键盘 input输入子系统 gpio-keys按键驱动的主要内容,如果未能解决你的问题,请参考以下文章

Linux/Android——input子系统核心

Linux系统编程应用 Linux Input子系统

[vue]利用自定义指令处理系统键盘遮挡input输入框

input子系统

Linux学习:输入子系统 input

vue+element 中 el-input框 限制 只能输入数字及几位小数(自定义)和输入框之键盘