01 GPIO控制
1. GPIO子系统
在 Linux 系统中,GPIO 由专门的 GPIO 子系统统一管理,该子系统基于内核的 GPIO 框架(gpiolib)实现,为 GPIO 硬件提供标准化的抽象接口。通过该框架,内核能够对不同平台的 GPIO 控制器进行统一封装,使用户空间或内核驱动可以以一致的方式访问 GPIO 资源。 GPIO的控制方式有两种:
- 通过sysfs文件系统导出到用户空间进行控制,从而以文件读写的方式实现对 GPIO 引脚的操作。(旧GPIO接口,路径在
/sys/class/gpio/,现在 Linux 内核已经不推荐继续使用) - 使用基于字符设备的 GPIO 接口(gpiochip)。(新GPIO接口,路径在
/dev/gpiochipN,Linux 官方推荐的新方式)

GPIO编号计算
每组GPIO有8个GPIO管脚,GPIO编号等于GPIO组合\*8+组内偏移号,例如GPIO4_2的GPIO编号为 4\*8+2=34 使用字符设备驱动时,GPIO1_4:chip_path对应/dev/gpiochip1,line_offset对应4
2. character GPIO
2.1 专业术语
libgpiod
libgpiod 是 Linux 用户空间访问 GPIO(通用输入输出)的标准库,是现代 GPIO 子系统(gpiolib character device interface)的官方用户态接口。
ioctl
ioctl 是一种设备控制系统调用接口,用于在 read/write 之外,通过命令码(request)和结构化参数(arg)与设备驱动进行交互,实现设备配置、状态查询和特殊控制功能。
说明
libgpiod 是 Linux 官方提供的 GPIO 用户空间参考库,它对 GPIO character device ioctl 接口进行了封装,提供统一的 GPIO line 申请、控制与事件 API。它在功能上相当于 GPIO 的用户态抽象层(userspace abstraction layer),但本质上仍依赖 ioctl 与内核 gpiolib 交互。
同时:用户完全可以绕过 libgpiod,直接基于 ioctl 实现自己的 GPIO 抽象层。
2.2 GPIO ioctl的使用
2.2.1 头文件
linux/gpio.h
这个头文件里定义了常用结构体和 ioctl:
- struct gpiohandle_request
- struct gpiohandle_data
- struct gpioevent_request
以及这些 ioctl 命令:
- GPIO_GET_CHIPINFO_IOCTL
- GPIO_GET_LINEINFO_IOCTL
- GPIO_GET_LINEHANDLE_IOCTL
- GPIO_GET_LINEEVENT_IOCTL
- GPIOHANDLE_GET_LINE_VALUES_IOCTL
- GPIOHANDLE_SET_LINE_VALUES_IOCTL
2.2.2 相关结构体
gpiohandle_request
用于申请普通输入/输出 GPIO line。
关键成员:
- lineoffsets[]:line 编号
- flags:输入还是输出
- default_values[]:默认输出值
- consumer_label:给内核看的占用者名字
- fd:成功后返回 line 句柄
gpiohandle_data
用于读写 line 电平。
关键成员:
- values[]:每条 line 的值
gpioevent_request
用于申请中断事件 line。
关键成员:
- lineoffset:line 编号
- handleflags:通常输入
- eventflags:上升沿/下降沿/双边沿
- fd:成功后返回事件 fd
2.3 实现呼吸灯应用程序
创建并编写gpio_hal.c
#include "gpio_hal.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
int gpio_handle_init(gpio_handle_t *gpio_handle)
{
struct gpiohandle_request req;
if (gpio_handle == NULL || gpio_handle->chip_path == NULL) {
errno = EINVAL;
return -1;
}
gpio_handle->chip_fd = -1;
gpio_handle->line_fd = -1;
gpio_handle->chip_fd = open(gpio_handle->chip_path, O_RDONLY);
if (gpio_handle->chip_fd < 0) {
perror("open gpiochip");
return -1;
}
memset(&req, 0, sizeof(req));
req.lineoffsets[0] = gpio_handle->line_offset;
req.flags = gpio_handle->gpio_mode;
req.default_values[0] = gpio_handle->default_value ? 1 : 0;
req.lines = 1;
if (gpio_handle->consumer_label[0] != '\0') {
strncpy(req.consumer_label,
gpio_handle->consumer_label,
sizeof(req.consumer_label) - 1);
req.consumer_label[sizeof(req.consumer_label) - 1] = '\0';
} else {
strncpy(req.consumer_label, "gpio-led", sizeof(req.consumer_label) - 1);
req.consumer_label[sizeof(req.consumer_label) - 1] = '\0';
}
if (ioctl(gpio_handle->chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &req) < 0) {
perror("GPIO_GET_LINEHANDLE_IOCTL");
close(gpio_handle->chip_fd);
gpio_handle->chip_fd = -1;
return -1;
}
gpio_handle->line_fd = req.fd;
return 0;
}
int gpio_set_value(gpio_handle_t *gpio_handle, int value)
{
struct gpiohandle_data data;
if (gpio_handle == NULL || gpio_handle->line_fd < 0) {
errno = EINVAL;
return -1;
}
memset(&data, 0, sizeof(data));
data.values[0] = value ? 1 : 0;
if (ioctl(gpio_handle->line_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0) {
perror("GPIOHANDLE_SET_LINE_VALUES_IOCTL");
return -1;
}
return 0;
}
void gpio_handle_close(gpio_handle_t *gpio_handle)
{
if (gpio_handle == NULL)
return;
if (gpio_handle->line_fd >= 0) {
close(gpio_handle->line_fd);
gpio_handle->line_fd = -1;
}
if (gpio_handle->chip_fd >= 0) {
close(gpio_handle->chip_fd);
gpio_handle->chip_fd = -1;
}
}创建并编写gpio_hal.h
#ifndef GPIO_HAL_H
#define GPIO_HAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <linux/gpio.h>
#define GPIO_CONSUMER_LABEL_LEN 32
typedef struct gpio_handle_t {
int chip_fd;
const char *chip_path;
unsigned int line_offset;
unsigned int gpio_mode;
int default_value;
int line_fd;
char consumer_label[GPIO_CONSUMER_LABEL_LEN];
} gpio_handle_t;
int gpio_handle_init(gpio_handle_t *gpio_handle);
int gpio_set_value(gpio_handle_t *gpio_handle, int value);
void gpio_handle_close(gpio_handle_t *gpio_handle);
#ifdef __cplusplus
}
#endif
#endif创建并编写main.c
#include "gpio_hal.h"
#include <linux/gpio.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define LED_CHIP_PATH "/dev/gpiochip1"
#define LED_LINE 6
int main(void)
{
gpio_handle_t led_handle;
memset(&led_handle, 0, sizeof(led_handle));
led_handle.chip_path = LED_CHIP_PATH;
led_handle.line_offset = LED_LINE;
led_handle.gpio_mode = GPIOHANDLE_REQUEST_OUTPUT;
led_handle.default_value = 0;
snprintf(led_handle.consumer_label, sizeof(led_handle.consumer_label), "led-gpio");
if (gpio_handle_init(&led_handle) < 0) {
perror("gpio_handle_init");
return 1;
}
printf("LED GPIO set high\n");
if (gpio_set_value(&led_handle, 1) < 0) {
gpio_handle_close(&led_handle);
return 1;
}
sleep(5);
printf("LED GPIO set low\n");
if (gpio_set_value(&led_handle, 0) < 0) {
gpio_handle_close(&led_handle);
return 1;
}
gpio_handle_close(&led_handle);
return 0;
}创建并编写Makefile
SDK_DIR := $(shell cd $(shell pwd)/../../.. && /bin/pwd)
include $(SDK_DIR)/build/base.mk
TARGET := gpio
SRCS := main.c gpio_hal.c
OBJS := $(SRCS:.c=.o)
.PHONY: all clean install
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(SDK_LD_CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(SDK_USR_CFLAGS) -c -o $@ $<
install: all
@mkdir -p $(XMEDIA_ROOTFS_DIR)/usr/bin
@cp -f $(TARGET) $(XMEDIA_ROOTFS_DIR)/usr/bin/$(TARGET)
@chmod 755 $(XMEDIA_ROOTFS_DIR)/usr/bin/$(TARGET)
@echo "Installed $(TARGET) to $(XMEDIA_ROOTFS_DIR)/usr/bin/$(TARGET)"
clean:
rm -f $(TARGET) $(OBJS)将应用程序编译进rootfs文件系统,登录Pico-G1运行应用程序
预期结果:LED亮5秒后熄灭
注意
若未能达到预期效果,可能是LED所在GPIO被复用为其他功能,可执行以下指令
xmmd.l 0x11980018 #查看GPIO1_6寄存器的值
xmmm 0x11980018 0x00001000 #修改寄存器的值修改后可继续运行应用程序
3 使用sysfs控制GPIO
3.1 将GPIO导出到用户空间
在板端 /sys/class/gpio 目录中,每个 GPIO 设备都有其自己的文件夹。这些文件夹的名称是 gpio 加上引脚编号,例如 /sys/class/gpio/gpio14 表示引脚编号为 14 的引脚,即 GPIO1_6。用户可使用如下命令查看:
~ # echo 14 > /sys/class/gpio/export
~ # ls /sys/class/gpio/
export gpiochip0 gpiochip24 gpiochip40 gpiochip56 gpiochip8
gpio14 gpiochip16 gpiochip32 gpiochip48 gpiochip64 unexport说明
echo 14 >sys/class/gpio 表示把 GPIO14 导出到用户空间,导出后就会生成:/sys/class/gpio/gpio14 可以使用 echo 14 > /sys/class/gpio/unexport 命令取消导出
3.2 GPIO控制目录
导出的GPIO对应的控制目录(即/sys/class/gpio/gpio14)里通常会有这些文件:
~ # ls /sys/class/gpio/gpio14/
active_low direction power uevent
device edge subsystem valueactive_low
用于设置 GPIO 电平逻辑是否反向 示例:
echo 1 > /sys/class/gpio/gpio14/active_low #表示把 GPIO14 的电平逻辑设置为反向direction
用于设置 GPIO 的方向,也就是它是输入还是输出 示例:
echo in > /sys/class/gpio/gpio14/direction #输入
echo out > /sys/class/gpio/gpio14/direction #输出power
电源管理相关目录/文件,一般不直接操作。
uevent
和设备事件相关,通常不是用户日常手工控制 GPIO 时最常用的文件
device
表示这个 GPIO 所属的设备信息
edge
echo both > /sys/class/gpio/gpio14/edge配置中断触发边沿,常见值:
- none:不产生中断
- rising:上升沿触发中断
- falling:下降沿触发中断
- both:双边沿触发中断
subsystem
表示这个 GPIO 属于哪个子系统
value
用于读取或设置GPIO的电平值
cat /sys/class/gpio/gpio14/value #读取电平
echo 1 > /sys/class/gpio/gpio14/value #设置为高电平
echo 0 > /sys/class/gpio/gpio14/value #设置为低电平