Linux GPIO驱动开发实战:从传统接口到新式gpiod
·
一、顶级架构一句话总结
设备树(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驱动,就掌握了嵌入式开发的基础!
更多推荐




所有评论(0)