当前位置:首页>Linux>Linux GPIO子系统

Linux GPIO子系统

  • 2026-10-01 21:41:48
Linux GPIO子系统

在Linux用户空间操作GPIO,经历了从sysfs到libgpiod的演进。

GPIO在内核中的模型

GPIO在Linux内核中由gpio_controller(通过gpiod描述符管理)抽象。每个GPIO控制器(如i.MX的GPIO1~GPIO5)向内核注册一组GPIO,板级设备树描述它们如何被具体硬件使用(LED、按键、复位信号等)。

// 设备树中GPIO的使用示例leds{compatible="gpio-leds";led0{label="user-led";gpios=<&gpio13 GPIO_ACTIVE_LOW>;// GPIO1_3, 低有效default-state="off";};};keys{compatible="gpio-keys";button0{label="user-button";gpios=<&gpio25 GPIO_ACTIVE_LOW>;// GPIO2_5, 低有效linux,code=<KEY_ENTER>;};};

gpios = <&gpio1 3 GPIO_ACTIVE_LOW>中,&gpio1引用GPIO控制器,3是控制器内编号,GPIO_ACTIVE_LOW告诉内核该引脚低电平为"有效"逻辑。应用层调用gpiod_set_value()时只需关心逻辑值,极性由内核自动处理——这和MCU的HAL库HAL_GPIO_WritePin需要自己管极性不同,内核帮屏蔽了这层。

方案一:sysfs接口

sysfs接口是早期的用户空间GPIO操作方式,通过文件系统节点暴露GPIO。

# 导出GPIO(以GPIO1_3为例,内核中全局编号为 1*32+3 = 35)echo 35 > /sys/class/gpio/export# 设置方向echo out > /sys/class/gpio/gpio35/direction# 写值(受active_low影响)echo 1 > /sys/class/gpio/gpio35/value# 读值cat /sys/class/gpio/gpio35/value# 设置低电平有效echo 1 > /sys/class/gpio/gpio35/active_low# 用完取消导出echo 35 > /sys/class/gpio/unexport

sysfs的优点是无需额外库,shell脚本就能操作GPIO,调试时极其方便。缺点也很明显:

全局编号是controller_index * ngpio + offset算出来的,换板子或换内核版本编号可能变,硬编码编号脆弱。每次操作都打开文件、写文件,性能差且有竞态——两个进程同时导出同一个GPIO会冲突。没有引脚状态查询、边沿事件异步通知不方便(要poll value文件,低效)。

// sysfs方式在C程序中的操作(繁琐且易错)intgpio_sysfs_write(int gpio, int value){char path[64];int fd;sprintf(path, "/sys/class/gpio/gpio%d/value", gpio);    fd = open(path, O_WRONLY);if (fd < 0) return-1;// 每次写都触发一次系统调用if (write(fd, value ? "1" : "0", 1) < 0) {        close(fd);return-1;    }    close(fd);return0;}

方案二:libgpiod

libgpiod是sysfs的继任者,通过/dev/gpiochipN字符设备操作GPIO,提供了C库、命令行工具和Python绑定。

# 命令行工具(libgpiod提供)# 列出所有GPIO控制器gpiodetect# 输出: gpiochip0 [30200000.gpio] (32 lines)#       gpiochip1 [30210000.gpio] (32 lines)# 查看某个控制器的引脚信息gpioinfo gpiochip0# 输出每个pin的编号、名称、方向、使用状态# 读取一个引脚(line 3 of gpiochip0)gpioget gpiochip0 3# 设置一个引脚gpioset gpiochip0 3=1# 监控引脚边沿事件gpiomon gpiochip0 3

libgpiod的核心概念是"line"(引脚)和"line request"(请求)。应用以结构体描述要操作的引脚集合、方向和标志,一次性请求,内核返回文件描述符,后续通过ioctl或poll操作。

// libgpiod C API操作GPIO#include<gpiod.h>intgpio_toggle_demo(void){// 打开GPIO控制器structgpiod_chip *chip = gpiod_chip_open("/dev/gpiochip0");if (!chip) return-1;// 获取引脚3structgpiod_line *line = gpiod_chip_get_line(chip, 3);if (!line) {        gpiod_chip_close(chip);return-1;    }// 请求输出模式structgpiod_line_request_configcfg = {        .consumer = "my-app",        // 消费者名,出现在gpioinfo中        .request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,        .flags = GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW,  // 低有效    };if (gpiod_line_request(line, &cfg, 0) < 0) {        gpiod_chip_close(chip);return-1;    }// 设置值    gpiod_line_set_value(line, 1);   // 逻辑1(物理低电平,因ACTIVE_LOW)    gpiod_line_release(line);    gpiod_chip_close(chip);return0;}

libgpiod的优势:

  • • 通过/dev/gpiochipN而非全局编号,引脚标识稳定(控制器名+偏移)
  • • 支持批量操作多个引脚(一次请求一组line,原子设置)
  • • 原生支持边沿事件(GPIOD_LINE_REQUEST_EVENT_*)和异步通知(返回fd,可poll/select)
  • • 消费者名可追踪,多个进程操作时gpioinfo能显示谁在用
  • • gpiod_line_request失败后内核保证不留下半初始化状态

边沿事件处理

按键、中断信号这类需要响应边沿变化的场景,libgpiod比sysfs优雅得多。

// 监控引脚边沿事件(上升沿/下降沿)intgpio_edge_wait(void){structgpiod_chip *chip = gpiod_chip_open("/dev/gpiochip0");structgpiod_line *line = gpiod_chip_get_line(chip, 3);structgpiod_line_request_configcfg = {        .consumer = "edge-monitor",        .request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES,  // 双沿    };    gpiod_line_request(line, &cfg, 0);// 等待事件发生(可配合poll/select做超时)structgpiod_line_eventevent;int ret = gpiod_line_event_wait(line, NULL);  // NULL=阻塞if (ret > 0) {        gpiod_line_event_read(line, &event);// event.event_type: GPIOD_LINE_EVENT_RISING_EDGE / FALLING_EDGE// event.ts: 事件时间戳        handle_edge(event.event_type);    }    gpiod_line_release(line);    gpiod_chip_close(chip);return0;}

sysfs要实现同样功能,必须poll/sys/class/gpio/gpioN/value文件,每次边沿触发后还要重新读取确认,效率低且容易丢失快速事件。libgpiod的边沿事件由内核层面缓冲,应用可批量读取,不会漏事件。

方案一 vs 方案二

维度
sysfs
libgpiod
内核版本
全版本(5.18起废弃)
4.8+(推荐5.18+)
接口形式
文件系统节点
字符设备 /dev/gpiochipN
引脚标识
全局编号(易变)
控制器+偏移(稳定)
批量操作
不支持
支持(原子设置多引脚)
边沿事件
poll value文件(低效)
原生事件+fd(高效)
消费者追踪
无
有(gpioinfo可见)
线程安全
差
好
调试便捷
shell即可
需安装工具

用户空间与内核态协作

一个常见模式:内核驱动处理底层GPIO逻辑(如按键去抖、边沿检测),通过input子系统上报按键事件;用户空间应用程序通过/dev/input/eventX读取,无需关心具体GPIO编号。这种方式把硬件细节隔离在内核,应用更干净。

// 用户空间读取input事件(按键通过gpio-keys驱动上报)#include<linux/input.h>intread_key_event(void){int fd = open("/dev/input/event0", O_RDONLY);structinput_eventev;while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {if (ev.type == EV_KEY) {if (ev.value == 1) {// 按键按下(KEY_ENTER等)                handle_key_press(ev.code);            } elseif (ev.value == 0) {                handle_key_release(ev.code);            }        }    }    close(fd);return0;}

gpio-keys驱动在设备树中声明按键引脚,内核负责中断、去抖、边沿处理,用户空间只拿到标准化的input_event。这种分工让应用逻辑和板级硬件解耦——换引脚只改设备树,应用代码不变。

最新文章

随机文章