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 输出引脚在推挽模式下的内部结构:
3.2 Linux GPIO 子系统架构
Linux GPIO 子系统采用经典的分层架构,从下到上依次为:
- 硬件层:SoC 内部的 GPIO 控制器寄存器
- 驱动层:芯片厂商实现的 pinctrl/gpio_chip 驱动
- 核心层:gpiolib 库,提供统一 API
- 用户接口层:sysfs / chardev / libgpiod
下图展示了 GPIO 子系统的完整软件栈:
架构要点: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 匹配规则