2. GPIO输入读取按键状态
任务目标
在瑞芯微RK3588平台上,通过GPIO子系统读取一个按键的电平状态,检测按键按下与释放动作,并在终端打印状态变化信息。
硬件连接
将按键一端连接至RK3588开发板的GPIO引脚(本案例使用GPIO4_B5,对应引脚编号为gpio-157),另一端接地。按键按下时引脚读到低电平,释放时读到高电平(需启用内部上拉)。
实现代码
以下代码通过字符设备驱动方式实现按键读取,使用Linux GPIO子系统标准接口,无需依赖特定平台头文件,可直接编译加载。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#define KEY_GPIO_NUM 157 /* GPIO4_B5: 4*32 + 4*8 + 5 = 157 */
static struct gpio_desc *key_gpio;
static int key_state = 1; /* 当前按键状态,1=释放,0=按下 */
static int key_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
char value;
int state;
state = gpiod_get_value(key_gpio);
if (state < 0) {
pr_err("Failed to read GPIO value\n");
return -EIO;
}
value = state ? '1' : '0';
if (copy_to_user(buf, &value, 1))
return -EFAULT;
return 1;
}
static const struct file_operations key_fops = {
.owner = THIS_MODULE,
.read = key_read,
};
static struct miscdevice key_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "key_status",
.fops = &key_fops,
};
static int key_probe(struct platform_device *pdev)
{
int ret;
key_gpio = gpio_to_desc(KEY_GPIO_NUM);
if (!key_gpio) {
pr_err("Invalid GPIO number %d\n", KEY_GPIO_NUM);
return -EINVAL;
}
ret = gpiod_direction_input(key_gpio);
if (ret) {
pr_err("Failed to set GPIO as input\n");
return ret;
}
/* 启用内部上拉,按键按下时读到低电平 */
gpiod_set_value_cansleep(key_gpio, 1);
ret = gpiod_direction_input(key_gpio);
if (ret) {
pr_err("Failed to enable pull-up\n");
return ret;
}
ret = misc_register(&key_miscdev);
if (ret) {
pr_err("Failed to register misc device\n");
return ret;
}
pr_info("Key driver initialized, GPIO %d\n", KEY_GPIO_NUM);
return 0;
}
static int key_remove(struct platform_device *pdev)
{
misc_deregister(&key_miscdev);
gpiod_put(key_gpio);
pr_info("Key driver removed\n");
return 0;
}
static const struct of_device_id key_of_match[] = {
{ .compatible = "rk3588,key-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, key_of_match);
static struct platform_driver key_driver = {
.probe = key_probe,
.remove = key_remove,
.driver = {
.name = "rk3588_key_demo",
.of_match_table = key_of_match,
},
};
module_platform_driver(key_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("RK3588 Driver Course");
MODULE_DESCRIPTION("RK3588 GPIO Key Input Demo");
设备树配置
在设备树中为驱动添加匹配节点(需根据实际使用的GPIO调整引脚配置):
/ {
key_demo {
compatible = "rk3588,key-demo";
status = "okay";
};
};
编译与加载
- 将上述代码保存为
key_demo.c,放入内核源码drivers/misc/目录,并在该目录的Kconfig和Makefile中添加对应配置项。 - 编译内核模块:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=drivers/misc/ modules - 将生成的
key_demo.ko拷贝到开发板,执行:insmod key_demo.ko - 查看内核日志确认加载成功:
dmesg | tail,应看到Key driver initialized, GPIO 157。
验证方法
- 加载驱动后,执行
cat /dev/key_status,终端应输出1(按键释放状态)。 - 按住按键不放,再次执行
cat /dev/key_status,终端应输出0(按键按下状态)。 - 连续执行多次读取命令,观察输出值随按键状态变化,说明GPIO电平读取正常。
通过条件:按键按下时读到0,释放时读到1,且驱动加载无错误日志。
常见问题
- 读取值始终为1:检查按键是否接对引脚,确认GPIO编号是否正确(可用
cat /sys/kernel/debug/gpio查看引脚占用情况)。 - 读取值始终为0:可能是引脚被其他驱动占用,或按键接线错误导致引脚直接接地。
- insmod报错:确认设备树节点已正确添加,且内核开启了CONFIG_OF支持。