一、顶级架构一句话总结

设备树(gpio描述) → GPIO子系统 → gpiod接口 → 硬件引脚控制

GPIO是嵌入式开发中最基础的外设接口,Linux提供了新旧两套API,新式gpiod接口更安全、更简洁。


二、GPIO子系统架构

架构层次

┌─────────────────────────────────────────────────────────┐
│                   GPIO子系统架构                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   用户空间    /sys/class/gpio/ 或 /dev/gpiochipX        │
│       ↓                                                 │
│   GPIO核心层  gpiolib.c (统一抽象层)                     │
│       ↓                                                 │
│   GPIO控制器  gpio_chip (厂商驱动)                       │
│       ↓                                                 │
│   硬件层      GPIO寄存器操作                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

GPIO控制器结构

struct gpio_chip {
    const char *label;              // 控制器名称
    struct gpio_device *gpiodev;    // GPIO设备
    struct device *parent;          // 父设备
    struct module *owner;           // 所属模块
    
    int (*request)(struct gpio_chip *chip, unsigned offset);
    void (*free)(struct gpio_chip *chip, unsigned offset);
    int (*get_direction)(struct gpio_chip *chip, unsigned offset);
    int (*direction_input)(struct gpio_chip *chip, unsigned offset);
    int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
    int (*get)(struct gpio_chip *chip, unsigned offset);
    void (*set)(struct gpio_chip *chip, unsigned offset, int value);
    // ...
};

三、新旧API对比

接口对比表

功能 旧接口(整数型) 新接口(描述符型)
获取GPIO gpio_request() gpiod_get()
释放GPIO gpio_free() gpiod_put()
设置方向 gpio_direction_input/output() gpiod_direction_input/output()
读写值 gpio_get_value() / gpio_set_value() gpiod_get_value() / gpiod_set_value()
获取中断号 gpio_to_irq() gpiod_to_irq()

为什么推荐新接口?

特性 旧接口 新接口
类型安全 无(整数) 有(结构体指针)
方向管理 手动 自动
错误处理 返回值 返回值+ERR_PTR
设备树集成 需手动解析 自动解析
资源管理 手动释放 devm自动释放

四、新式gpiod接口详解

1. 头文件

#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>

2. 获取GPIO描述符

// 从设备树获取单个GPIO
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                            enum gpiod_flags flags);

// 获取多个GPIO
struct gpio_descs *gpiod_get_array(struct device *dev, const char *con_id,
                                   enum gpiod_flags flags);

// 获取索引GPIO
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id,
                                  unsigned int idx, enum gpiod_flags flags);

// 可选获取(不存在不报错)
struct gpio_desc *gpiod_get_optional(struct device *dev, const char *con_id,
                                     enum gpiod_flags flags);

3. 标志位

enum gpiod_flags {
    GPIOD_ASIS = 0,           // 不改变当前状态
    GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET,           // 输入
    GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET |     // 输出低
                    GPIOD_FLAGS_BIT_DIR_OUT |
                    GPIOD_FLAGS_BIT_DIR_VAL,
    GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET |    // 输出高
                     GPIOD_FLAGS_BIT_DIR_OUT,
};

4. 释放GPIO

// 释放单个GPIO
void gpiod_put(struct gpio_desc *desc);

// 释放多个GPIO
void gpiod_put_array(struct gpio_descs *descs);

// devm自动释放版本
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
                                 enum gpiod_flags flags);
void devm_gpiod_put(struct device *dev, struct gpio_desc *desc);

5. 方向设置

// 设置为输入
int gpiod_direction_input(struct gpio_desc *desc);

// 设置为输出
int gpiod_direction_output(struct gpio_desc *desc, int value);

// 设置为输出(原始值,不考虑active-low)
int gpiod_direction_output_raw(struct gpio_desc *desc, int value);

6. 读写操作

// 读取值(考虑active-low)
int gpiod_get_value(const struct gpio_desc *desc);

// 读取原始值(不考虑active-low)
int gpiod_get_value_cansleep(const struct gpio_desc *desc);
int gpiod_get_raw_value(const struct gpio_desc *desc);

// 设置值(考虑active-low)
void gpiod_set_value(struct gpio_desc *desc, int value);

// 设置原始值
void gpiod_set_raw_value(struct gpio_desc *desc, int value);

// 设置多个GPIO
void gpiod_set_array_value(unsigned int array_size,
                           struct gpio_desc **desc_array,
                           int *value_array);

7. 获取中断号

int gpiod_to_irq(const struct gpio_desc *desc);

五、设备树配置

GPIO设备树节点

/ {
    my_device {
        compatible = "mycompany,mydevice";
        
        // 单个GPIO
        led-gpio = <&gpio1 21 GPIO_ACTIVE_LOW>;
        
        // 多个GPIO
        gpios = <&gpio1 21 GPIO_ACTIVE_LOW>,
                <&gpio1 22 GPIO_ACTIVE_HIGH>,
                <&gpio2 10 GPIO_ACTIVE_LOW>;
        
        // 带名称的GPIO
        reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
        enable-gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
    };
};

GPIO标志说明

标志 说明
GPIO_ACTIVE_HIGH 高电平有效
GPIO_ACTIVE_LOW 低电平有效
GPIO_OPEN_DRAIN 开漏输出
GPIO_OPEN_SOURCE 开源输出
GPIO_PULL_UP 内部上拉
GPIO_PULL_DOWN 内部下拉

六、完整驱动示例

LED控制驱动

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/delay.h>

struct led_data {
    struct gpio_desc *led_gpio;
    const char *name;
};

static int led_probe(struct platform_device *pdev)
{
    struct led_data *data;
    struct device *dev = &pdev->dev;
    int ret;
    
    data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    
    // 获取GPIO(自动设置为输出高电平)
    data->led_gpio = devm_gpiod_get(dev, "led", GPIOD_OUT_HIGH);
    if (IS_ERR(data->led_gpio)) {
        dev_err(dev, "Failed to get LED GPIO\n");
        return PTR_ERR(data->led_gpio);
    }
    
    // 获取名称
    of_property_read_string(dev->of_node, "label", &data->name);
    
    platform_set_drvdata(pdev, data);
    
    dev_info(dev, "LED %s initialized\n", data->name);
    return 0;
}

static int led_remove(struct platform_device *pdev)
{
    struct led_data *data = platform_get_drvdata(pdev);
    
    // 关闭LED
    gpiod_set_value(data->led_gpio, 0);
    
    dev_info(&pdev->dev, "LED removed\n");
    return 0;
}

// LED闪烁函数
static void led_blink(struct led_data *data, int times)
{
    int i;
    for (i = 0; i < times; i++) {
        gpiod_set_value(data->led_gpio, 1);
        msleep(500);
        gpiod_set_value(data->led_gpio, 0);
        msleep(500);
    }
}

static const struct of_device_id led_of_match[] = {
    { .compatible = "mycompany,led", },
    { }
};
MODULE_DEVICE_TABLE(of, led_of_match);

static struct platform_driver led_driver = {
    .probe = led_probe,
    .remove = led_remove,
    .driver = {
        .name = "led_driver",
        .of_match_table = led_of_match,
    },
};
module_platform_driver(led_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Developer");

按键输入驱动

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/input.h>

struct button_data {
    struct gpio_desc *button_gpio;
    int irq;
    struct input_dev *input;
};

static irqreturn_t button_irq_handler(int irq, void *dev_id)
{
    struct button_data *data = dev_id;
    int state;
    
    // 读取按键状态
    state = gpiod_get_value(data->button_gpio);
    
    // 上报按键事件
    input_report_key(data->input, KEY_ENTER, !state);
    input_sync(data->input);
    
    pr_info("Button %s\n", state ? "released" : "pressed");
    return IRQ_HANDLED;
}

static int button_probe(struct platform_device *pdev)
{
    struct button_data *data;
    struct device *dev = &pdev->dev;
    int ret;
    
    data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    
    // 获取GPIO(设置为输入)
    data->button_gpio = devm_gpiod_get(dev, "button", GPIOD_IN);
    if (IS_ERR(data->button_gpio))
        return PTR_ERR(data->button_gpio);
    
    // 获取中断号
    data->irq = gpiod_to_irq(data->button_gpio);
    if (data->irq < 0)
        return data->irq;
    
    // 申请输入设备
    data->input = devm_input_allocate_device(dev);
    if (!data->input)
        return -ENOMEM;
    
    data->input->name = "my-button";
    input_set_capability(data->input, EV_KEY, KEY_ENTER);
    
    ret = input_register_device(data->input);
    if (ret)
        return ret;
    
    // 注册中断
    ret = devm_request_irq(dev, data->irq, button_irq_handler,
                           IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                           "button_irq", data);
    if (ret)
        return ret;
    
    platform_set_drvdata(pdev, data);
    dev_info(dev, "Button initialized\n");
    return 0;
}

static const struct of_device_id button_of_match[] = {
    { .compatible = "mycompany,button", },
    { }
};
MODULE_DEVICE_TABLE(of, button_of_match);

static struct platform_driver button_driver = {
    .probe = button_probe,
    .driver = {
        .name = "button_driver",
        .of_match_table = button_of_match,
    },
};
module_platform_driver(button_driver);

MODULE_LICENSE("GPL");

七、用户空间GPIO操作

sysfs接口(旧方式)

# 导出GPIO
echo 21 > /sys/class/gpio/export

# 设置方向
echo out > /sys/class/gpio/gpio21/direction
echo in > /sys/class/gpio/gpio21/direction

# 读写值
echo 1 > /sys/class/gpio/gpio21/value
cat /sys/class/gpio/gpio21/value

# 释放GPIO
echo 21 > /sys/class/gpio/unexport

libgpiod工具(新方式)

# 安装工具
apt install gpiod-tools

# 查看GPIO控制器
gpiodetect

# 查看GPIO状态
gpioinfo gpiochip0

# 设置GPIO值
gpioset gpiochip0 21=1

# 读取GPIO值
gpioget gpiochip0 21

# 监控GPIO事件
gpiomon gpiochip0 21

八、常见问题与解决方案

问题 原因 解决方案
gpiod_get返回错误 设备树配置错误 检查gpio-*属性名和引用
GPIO无法控制 方向未设置或权限问题 使用正确的flags参数
中断不触发 触发类型错误 检查IRQF_TRIGGER_*标志
值读取不正确 active-low配置 检查设备树GPIO_ACTIVE_*

九、终极总结

GPIO驱动 = 嵌入式开发的基础技能

  • 新接口优势:类型安全、自动管理、设备树集成
  • 核心API:gpiod_get/put、gpiod_direction_*、gpiod_set/get_value
  • 设备树配置:使用gpio-*属性描述GPIO
  • 最佳实践:优先使用devm_前缀的资源管理API

掌握GPIO驱动,就掌握了嵌入式开发的基础!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐