设计板需要提供六个按键进行人机交互,起初准备使用CH455G键盘扫描芯片,设计键盘电路
随着旋转编码器Linux内核驱动的调试成功
Linux 输入设备调试详解(零基础开发)Rotary_Encoder旋转编码器模块(EC11)通用GPIO为例 挂载input输入子系统
对于更加简单,参考资料更加多的按键输入,可以尝试使用Linux内核驱动来解决,减少外围电路的设计负担,以及调试i2c的痛苦
有了之前调试旋转编码器的经验,这次也是信心十足,但整个过程还是花费了三天左右的时间,并且中间犯了一个常识性错误
对于按键,先准备好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
搜索到了我们需要的普通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.c
,gpio_keys.c
采用了platform 驱动框架,在KEY 驱动上使用了input 子系统实现。
nano drivers/input/keyboard/gpio_keys.c
找到了驱动,按照调试内核驱动的老规矩,首先我们在内核驱动中先不开启gpio-keys驱动,而是提取出来编译成ko模块进行测试
所以在make menuconfig
找到驱动的位置后,取消掉驱动,然后编译,生成zImage
文件
现在就可以把dtb文件和zImage文件烧录进板子了
在编译驱动之前,先给驱动加上各种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
nano /proc/bus/input/devices
可以看到我们gpio-keys
在input节点下,可以看到我们的event2
从驱动加载情况上来看,驱动正常运行,没有问题
这里测试按键的输入有好多种方法,这里列举几个
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的电平究竟发生了什么
在对按键进行拉高之后,按键程序立刻有了输入