Linux GPIO架构
GPIO是通用输入/输出的缩写,是嵌入式Linux系统中最常用的外设之一。
在内部,Linux 内核对 GPIO 的访问方法可以参考如下内容
GPIO Driver Interface — The Linux Kernel documentation
GPIO Descriptor Consumer Interface — The Linux Kernel documentation
为了管理 GPIO 注册和分配,Linux 内核中有一个名为 gpiolib 的框架。此框架为在内核空间和用户空间应用程序中运行的设备驱动程序提供 API。
旧的方法:sysfs 接口
在 Linux 版本 4.7 之前,在用户空间中管理 GPIO 行的接口始终通过导出在 /sys/class/gpio 中的文件在 sysfs 中。例如,如果我想设置GPIO,我必须:
- 确定GPIO线的编号。
- 导出 GPIO,将其编号写入 /sys/class/gpio/export。
- 将 GPIO 行配置为输出输出到 /sys/class/gpio/gpioX/direction。
- 将 GPIO 写入 1 设置为 /sys/class/gpio/gpioX/value。
举个实际的例子,要从用户空间设置 GPIO 504,我们必须执行以下命令:
# echo 504 > /sys/class/gpio/export
# echo out > /sys/class/gpio/gpio504/direction
# echo 1 > /sys/class/gpio/gpio504/value
这个界面非常简单,运行良好,但有一些不足:
- GPIO 的分配不绑定到任何进程,因此,如果使用 GPIO 的进程结束执行或崩溃,则 GPIO 行可能保持导出状态。
- 我们可以让多个进程访问同一 GPIO 行,因此并发可能是一个问题。
- 写入多个引脚需要对大量文件(导出、方向、值等)进行 open()/read()/write()/close() 操作。
- 捕获事件(来自 GPIO 线路的中断)的轮询过程不可靠。
- 没有用于配置GPIO线路的接口(开源、开漏等)。
- 分配给 GPIO 线路的编号不稳定。
新的方法:chardev 接口
从 Linux 版本 4.8 开始,GPIO sysfs 接口被弃用,如果想要在4.8版本以后仍然使用旧的版本,可以在Linux config中将如下开关配置为Y
CONFIG_GPIO_SYSFS =Y
新的方法基于字符设备的新 API,可以从用户空间访问 GPIO 。
每个 GPIO 控制器 (gpiochip) 在 /dev 中都有一个字符设备,我们可以使用文件操作 (open(), read(), write(), ioctl(), poll(), close()) 来管理 GPIO 行并与之交互:
# ls /dev/gpiochip*
/dev/gpiochip0 /dev/gpiochip2 /dev/gpiochip4 /dev/gpiochip6
/dev/gpiochip1 /dev/gpiochip3 /dev/gpiochip5 /dev/gpiochip7
尽管这个新的 char 设备接口可以防止使用 echo 和 cat 等标准命令行工具操作 GPIO,但与 sysfs 接口相比,它具有一些优势,包括:
- GPIO 的分配与使用它的进程相关联,从而改进了对用户空间进程使用哪些 GPIO 行的控制。
- 可以一次读取或写入多个GPIO线。
- 可以按名称查找GPIO控制器和GPIO线路。
- 可以配置引脚的状态(开源、漏极开路等)。
- 捕获事件(来自 GPIO 线路的中断)的轮询过程是可靠的。
示例C代码:具体参照 libgpiod 项目
#include <stdio.h>
#include <gpiod.h>
int main() {
// 打开 GPIO 控制器
struct gpiod_chip *chip;
chip = gpiod_chip_open("/dev/gpiochip0");
// 设置要控制的 GPIO 线路编号
int gpioLine = 17;
// 获取指定线路的 GPIO 行对象
struct gpiod_line *line;
line = gpiod_chip_get_line(chip, gpioLine);
// 设置 GPIO 行为输出模式
int ret = gpiod_line_request_output(line, "example", 0);
if (ret < 0) {
printf("无法设置 GPIO 行为输出模式\n");
return 1;
}
// 将 GPIO 置高
gpiod_line_set_value(line, 1);
// 延时一段时间
usleep(1000000);
// 将 GPIO 置低
gpiod_line_set_value(line, 0);
// 释放 GPIO 行对象
gpiod_line_release(line);
// 关闭 GPIO 控制器
gpiod_chip_close(chip);
return 0;
}
库和工具
为了使用这个新的 char 设备接口,libgpiod 项目提供了一个库和一组工具。
例如,以下 C 语言程序使用 libgpiod 读取 GPIO 行:
int main() {
struct gpiod_chip *chip;
struct gpiod_line *line;
int req, value;
chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip)
return -1;
line = gpiod_chip_get_line(chip, 3);
if (!line) {
gpiod_chip_close(chip);
return -1;
}
req = gpiod_line_request_input(line, "gpio_state");
if (req) {
gpiod_chip_close(chip);
return -1;
}
value = gpiod_line_get_value(line);
printf("GPIO value is: %d\n", value);
gpiod_chip_close(chip);
}
该库是 C 语言,但有其他语言(如 C++ 和 Python)的绑定。
如果要从终端管理 GPIO 行并与之交互,可以使用 libgpiod 提供的命令行工具。
gpiodetect 命令将列出所有 gpiochip、labels和 GPIO lines:
# gpiodetect
gpiochip0 [209c000.gpio] (32 lines)
gpiochip1 [20a0000.gpio] (32 lines)
gpiochip2 [20a4000.gpio] (32 lines)
gpiochip3 [20a8000.gpio] (32 lines)
gpiochip4 [20ac000.gpio] (32 lines)
gpiochip5 [20b0000.gpio] (32 lines)
gpiochip6 [20b4000.gpio] (32 lines)
gpiochip7 [mcp23s08.0] (8 lines)
gpioinfo 命令
# gpioinfo 0
gpiochip0 - 32 lines:
line 0: unnamed unused input active-high
line 1: unnamed unused input active-high
line 2: unnamed unused input active-high
line 3: unnamed "scl" output active-high [used open-drain]
line 4: unnamed unused input active-high
line 5: unnamed unused input active-high
line 6: unnamed "sda" output active-high [used open-drain]
line 7: unnamed "Push Button" input active-low [used]
line 8: unnamed unused input active-high
line 9: unnamed "?" output active-high [used]
line 10: unnamed unused input active-high
line 11: unnamed unused input active-high
[...]
gpioset 命令 将gpiochip7组的第0个引脚电平设置为1
# gpioset gpiochip7 0=1
gpioget 命令 读取gpiochip7组的第0个引脚电平,取得值为1
# gpioget gpiochip7 0
1
gpiomon 命令 可以检测端子变化状态
# gpiomon 0 7
event: FALLING EDGE offset: 7 timestamp: [ 4564.943435210]
event: RISING EDGE offset: 7 timestamp: [ 4565.072156210]
event: FALLING EDGE offset: 7 timestamp: [ 4566.113641877]
event: RISING EDGE offset: 7 timestamp: [ 4566.366691877]
所有这些命令的源代码都可以在 libgpiod 存储库中找到。