文章目录
前言
设计板需要提供六个按键进行人机交互,起初准备使用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.c
,gpio_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的电平究竟发生了什么
在对按键进行拉高之后,按键程序立刻有了输入