从 4.8 版开始,Linux 内核引入了基于字符设备的新用户空间 API,用于管理和控制 GPIO(通用输入/输出)。这篇文章介绍了新接口的基本原理,并通过一个简单的教程/示例演示了如何使用新 API 控制 GPIO。
教程中使用的硬件是 Raspberry Pi 3B,但代码是通用的,可用于任何嵌入式硬件。
From the version 4.8, the Linux kernel introduces a new user space API based on character devices for managing and controlling GPIOs ( General-Purpose Input/Output). This post presents the basic of the new interface as well as a simple tutorial/example to demonstrate how to use the new API to control GPIOs. The hardware used in the tutorial is the Raspberry Pi 3B but the code is generic and can be used on any embedded hardware. |
The old user space interface
在 Linux 内核 4.8 之前,在用户空间管理 GPIO 的唯一接口是 sysfs 接口。GPIO 通过 /sys/class/gpio 中的导出文件进行配置和控制。可通过该接口执行 GPIO 的基本操作:
-
通过 /sys/class/gpio/export 导出 GPIO
-
通过 /sys/class/gpio/export 配置 GPIO 方向(输入/输出): 通过: /sys/class/gpio/gpioX/direction 配置 GPIO 方向(输入/输出
-
通过 /sys/class/gpio/gpioX/value 读写 GPIO 值
该接口简单易用,但也有一些缺点:
-
缺乏批量 GPIO 读写功能,GPIO 配置选项有限(例如:无法将 GPIO 配置为低电平有效、漏极开路、开源等)。
-
当两个或多个进程同时访问同一个 GPIO 时,可能会出现竞赛条件问题。
-
从 GPIO 轮询事件不可靠
-
等等。
Before the Linux kernel 4.8, the only interface to manage GPIO in user space is the sysfs interface. GPIOs are configured and controlled via exported files in /sys/class/gpio. Basic GPIO operations that can be performed via this interface: * Export a GPIO via /sys/class/gpio/export * Configure the GPIO direction (input/output) via: /sys/class/gpio/gpioX/direction * Read/write GPIO value via /sys/class/gpio/gpioX/value This interface is simple and easy to use but it has some drawbacks: * Lack of bulk GPIO reads and writes, GPIO configuration options are limited (for example: unable to configure a GPIO as Active low, open drain, open source etc.) * Race condition problems may occur when two or more processes accessing the same GPIO at the same time. * Polling event from GPIO is not reliable * etc. |
The new Character Device interface
自 4.8 版起,Linux 内核引入了基于字符设备的新用户空间 GPIO 接口。用户空间的 GPIO 控制器接口以字符设备的形式提供: /dev/gpiochipX。基本的文件操作,如 open()、read()、write()、ioctl()、poll()、close(),都可以用来与 GPIO 控制器交互。
本节将详细介绍如何使用这一新 API,通过 ioctl 接口配置和控制 GPIO。
基本上,要与 GPIO 控制器交互,我们首先需要使用传统的文件打开操作打开字符设备:
Since version 4.8, Linux kernel introduces a new user-space GPIO interface based on character device. Interface to the GPIO controller is available in user space in form of a character device: /dev/gpiochipX. Basic file operations such as open(), read(), write(), ioctl(), poll(), close() can be used to interact with the GPIO controller. This section will detail on how to use this new API to configure and control GPIO via the ioctl interface. Basically, to interact with the GPIO controller, we first need to open character device using the traditional file open operation: |
// include API header for the new interface
#include <linux/gpio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <stdlib.h>
#define DEV_NAME "/dev/gpiochip0"
void func()
{
int fd, ret;
fd = open(DEV_NAME, O_RDONLY);
if (fd < 0)
{
printf("Unabled to open %%s: %%s", dev_name, strerror(errno));
return;
}
/*
control GPIO here, such as:
- configure
- read
- write
- polling
*/
(void)close(fd);
}
请注意,我们以只读模式打开字符设备 /dev/gpiochip0,所有 GPIO 控制操作都将在打开的文件描述符 fd 上执行。
Note that we open the character device /dev/gpiochip0 in read only mode, all GPIO control operations will be performed on the opened file descriptor fd. |
本教程包括以下操作:
-
读取 GPIO 芯片和线路信息
-
GPIO 读写操作(批量模式)。
-
GPIO 事件轮询
This tutorial covers the following operations: * GPIO chip and lines information reading * GPIO read and write operations (in bulk mode). * GPIO event polling |
通过 IOCTL 接口获取 GPIO 芯片信息
GPIO 芯片信息存储在一个结构类型为 gpiochip_info 的结构中,可通过 IOCTL GPIO_GET_CHIPINFO_IOCTL 请求进行查询:
Get GPIO chip information via the IOCTL interface GPIO chip information is stored in a structure of type struct gpiochip_info and can be queried via the IOCTL GPIO_GET_CHIPINFO_IOCTL request: |
struct gpiochip_info info;
struct gpioline_info line_info;
int fd, ret;
// open the device
fd = open(DEV_NAME, O_RDONLY);
if (fd < 0)
{
printf("Unabled to open %%s: %%s", dev_name, strerror(errno));
return;
}
// Query GPIO chip information
ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
if (ret == -1)
{
printf("Unable to get chip info from ioctl: %%s", strerror(errno));
close(fd);
return;
}
printf("Chip name: %%s\n", info.name);
printf("Chip label: %%s\n", info.label);
printf("Number of lines: %%d\n", info.lines);
在第 13 行,我们使用 IOCTL 接口(GPIO_GET_CHIPINFO_IOCTL 请求)从内核中查询芯片信息。结构信息包含芯片名称、芯片标签,以及重要的 GPIO 线路数。在此基础上,我们可以通过对文件描述符发出 IOCTL GPIO_GET_LINEINFO_IOCTL 请求,进一步查询各 GPIO 线路的状态。每个 GPIO 的线路信息都存储在一个结构类型为 struct gpioline_info 的结构中:
On line 13 we query the chip info from the kernel using the IOCTL interface (GPIO_GET_CHIPINFO_IOCTL request). The structure info contains the chip name, the chip label, and importantly the number of GPIO lines. From here, we can further query the state of each GPIO lines by issuing the IOCTL GPIO_GET_LINEINFO_IOCTL request on the file descriptor. The line information for each GPIO is stored in a structure of type: struct gpioline_info: |
struct gpioline_info line_info;
for (int i = 0; i < info.lines; i++)
{
line_info.line_offset = i;
ret = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &line_info);
if (ret == -1)
{
printf("Unable to get line info from offset %%d: %%s", i, strerror(errno));
}
else
{
printf("offset: %%d, name: %%s, consumer: %%s. Flags:\t[%%s]\t[%%s]\t[%%s]\t[%%s]\t[%%s]\n",
i,
line_info.name,
line_info.consumer,
(line_info.flags & GPIOLINE_FLAG_IS_OUT) ? "OUTPUT" : "INPUT",
(line_info.flags & GPIOLINE_FLAG_ACTIVE_LOW) ? "ACTIVE_LOW" : "ACTIVE_HIGHT",
(line_info.flags & GPIOLINE_FLAG_OPEN_DRAIN) ? "OPEN_DRAIN" : "...",
(line_info.flags & GPIOLINE_FLAG_OPEN_SOURCE) ? "OPENSOURCE" : "...",