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";
    };
};

编译与加载

  1. 将上述代码保存为key_demo.c,放入内核源码drivers/misc/目录,并在该目录的Kconfig和Makefile中添加对应配置项。
  2. 编译内核模块:make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- M=drivers/misc/ modules
  3. 将生成的key_demo.ko拷贝到开发板,执行:insmod key_demo.ko
  4. 查看内核日志确认加载成功:dmesg | tail,应看到Key driver initialized, GPIO 157。

验证方法

  1. 加载驱动后,执行cat /dev/key_status,终端应输出1(按键释放状态)。
  2. 按住按键不放,再次执行cat /dev/key_status,终端应输出0(按键按下状态)。
  3. 连续执行多次读取命令,观察输出值随按键状态变化,说明GPIO电平读取正常。

通过条件:按键按下时读到0,释放时读到1,且驱动加载无错误日志。

常见问题

  • 读取值始终为1:检查按键是否接对引脚,确认GPIO编号是否正确(可用cat /sys/kernel/debug/gpio查看引脚占用情况)。
  • 读取值始终为0:可能是引脚被其他驱动占用,或按键接线错误导致引脚直接接地。
  • insmod报错:确认设备树节点已正确添加,且内核开启了CONFIG_OF支持。