在Linux系统中,GPIO(General Purpose Input/Output,通用输入输出)是一种常见的硬件接口,用于控制外部设备或读取外部信号,要正确操作GPIO,首先需要获取其对应的GPIO号,Linux内核通过多种方式管理和暴露GPIO,用户或开发者可通过设备树、sysfs接口或编程库(如libgpiod)来获取GPIO号,本文将详细介绍这些方法的具体步骤和原理。
GPIO号的基本概念
Linux中的GPIO号并非硬件上的固定编号,而是内核根据GPIO控制器的层级结构分配的逻辑编号,常见的编号方式有两种:线性编号和分层编号。
- 线性编号:早期Linux版本采用全局线性编号,所有GPIO控制器的GPIO统一从0开始连续编号,例如GPIO 0、GPIO 1……GPIO 100,这种方式简单直接,但在多控制器系统中容易冲突,且难以追溯GPIO所属的控制器。
- 分层编号:现代Linux内核推荐分层编号,格式为
<chip_name>:<offset>
,其中chip_name
是GPIO控制器的名称(如gpiochip0
),offset
是该GPIO在控制器内的偏移量(从0开始),例如gpiochip0:18
表示gpiochip0
控制器的第18个GPIO,这种编号方式清晰且可扩展,是当前的主流方式。
通过设备树获取GPIO号
设备树(Device Tree, DT)是描述硬件结构的数据结构,Linux内核通过解析设备树来识别硬件资源,包括GPIO,在设备树中,GPIO通常通过gpio
属性或pinctrl
属性定义,开发者可通过以下步骤获取GPIO号:
定位设备树中的GPIO定义
在设备树源文件(.dts
)中,外设节点(如LED、按键)会通过gpio
属性引用GPIO,格式为:
led-gpio = <&gpio1 18 GPIO_ACTIVE_HIGH>;
&gpio1
:引用名为gpio1
的GPIO控制器节点(需在设备树中定义gpio-controller
属性);18
:GPIO在控制器内的偏移量;GPIO_ACTIVE_HIGH
:GPIO的有效电平(高电平有效,可选参数)。
解析控制器节点获取基址
GPIO控制器节点(如gpio1
)通常包含#gpio-cells
和reg
属性:
#gpio-cells
:指定GPIO描述符的元数(通常为2,分别对应偏移量和有效电平);reg
:控制器的寄存器基地址,用于内核分配线性编号时的基址计算。
内核在启动时会解析设备树,为每个GPIO控制器分配线性编号的基址(base
)和GPIO数量(ngpio
),例如gpio1
的base
为32,ngpio
为32,则其管理的GPIO线性编号为32-63。
计算线性GPIO号
若需线性编号,可通过公式计算:
线性GPIO号 = 控制器基址(base) + 偏移量(offset)
例如gpio1
的base
为32,偏移量为18,则线性GPIO号为32+18=50。
查看设备树解析后的GPIO信息
内核启动后,可通过/proc/device-tree
或libfdt
工具查看解析后的GPIO信息,查看gpio1
控制器的基址和数量:
cat /sys/class/gpio/gpiochip32/base # 输出基址(如32) cat /sys/class/gpio/gpiochip32/ngpio # 输出GPIO数量(如32)
通过sysfs接口获取GPIO号
sysfs是Linux内核提供的虚拟文件系统,用于导出硬件信息,虽然新版本内核推荐使用libgpiod,但sysfs仍可用于获取GPIO号,尤其适用于快速调试。
查看GPIO控制器信息
所有GPIO控制器位于/sys/class/gpio/
目录下,名称格式为gpiochipN
(N
为控制器编号),通过以下命令列出所有控制器:
ls /sys/class/gpio/ | grep gpiochip
进入某个控制器目录(如gpiochip0
),可查看其管理的GPIO范围:
cat /sys/class/gpio/gpiochip0/base # 控制器起始GPIO号(线性编号) cat /sys/class/gpio/gpiochip0/ngpio # 控制器管理的GPIO总数
gpiochip0
的base
为0,ngpio
为64,则其管理的GPIO号为0-63。
导出并获取GPIO号
若需操作特定GPIO,可通过export
文件导出(需root权限):
echo <gpio_number> > /sys/class/gpio/export
导出后,系统会在/sys/class/gpio/
下创建gpioN
目录(N
为线性GPIO号),例如导出GPIO 18后,路径为/sys/class/gpio/gpio18
。
通过GPIO属性验证
在gpioN
目录中,label
文件可能包含GPIO的硬件描述(若设备树中定义),可用于确认GPIO号是否正确:
cat /sys/class/gpio/gpio18/label
若输出类似GPIO18
或外设名称(如LED_GREEN
),则说明GPIO号正确。
sysfs关键文件说明
下表总结了sysfs中与GPIO相关的关键文件及其作用:
文件路径 | 作用说明 |
---|---|
/sys/class/gpio/export |
导出GPIO,写入线性GPIO号后创建gpioN 目录 |
/sys/class/gpio/unexport |
取消导出GPIO,写入线性GPIO号删除gpioN 目录 |
/sys/class/gpio/gpiochipN/base |
GPIO控制器gpiochipN 的起始线性GPIO号 |
/sys/class/gpio/gpiochipN/ngpio |
GPIO控制器gpiochipN 管理的GPIO总数 |
/sys/class/gpio/gpioN/value |
GPIO的值(0/1,输出模式时写入,输入模式时读取) |
/sys/class/gpio/gpioN/direction |
GPIO方向(in 输入/out 输出) |
通过libgpiod库获取GPIO号
libgpiod是Linux官方推荐的GPIO操作库,支持分层编号,功能更完善且安全,以下以C语言为例,说明如何通过libgpiod获取GPIO号:
安装libgpiod工具和库
sudo apt install gpiod libgpiod-dev # 安装工具和开发库
使用命令行工具查看GPIO信息
-
gpiodetect
:列出所有GPIO控制器及其名称和偏移量范围:gpiodetect # 输出示例:gpiochip0 [gpio0] (32 lines), gpiochip1 [gpio1] (32 lines)
其中
gpiochip0 [gpio0]
表示控制器名称为gpiochip0
,管理的GPIO偏移量为0-31。 -
gpioinfo
:查看指定控制器的GPIO线信息(包括偏移量、名称、方向等):gpioinfo gpiochip0 # 输出示例: # line 0: "GPIO0" unused input active-high # line 1: "GPIO1" unused input active-high # ... # line 18: "LED_GREEN" output active-high [used]
输出中的
line 18
即偏移量为18的GPIO,结合控制器名称gpiochip0
,分层编号为gpiochip0:18
。
编程获取GPIO号
使用libgpiod库打开控制器并获取GPIO线的偏移量:
#include <gpiod.h> #include <stdio.h> int main() { const char *chipname = "gpiochip0"; // 控制器名称 struct gpiod_chip *chip; struct gpiod_line *line; int offset = 18; // 目标GPIO偏移量 // 打开GPIO控制器 chip = gpiod_chip_open_by_name(chipname); if (!chip) { perror("Failed to open chip"); return 1; } // 获取GPIO线 line = gpiod_chip_get_line(chip, offset); if (!line) { perror("Failed to get line"); gpiod_chip_close(chip); return 1; } // 输出GPIO信息 printf("Controller: %sn", gpiod_chip_name(chip)); printf("Offset: %dn", gpiod_line_offset(line)); printf("Name: %sn", gpiod_line_name(line) ? gpiod_line_name(line) : "unnamed"); gpiod_line_release(line); gpiod_chip_close(chip); return 0; }
编译并运行:
gcc -o gpio_info gpio_info.c -lgpiod ./gpio_info
输出将包含控制器名称、偏移量及GPIO名称,通过控制器名称:偏移量
即可得到分层GPIO号。
获取Linux GPIO号的方法需根据场景选择:
- 设备树开发:通过解析设备树中的
gpio
属性和控制器节点,结合基址和偏移量计算线性编号,或直接使用分层编号<chip_name>:<offset>
。 - 快速调试:使用sysfs接口,通过
gpiochipN
目录的base
和ngpio
确定GPIO范围,或导出GPIO后查看label
文件验证。 - 编程开发:推荐使用libgpiod库,通过
gpiodetect
和gpioinfo
命令查看GPIO信息,或调用API获取控制器和偏移量,形成分层编号。
随着Linux内核的发展,分层编号和libgpiod已成为主流,建议新项目优先采用,以提高代码的可读性和可维护性。
相关问答FAQs
Q1: 为什么在设备树中定义了GPIO,但通过gpioinfo
却找不到对应的GPIO线?
A: 可能的原因包括:
- 设备树未正确编译到内核或设备树文件(
.dtb
)未加载到系统中,可通过fdt addr
命令检查设备树地址; - GPIO控制器驱动未加载,可通过
lsmod | grep <controller_driver>
查看驱动是否加载,或手动加载(如modprobe gpio_ich
); - GPIO已被其他设备占用,可通过
gpioinfo
查看[used]
标记,或检查/sys/class/gpio/gpioN
目录是否存在。
Q2: sysfs和libgpiod有什么区别?什么场景下使用哪种?
A: 区别如下:
- sysfs:是传统接口,通过文件操作GPIO,简单直观,但功能有限(如不支持原子操作),且存在并发安全问题(多进程同时操作同一GPIO时易冲突)。
- libgpiod:是现代库,基于字符设备(
/dev/gpiochipN
),支持分层编号、原子操作、多线程安全,并提供命令行工具和API接口。
场景选择:
- 快速调试或简单脚本操作,可使用sysfs;
- 驱动开发、复杂应用程序或需要稳定性的项目,推荐使用libgpiod。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/34996.html