4. GPIO 操作 API:申请、方向与读写

在 Linux 内核中,GPIO(General Purpose Input/Output)是最基础、最常用的硬件资源之一。无论是点亮 LED、读取按键状态,还是驱动外设模块,都离不开对 GPIO 引脚的精确控制。内核提供了一套标准化的 GPIO 操作 API,帮助驱动开发者以统一、安全的方式管理 GPIO 资源。本章将系统讲解 GPIO 的申请/释放、方向设置、值读写这三大类核心 API,并结合按键中断场景说明其典型用法。

学习提示: 本章所有 API 均基于 Linux 内核的 gpiolib 框架,适用于大多数主流平台(ARM、x86、RISC-V 等)。掌握这些 API 是编写 GPIO 驱动的基础,也是后续学习中断、输入子系统的前提。

4.1 GPIO 操作 API 全景

下表列出了 GPIO 操作中最常用的六个 API 函数,它们分别对应资源的生命周期管理、方向配置和电平读写。在实际驱动中,这些函数通常成对出现,形成规范的“申请-配置-使用-释放”流程。

分类 函数原型 功能说明 返回值
资源管理 gpio_request(unsigned gpio, const char *label) 申请一个 GPIO 引脚,标记其已被使用 0 成功;负值错误码
gpio_free(unsigned gpio) 释放之前申请的 GPIO 引脚 void
方向设置 gpio_direction_input(unsigned gpio) 将 GPIO 设置为输入方向 0 成功;负值错误码
gpio_direction_output(unsigned gpio, int value) 将 GPIO 设置为输出方向,并设置初始电平 0 成功;负值错误码
电平读写 gpio_get_value(unsigned gpio) 读取 GPIO 引脚的当前电平(0 或 1) 0 或 1(非负)
gpio_set_value(unsigned gpio, int value) 设置 GPIO 引脚的输出电平(0 或 1) void
重要提醒: 在调用 gpio_direction_input 或 gpio_direction_output 之前,必须先调用 gpio_request 成功申请该 GPIO。未申请直接设置方向会导致内核警告或驱动行为异常。

4.2 GPIO 资源管理:申请与释放

GPIO 是系统共享资源,多个驱动模块可能竞争同一引脚。内核通过 gpio_request 和 gpio_free 实现资源独占访问。申请成功后,其他驱动再对该 GPIO 调用 gpio_request 会返回 -EBUSY,从而避免冲突。

4.2.1 gpio_request 详解

  • 参数 gpio: GPIO 编号,通常由硬件平台定义,例如 32 或 GPIO_3_5(平台相关宏)。
  • 参数 label: 用于调试的字符串标签,通常填写驱动名称或功能描述,如 "key_interrupt"。
  • 典型错误:
    • -EINVAL:GPIO 编号无效或超出平台支持范围。
    • -EBUSY:该 GPIO 已被其他驱动申请。

4.2.2 gpio_free 使用规范

释放 GPIO 通常在驱动卸载或错误处理路径中调用。释放后,该 GPIO 可被其他模块重新申请。建议在驱动结构体中记录已申请的 GPIO 编号,以便在清理时统一释放。

/* 申请 GPIO 示例 */
int ret;
ret = gpio_request(KEY_GPIO, "my_key");
if (ret < 0) {
    pr_err("Failed to request GPIO %d: %d\n", KEY_GPIO, ret);
    return ret;
}

/* 使用 GPIO ... */

/* 释放 GPIO */
gpio_free(KEY_GPIO);

4.3 GPIO 方向设置:输入与输出

方向设置决定了 GPIO 引脚在电气特性上是读取外部信号还是驱动外部负载。对于按键中断场景,GPIO 必须配置为输入方向;对于 LED 控制,则需配置为输出方向。

4.3.1 gpio_direction_input

将 GPIO 设置为输入模式。调用后,可以通过 gpio_get_value 读取引脚电平。对于按键驱动,通常在 probe 函数中完成此设置。

/* 将 GPIO 设置为输入 */
ret = gpio_direction_input(KEY_GPIO);
if (ret < 0) {
    pr_err("Failed to set GPIO %d as input: %d\n", KEY_GPIO, ret);
    gpio_free(KEY_GPIO);
    return ret;
}

4.3.2 gpio_direction_output

将 GPIO 设置为输出模式,并同时指定初始输出电平。第二个参数 value 为 0 表示输出低电平,非 0 表示输出高电平。此函数在初始化 LED、蜂鸣器等输出设备时非常有用。

/* 将 GPIO 设置为输出,初始输出高电平 */
ret = gpio_direction_output(LED_GPIO, 1);
if (ret < 0) {
    pr_err("Failed to set GPIO %d as output: %d\n", LED_GPIO, ret);
    gpio_free(LED_GPIO);
    return ret;
}

4.4 GPIO 电平读写:获取与设置

电平读写是 GPIO 操作的核心交互方式。输入场景下读取外部电平,输出场景下控制外部设备。

4.4.1 gpio_get_value

返回指定 GPIO 引脚的当前电平值。返回值 0 表示低电平,1 表示高电平。此函数可以在中断上下文或进程上下文中调用,但需注意在中断上下文中避免睡眠。

/* 读取按键 GPIO 电平 */
int val = gpio_get_value(KEY_GPIO);
pr_info("Key GPIO %d value: %d\n", KEY_GPIO, val);

4.4.2 gpio_set_value

设置指定 GPIO 引脚的输出电平。第二个参数 value 为 0 输出低电平,非 0 输出高电平。此函数同样可在中断上下文中使用,但应避免频繁调用导致性能开销。

/* 设置 LED GPIO 输出高电平(点亮 LED) */
gpio_set_value(LED_GPIO, 1);

/* 设置 LED GPIO 输出低电平(熄灭 LED) */
gpio_set_value(LED_GPIO, 0);

4.5 GPIO 操作流程:从申请到释放

下图展示了 GPIO 操作的完整生命周期,从资源申请到最终释放,每个步骤都对应一个 API 调用。在按键中断驱动中,此流程通常发生在 probe 和 remove 函数中。

GPIO 操作完整生命周期流程图 gpio_request() gpio_direction_input/output() gpio_get_value/set_value() request_irq() 注册中断 gpio_free() 关键说明 • 必须先 request 再设方向 • 中断注册在方向设置之后 • 释放前需先注销中断 按键中断场景 • 方向设为输入 • 中断触发后读取电平

4.6 按键中断驱动中的 GPIO 操作示例

以下代码片段展示了一个按键中断驱动中 GPIO 操作的完整流程。该驱动在 probe 中申请 GPIO、设置为输入、注册中断;在 remove 中注销中断、释放 GPIO。

#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>

#define KEY_GPIO    32      /* 假设按键连接在 GPIO 32 */
#define KEY_IRQ      gpio_to_irq(KEY_GPIO)

static irqreturn_t key_isr(int irq, void *dev_id)
{
    int val = gpio_get_value(KEY_GPIO);
    pr_info("Key interrupt! GPIO value: %d\n", val);
    return IRQ_HANDLED;
}

static int __init key_drv_init(void)
{
    int ret;

    /* 1. 申请 GPIO */
    ret = gpio_request(KEY_GPIO, "key_interrupt");
    if (ret < 0) {
        pr_err("gpio_request failed: %d\n", ret);
        return ret;
    }

    /* 2. 设置方向为输入 */
    ret = gpio_direction_input(KEY_GPIO);
    if (ret < 0) {
        pr_err("gpio_direction_input failed: %d\n", ret);
        gpio_free(KEY_GPIO);
        return ret;
    }

    /* 3. 注册中断 */
    ret = request_irq(KEY_IRQ, key_isr,
                      IRQF_TRIGGER_FALLING,
                      "key_interrupt", NULL);
    if (ret < 0) {
        pr_err("request_irq failed: %d\n", ret);
        gpio_free(KEY_GPIO);
        return ret;
    }

    pr_info("Key driver initialized successfully\n");
    return 0;
}

static void __exit key_drv_exit(void)
{
    /* 4. 注销中断 */
    free_irq(KEY_IRQ, NULL);

    /* 5. 释放 GPIO */
    gpio_free(KEY_GPIO);

    pr_info("Key driver exited\n");
}

module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
代码要点:
  • gpio_to_irq() 将 GPIO 编号转换为对应的中断号,这是注册中断前的必要步骤。
  • 中断触发标志 IRQF_TRIGGER_FALLING 表示下降沿触发,适用于按键按下时电平从高变低的场景。
  • 错误处理路径中,每步失败都要释放之前已申请的资源,避免资源泄漏。

4.7 常见错误与调试建议

错误场景 可能原因 解决方案
gpio_request 返回 -EBUSY 该 GPIO 已被其他驱动或内核模块占用 检查 /sys/kernel/debug/gpio 查看占用情况;确认 GPIO 编号是否正确
gpio_direction_input 返回 -EINVAL GPIO 未申请或编号超出范围 确保先调用 gpio_request;核对平台 GPIO 映射表
中断不触发 GPIO 方向未设为输入;中断触发类型配置错误 检查 gpio_direction_input 是否调用;确认硬件电平变化与触发标志匹配
gpio_get_value 始终返回 0 或 1 GPIO 内部上拉/下拉配置不当;硬件连接错误 检查原理图;使用万用表测量引脚电平;考虑配置内部上拉/下拉

4.8 本章小结

本章系统介绍了 Linux 内核中 GPIO 操作的六大核心 API,包括资源管理(gpio_request/gpio_free)、方向设置(gpio_direction_input/gpio_direction_output)和电平读写(gpio_get_value/gpio_set_value)。通过按键中断驱动的完整示例,展示了这些 API 在实际开发中的组合使用方式。掌握这些基础操作,是后续学习 GPIO 中断、输入子系统、设备树 GPIO 描述等高级主题的必备前提。