3. GPIO 子系统概述:硬件原理与软件架构

GPIO(General Purpose Input/Output,通用输入输出)是嵌入式系统中最基础、最常用的外设接口。本章从硬件电气原理出发,逐步深入到 Linux GPIO 子系统的软件分层设计,帮助您建立从“引脚电平”到“驱动框架”的完整认知。

3.1 GPIO 硬件原理

GPIO 引脚在硬件层面是一个可编程的三态引脚,通过内部寄存器配置,可以在以下三种状态间切换:

工作模式 内部结构 典型应用
推挽输出 PMOS + NMOS 互补输出级 驱动 LED、蜂鸣器、继电器
开漏输出 仅 NMOS,需外部上拉电阻 I²C 总线、多设备共享线
浮空输入 高阻态,电平由外部决定 按键检测、中断触发
上拉/下拉输入 内部弱上拉/弱下拉电阻 防抖、默认电平固定
关键指标:GPIO 驱动能力通常为 4~20 mA(视芯片而定),驱动大负载时必须加三极管或 MOSFET 扩流,禁止直接从引脚取大电流。

下图展示了一个典型 GPIO 输出引脚在推挽模式下的内部结构:

GPIO 推挽输出内部结构 VDD PMOS NMOS GND 输出节点 Pad 输出 1 → PMOS 导通 输出 0 → NMOS 导通

3.2 Linux GPIO 子系统架构

Linux GPIO 子系统采用经典的分层架构,从下到上依次为:

  1. 硬件层:SoC 内部的 GPIO 控制器寄存器
  2. 驱动层:芯片厂商实现的 pinctrl/gpio_chip 驱动
  3. 核心层:gpiolib 库,提供统一 API
  4. 用户接口层:sysfs / chardev / libgpiod

下图展示了 GPIO 子系统的完整软件栈:

Linux GPIO 子系统软件架构 用户空间:sysfs / chardev / libgpiod 系统调用接口 (VFS / ioctl) gpiolib 核心层 gpiochip_add / gpiod_get / 中断映射 pinctrl 子系统 (引脚复用/配置) 硬件驱动层 (gpio_chip 实现) 寄存器读写 / 中断控制器 / 电源管理 用户态 内核态
架构要点:gpiolib 核心层屏蔽了不同 SoC 的寄存器差异,驱动开发者只需调用 gpiod_get_value() / gpiod_set_value() 等 API,无需关心底层硬件细节。

3.3 gpiolib 库核心 API

gpiolib 提供了两套 API 体系:旧版整数描述符(已废弃)和新版 OPP 描述符(推荐)。以下为当前主流 API:

函数 功能 所属阶段
gpiod_get() 获取 GPIO 描述符(基于设备树) probe
gpiod_direction_output() 设置为输出模式并指定初始电平 probe
gpiod_direction_input() 设置为输入模式 probe
gpiod_set_value() 设置输出电平(0/1) 运行时
gpiod_get_value() 读取输入电平 运行时
gpiod_to_irq() 获取 GPIO 对应的中断号 probe
gpiod_put() 释放 GPIO 描述符 remove

一个典型的 GPIO 输出驱动 probe 函数片段:

#include <linux/gpio/consumer.h>

static int my_driver_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct gpio_desc *led_gpio;
    int ret;

    // 从设备树获取名为 "led" 的 GPIO
    led_gpio = gpiod_get(dev, "led", GPIOD_OUT_LOW);
    if (IS_ERR(led_gpio)) {
        dev_err(dev, "Failed to get led GPIO\n");
        return PTR_ERR(led_gpio);
    }

    // 设置输出高电平
    gpiod_set_value(led_gpio, 1);

    // 保存描述符到私有数据
    platform_set_drvdata(pdev, led_gpio);

    return 0;
}

static int my_driver_remove(struct platform_device *pdev)
{
    struct gpio_desc *led_gpio = platform_get_drvdata(pdev);

    gpiod_set_value(led_gpio, 0);
    gpiod_put(led_gpio);

    return 0;
}
注意事项:
  • 新版驱动必须使用 gpiod_* API,gpio_* 整数接口已被标记为 deprecated。
  • 设备树中需正确配置 gpios 属性,否则 gpiod_get() 返回 -EPROBE_DEFER。
  • 中断申请建议使用 devm_request_threaded_irq() 配合 gpiod_to_irq()。

3.4 设备树中的 GPIO 绑定

设备树是 GPIO 描述符与硬件引脚之间的桥梁。以下是一个典型绑定示例:

// 在设备节点中声明 GPIO
my_device {
    compatible = "vendor,my-device";
    led-gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;  // GPIO1_15
    btn-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>;     // GPIO2_3
};

// 驱动中获取
led_gpio = gpiod_get(dev, "led", GPIOD_OUT_LOW);
btn_gpio = gpiod_get(dev, "btn", GPIOD_IN);

设备树属性与驱动 API 的对应关系:

设备树属性 驱动 API 说明
xxx-gpios gpiod_get(dev, "xxx", ...) 属性名去掉 -gpios 即为 con_id
GPIO_ACTIVE_HIGH gpiod_is_active_low() 标志位决定电平反转逻辑
GPIO_ACTIVE_LOW 内部自动处理反转 驱动无需手动取反
最佳实践:设备树中尽量使用 GPIO_ACTIVE_LOW 描述硬件真实电平(如按键按下为低),驱动中始终以逻辑值操作,gpiolib 会自动完成电平转换。

3.5 本章小结

通过本章学习,您应掌握:

  • GPIO 硬件三种工作模式的电气特性与适用场景
  • Linux GPIO 子系统的四层架构:用户接口 → gpiolib 核心 → pinctrl → 硬件驱动
  • gpiolib 新版 API 的使用方法,包括获取、配置、读写、释放
  • 设备树中 GPIO 绑定的标准写法与 con_id 匹配规则