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秒后熄灭
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 #设置为低电平