GPIO驱动开发:从gpiod接口到设备树配置的实战指南
GPIO是嵌入式开发里打交道最多的东西——点灯、按键、复位芯片、使能电源,哪样都离不开它。Linux内核的GPIO子系统经历了几轮大改,旧的全局整数接口在6.8里已经标记deprecated,新式gpiod描述符接口才是正道。本文基于Linux 6.8内核,从子系统架构讲起,对比新旧API,再用LED和按键两个实例把gpiod接口和设备树配置串起来。
一、GPIO子系统架构
内核把GPIO操作抽象成四层结构,驱动开发者只需要跟最上面的接口层打交道:
- 用户空间:sysfs方式。.8里默认禁用,libgpiod是新的标准用户态接。- **GPIO核心层(gpiolib):管理所有GPIO控制器的注册和GPIO描述符的分配,提供统一的gpiod API
- **GPIO控制器驱动(gpio_chip):每个SoC的GPIO控制器实现自己的
gpio_chip,注册到gpiolib - *硬件寄存:控制器驱动最终操作的就是GPIO控制器的寄存。
gpio_chip结构体关键回调
// linux/gpio/driver.h(简化)
struct gpio_chip {
const char *label;
unsigned int ngpio; // 该控制器管理的GPIO数量
// 方向设置
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);
// 中断映射
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
// 获取方向
int (*get_direction)(struct gpio_chip *chip, unsigned offset);
// 可睡眠版本(I2C/SPI扩展GPIO必须实现。 int (*get_value_cansleep)(struct gpio_chip *chip, unsigned offset);
void (*set_value_cansleep)(struct gpio_chip *chip, unsigned offset, int value);
struct gpio_device *gpiodev; // 内部使用
};
SoC厂商的BSP工程师负责实现这些回调,驱动开发者用gpiod接口就行了,不需要直接碰gpio_chip。
二、新旧API对比
内核GPIO接口重构过一次。旧接口用全局整数编号标识GPIO,新接口用描述符指针。
旧接口(整数型,已deprecated。
#include <linux/gpio.h> // 6.8中已标记deprecated
int gpio_request(unsigned gpio, const char *label);
void gpio_free(unsigned gpio);
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);
int gpio_to_irq(unsigned gpio);
旧接口的问题很明显:一个裸整数没有任何类型安全,谁都能传个错值进去,编译器拦不住。
新接口(描述符型。
#include <linux/gpio/consumer.h>
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, ...);
void gpiod_put(struct gpio_desc *desc);
int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);
int gpiod_get_value(struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
int gpiod_to_irq(struct gpio_desc *desc);
对比表格
| 对比维度 | 旧接口(整数型) | 新接口(描述符型) |
|---|---|---|
| GPIO标识 | 全局整数编号 | struct gpio_desc *指针 |
| 类型安全 | 无,裸整数可随意传递 | 有,编译期检查类型 |
| 方向管理 | 手动调用direction函数 | 获取时可通过标志位指定方向 |
| 设备树集成 | 需要of_get_gpio手动解析 | 自动从设备树绑定,con_id匹配 |
| 资源管理 | 手动request/free | devm版本自动释放 |
| 6.8状态 | 标记deprecated | 推荐使用 |
| 代码可读性 | gpio_set_value(23, 1)看不出含义 |
gpiod_set_value(led_gpio, 1)意图明确 |
新项目直接用gpiod接口,别犹豫。
三、新式gpiod接口详解
头文件
#include <linux/gpio/consumer.h> // gpiod核心接口
#include <linux/gpio/driver.h> // gpio_chip(写控制器驱动才需要)
获取描述。
// 按con_id获取单个GPIO(设备树中gpio-xxx的xxx就是con_id。struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
enum gpiod_flags flags);
// 获取同一con_id下的第index个GPIO
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id,
unsigned int index, enum gpiod_flags flags);
// 批量获取同一con_id下的所有GPIO
struct gpio_descs *gpiod_get_array(struct device *dev, const char *con_id,
enum gpiod_flags flags);
// 获取可选GPIO,设备树中没有也不报错,返回NULL
struct gpio_desc *gpiod_get_optional(struct device *dev, const char *con_id,
enum gpiod_flags flags);
gpiod_get内部会根据con_id去设备树找gpio-xxx或xxx-gpios属性,找不到返回-ENOENT。
标志。
enum gpiod_flags {
GPIOD_ASIS = 0, // 不改变当前方向和。 GPIOD_IN = 1, // 设置为输。 GPIOD_OUT_LOW = 2, // 设置为输出,初始低电。 GPIOD_OUT_HIGH = 3, // 设置为输出,初始高电。};
获取时直接指定标志位,省去了单独调direction函数的步骤。
释放
void gpiod_put(struct gpio_desc *desc);
void gpiod_put_array(struct gpio_descs *descs);
devm资源管理版本
// 自动释放,probe失败或remove时自动put
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
enum gpiod_flags flags);
struct gpio_desc *devm_gpiod_get_index(struct device *dev, const char *con_id,
unsigned int index, enum gpiod_flags flags);
struct gpio_desc *devm_gpiod_get_optional(struct device *dev, const char *con_id,
enum gpiod_flags flags);
struct gpio_descs *devm_gpiod_get_array(struct device *dev, const char *con_id,
enum gpiod_flags flags);
// 手动提前释放(一般不需要,remove时自动释放)
void devm_gpiod_put(struct device *dev, struct gpio_desc *desc);
devm版本是首选,省得在错误处理路径和remove里手动释放。
方向设置
int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);
gpiod_direction_output的value参数是逻辑值,受active-low影响。
读写。
// 读值,返回0。(逻辑值)
int gpiod_get_value(struct gpio_desc *desc);
// 写值,0。(逻辑值)
void gpiod_set_value(struct gpio_desc *desc, int value);
// 可睡眠版本,用于I2C/SPI扩展GPIO
int gpiod_get_value_cansleep(struct gpio_desc *desc);
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);
注意:如果GPIO控制器挂在I2C或SPI总线上(比如PCAL6416、MCP23008这类扩展芯片),读写操作可能睡眠,必须用_cansleep版本。用错版本会在运行时打印警告栈跟踪。
中断映射
int gpiod_to_irq(struct gpio_desc *desc);
返回对应的Linux IRQ编号,失败返回负数。拿到IRQ号后就可以用request_irq或devm_request_irq注册中断处理函数。
四、设备树GPIO配置
gpiod接口能自动从设备树获取GPIO信息,前提是DTS写对。
gpio-/gpios属性语。
// 单个GPIO
gpio-xxx = <&gpio_controller pin_number flags>;
// 。xxx-gpios = <&gpio_controller pin_number flags>;
// 多个GPIO
xxx-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>,
<&gpio1 3 GPIO_ACTIVE_HIGH>;
gpiod_get查找规则。- gpiod_get(dev, "xxx", flags) 查找 gpio-xxx xxx-gpios
- gpiod_get_index(dev, "xxx", 1, flags) 查找 xxx-gpios 的第二个
GPIO标志
// include/dt-bindings/gpio/gpio.h
#define GPIO_ACTIVE_HIGH 0 // 高电平有效(默认。#define GPIO_ACTIVE_LOW 1 // 低电平有。#define GPIO_OPEN_DRAIN 2 // 开漏输。#define GPIO_OPEN_SOURCE 4 // 开源输。#define GPIO_PULL_UP 8 // 内部上拉(部分SoC支持```
### active-low语义
这是最容易搞混的地方。active-low的意思是"逻辑1对应物理低电平。
| 设备树标志 | gpiod_set_value(desc, 1) | gpiod_set_value(desc, 0) |
|:-----------|:-------------------------|:-------------------------|
| GPIO_ACTIVE_HIGH | 输出高电平 | 输出低电平 |
| GPIO_ACTIVE_LOW | 输出低电平 | 输出高电平 |
举个例子:LED接在GPIO上,低电平点亮。设备树标`GPIO_ACTIVE_LOW`,代码里`gpiod_set_value(led, 1)`就是点亮,`gpiod_set_value(led, 0)`就是熄灭。逻辑值和物理电平的映射交给内核处理,驱动代码不用关心硬件接法。
### 完整DTS示例
```dts
// GPIO控制器节点(SoC厂商提供。&pinctrl {
gpio0: gpio@ff200000 {
compatible = "vendor,soc-gpio";
reg = <0xff200000 0x100>;
gpio-controller;
#gpio-cells = <2>; // pin_number + flags
ngpios = <32>;
interrupt-controller;
#interrupt-cells = <2>;
};
};
// LED设备节点
my_led {
compatible = "linuxros,led-gpio";
gpio-led = <&gpio0 17 GPIO_ACTIVE_LOW>; // GPIO0_17,低电平点亮
default-state = "off";
};
// 按键设备节点
my_button {
compatible = "linuxros,button-gpio";
gpio-button = <&gpio0 23 GPIO_ACTIVE_LOW>; // GPIO0_23,按下为。 debounce-ms = <10>;
};
#gpio-cells = <2>表示引用这个控制器时需要两个参数:引脚编号和标志位。有些SoC需要3个cell(加一个bank编号),看具体binding文档。
五、LED控制驱动示例
用Platform驱动 + gpiod接口实现一个LED控制驱动,支持亮灭和闪烁。
// led_gpio.c - 基于gpiod的LED驱动(Linux 6.8。#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/kthread.h>
struct led_gpio_priv {
struct gpio_desc *led;
struct task_struct *blink_task;
bool blinking;
};
/* 点亮LED */
static void led_on(struct led_gpio_priv *priv)
{
gpiod_set_value(priv->led, 1);
}
/* 熄灭LED */
static void led_off(struct led_gpio_priv *priv)
{
gpiod_set_value(priv->led, 0);
}
/* 闪烁线程 */
static int blink_thread(void *data)
{
struct led_gpio_priv *priv = data;
while (!kthread_should_stop()) {
gpiod_set_value(priv->led, 1);
msleep(500);
gpiod_set_value(priv->led, 0);
msleep(500);
}
return 0;
}
/* 启动闪烁 */
static void led_start_blink(struct led_gpio_priv *priv)
{
if (priv->blinking)
return;
priv->blinking = true;
priv->blink_task = kthread_run(blink_thread, priv, "led_blink");
if (IS_ERR(priv->blink_task)) {
priv->blinking = false;
dev_err(priv->led->dev, "failed to create blink thread\n");
}
}
/* 停止闪烁 */
static void led_stop_blink(struct led_gpio_priv *priv)
{
if (!priv->blinking)
return;
kthread_stop(priv->blink_task);
priv->blinking = false;
led_off(priv);
}
static int led_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct led_gpio_priv *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* 获取GPIO描述符,初始输出低电平(LED熄灭*/
priv->led = devm_gpiod_get(dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(priv->led))
return dev_err_probe(dev, PTR_ERR(priv->led),
"failed to get LED GPIO\n");
platform_set_drvdata(pdev, priv);
/* 启动时闪3次表示驱动加载成功 */
int i;
for (i = 0; i < 3; i++) {
led_on(priv);
msleep(200);
led_off(priv);
msleep(200);
}
dev_info(dev, "LED GPIO driver loaded\n");
return 0;
}
static void led_gpio_remove(struct platform_device *pdev)
{
struct led_gpio_priv *priv = platform_get_drvdata(pdev);
led_stop_blink(priv);
/* devm_gpiod_get分配的资源自动释放,不需要手动put */
dev_info(&pdev->dev, "LED GPIO driver removed\n");
}
static const struct of_device_id led_gpio_of_match[] = {
{ .compatible = "linuxros,led-gpio" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, led_gpio_of_match);
static struct platform_driver led_gpio_driver = {
.probe = led_gpio_probe,
.remove = led_gpio_remove,
.driver = {
.name = "led-gpio",
.of_match_table = led_gpio_of_match,
},
};
module_platform_driver(led_gpio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("GPIO LED driver using gpiod interface");
关键点:
devm_gpiod_get(dev, "led", GPIOD_OUT_LOW)自动查找设备树中的gpio-led或led-gpios属。-GPIOD_OUT_LOW获取时直接设置为输出低电平,不用再调gpiod_direction_outputdevm_err_probe。.8的标准做法,处理EPROBE_DEFER并统一日志格式- devm分配的资源在remove时自动释放,不需要手动gpiod_put
六、按键中断驱动示例
按键检测是GPIO中断最典型的应用。用gpiod获取引脚,gpiod_to_irq转换成IRQ号,再注册中断处理函数。
// button_gpio.c - 基于gpiod的按键中断驱动(Linux 6.8。#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_gpio_priv {
struct gpio_desc *button;
int irq;
struct input_dev *input;
u32 debounce_ms;
unsigned long last_jiffies;
};
/* 中断处理函数 */
static irqreturn_t button_isr(int irq, void *dev_id)
{
struct button_gpio_priv *priv = dev_id;
unsigned long now = jiffies;
unsigned long diff = now - priv->last_jiffies;
/* 软件消抖:两次中断间隔太短就忽略 */
if (diff < msecs_to_jiffies(priv->debounce_ms))
return IRQ_HANDLED;
priv->last_jiffies = now;
int val = gpiod_get_value(priv->button);
/* 上报按键事件 */
input_report_key(priv->input, KEY_POWER, val);
input_sync(priv->input);
dev_dbg(priv->input->dev.parent, "button %s\n", val ? "pressed" : "released");
return IRQ_HANDLED;
}
static int button_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct button_gpio_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* 获取GPIO描述符,设置为输*/
priv->button = devm_gpiod_get(dev, "button", GPIOD_IN);
if (IS_ERR(priv->button))
return dev_err_probe(dev, PTR_ERR(priv->button),
"failed to get button GPIO\n");
/* 读取消抖时间,默。0ms */
if (of_property_read_u32(dev->of_node, "debounce-ms", &priv->debounce_ms))
priv->debounce_ms = 10;
priv->last_jiffies = 0;
/* GPIO转IRQ */
priv->irq = gpiod_to_irq(priv->button);
if (priv->irq < 0)
return dev_err_probe(dev, priv->irq, "failed to map GPIO to IRQ\n");
/* 注册input设备 */
priv->input = devm_input_allocate_device(dev);
if (!priv->input)
return -ENOMEM;
priv->input->name = "gpio-button";
priv->input->dev.parent = dev;
__set_bit(EV_KEY, priv->input->evbit);
__set_bit(KEY_POWER, priv->input->keybit);
ret = input_register_device(priv->input);
if (ret)
return dev_err_probe(dev, ret, "failed to register input device\n");
/* 注册中断,双边沿触发 */
ret = devm_request_irq(dev, priv->irq, button_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"button-irq", priv);
if (ret)
return dev_err_probe(dev, ret, "failed to request IRQ\n");
platform_set_drvdata(pdev, priv);
dev_info(dev, "button GPIO driver loaded (IRQ=%d, debounce=%ums)\n",
priv->irq, priv->debounce_ms);
return 0;
}
static void button_gpio_remove(struct platform_device *pdev)
{
/* devm自动释放所有资源:gpiod、irq、input_device */
dev_info(&pdev->dev, "button GPIO driver removed\n");
}
static const struct of_device_id button_gpio_of_match[] = {
{ .compatible = "linuxros,button-gpio" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, button_gpio_of_match);
static struct platform_driver button_gpio_driver = {
.probe = button_gpio_probe,
.remove = button_gpio_remove,
.driver = {
.name = "button-gpio",
.of_match_table = button_gpio_of_match,
},
};
module_platform_driver(button_gpio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linuxros");
MODULE_DESCRIPTION("GPIO button interrupt driver using gpiod interface");
关键点:
GPIOD_IN获取时直接设置为输入模式gpiod_to_irq把GPIO描述符转成IRQ编号,gpiod和中断子系统靠它衔接IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING双边沿触发,按下和松开都能捕获- 软件消抖用jiffies时间差实现,够用
- 用input子系统上报按键事件,用户空间通过
/dev/input/eventX读取
七、用户空间GPIO操作
旧方式:sysfs(已deprecated。
# 导出GPIO 17
echo 17 > /sys/class/gpio/export
# 设置方向
echo out > /sys/class/gpio/gpio17/direction
echo in > /sys/class/gpio/gpio17/direction
# 读写。echo 1 > /sys/class/gpio/gpio17/value
cat /sys/class/gpio/gpio17/value
# 取消导出
echo 17 > /sys/class/gpio/unexport
6.8内核默认编译选项CONFIG_GPIO_SYSFS不再开启,这个接口基本废了。就算手动开启,内核也会在sysfs目录下打印deprecated警告。
新方式:libgpiod
libgpiod是GPIO sysfs的替代方案,直接通过字符设备/dev/gpiochipX和内核通信,不需要export/unexport。
安装
# Ubuntu/Debian
sudo apt install gpiod libgpiod-dev
# 从源码编。git clone https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
cd libgpiod && mkdir build && cd build
meson setup .. && ninja && sudo ninja install
命令行工。
# 列出所有GPIO控制。gpiodetect
# 输出示例。# gpiochip0 [ff200000.gpio] (32 lines)
# gpiochip1 [ff210000.gpio] (16 lines)
# 查看某个控制器的所有引脚状态gpioinfo gpiochip0
# 输出示例。# line 0: unnamed unused input active-high
# line 17: unnamed "led" output active-low [used]
# 读取GPIO。gpioget gpiochip0 23
# 输出。 。1
# 设置GPIO值(输出高电平)
gpioset gpiochip0 17=1
# 监控GPIO事件(边沿中断)
gpiomon gpiochip0 23
# 按下按键时输出:
# event: FALLING EDGE offset: 23 timestamp: [1234567890.123456789]
C语言调用libgpiod
// gpiod_user.c - 用户空间GPIO操作示例
#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
struct gpiod_line *line;
struct gpiod_chip *chip;
/* 打开GPIO控制*/
chip = gpiod_chip_open_by_name("gpiochip0");
if (!chip) {
perror("open chip failed");
return -1;
}
/* 获取GPIO 17 */
line = gpiod_chip_get_line(chip, 17);
if (!line) {
perror("get line failed");
gpiod_chip_close(chip);
return -1;
}
/* 设置为输出,初始低电*/
if (gpiod_line_request_output(line, "led-test", 0) < 0) {
perror("request output failed");
gpiod_chip_close(chip);
return -1;
}
/* 闪烁5*/
for (int i = 0; i < 5; i++) {
gpiod_line_set_value(line, 1);
usleep(500000);
gpiod_line_set_value(line, 0);
usleep(500000);
}
gpiod_line_release(line);
gpiod_chip_close(chip);
return 0;
}
编译:gcc -o gpiod_user gpiod_user.c -lgpiod
八、常见问题
*Q1:gpiod_get返回-ENOENT
驱动里写gpiod_get(dev, "led", flags),内核会去设备树找gpio-led或led-gpios属性。属性名对不上就返回-ENOENT。检查三点:con_id拼写、设备树属性名格式、节点是否和驱动匹配上。
Q2:gpiod_set_value不生效?
先确认方向是不是output——gpiod_get时用了GPIOD_IN或GPIOD_ASIS,引脚可能还停在输入模式。再检查active-low配置:设备树标了GPIO_ACTIVE_LOW时,gpiod_set_value(desc, 1)输出的是物理低电平,拿万用表量的时候别搞反了。最后确认GPIO有没有被其他驱动占用(gpioinfo查看used状态)。
Q3:sysfs方式不可用?
6.8默认不编译CONFIG_GPIO_SYSFS,/sys/class/gpio/目录不存在。装libgpiod,用gpioget/gpioset/gpiomon替代。如果非要sysfs,改内核配置CONFIG_GPIO_SYSFS=y重新编译,但不推荐。
*Q4:gpiod_get_value_cansleep警告
GPIO控制器挂在I2C或SPI总线上时,读写操作可能睡眠。在中断上下文或spinlock保护区内调用了gpiod_get_value而不是gpiod_get_value_cansleep,内核会打警告。把gpiod_get_value换成gpiod_get_value_cansleep,或者不在原子上下文里读GPIO。
Q5:devm_gpiod_get和gpiod_get怎么选?
无脑选devm版本。devm_gpiod_get在probe失败或remove时自动释放GPIO,省去手动gpiod_put的麻烦,也避免忘记释放导致GPIO一直被占用。只有一种情况用裸gpiod_get:GPIO的生命周期需要脱离设备生命周期独立管理,比如热插拔场景:
九、总结
gpiod接口速查看
| 操作 | 函数 | 说明 |
|---|---|---|
| 获取GPIO | devm_gpiod_get(dev, con_id, flags) |
首选,自动释放 |
| 可选获 | devm_gpiod_get_optional(dev, con_id, flags) |
设备树没有也不报 |
| 批量获取 | devm_gpiod_get_array(dev, con_id, flags) |
多个GPIO一次获 |
| 设输 | gpiod_direction_input(desc) |
或获取时用GPIOD_IN |
| 设输 | gpiod_direction_output(desc, val) |
或获取时用GPIOD_OUT_LOW/HIGH |
| 读 | gpiod_get_value(desc) |
返回逻辑。/1 |
| 写 | gpiod_set_value(desc, val) |
写逻辑值,受active-low影响 |
| 读。可睡眠 | gpiod_get_value_cansleep(desc) |
I2C/SPI扩展GPIO必须 |
| 写。可睡眠 | gpiod_set_value_cansleep(desc, val) |
I2C/SPI扩展GPIO必须 |
| 转中 | gpiod_to_irq(desc) |
返回IRQ编号 |
| 释放 | devm_gpiod_put(dev, desc) |
一般不需要,remove自动释放 |
设备树GPIO属性速查
| 属性格 | gpiod_get的con_id | 说明 |
|---|---|---|
gpio-led = <...> |
"led" |
前缀gpio- |
led-gpios = <...> |
"led" |
后缀-gpios |
reset-gpios = <...> |
"reset" |
常见复位引脚 |
enable-gpios = <...> |
"enable" |
常见使能引脚 |
标志位速查
| 标志 | 含义 | 典型场景 |
|---|---|---|
GPIOD_ASIS |
不改变方 | 需要保留bootloader设置 |
GPIOD_IN |
输入 | 按键、传感器 |
GPIOD_OUT_LOW |
输出低电平 | LED初始熄灭、复位引 |
GPIOD_OUT_HIGH |
输出高电平 | 使能引脚、电源控制 |
本文首发于linuxros.cn,转载请注明出处。