嵌入式开发中,GPIO控制是最基础却又最频繁的操作之一。传统上,许多开发者习惯通过sysfs接口操作GPIO,这种方式虽然简单直观,但随着Linux内核的发展,sysfs接口逐渐暴露出性能瓶颈和功能局限。在RK3588这样的高性能ARM平台上,使用现代GPIO控制库libgpiod不仅能提升代码质量,还能充分利用硬件特性。
sysfs接口通过文件系统暴露GPIO操作,开发者通过读写/sys/class/gpio目录下的文件来控制GPIO。这种方式虽然入门简单,但在实际项目中存在诸多问题:
相比之下,libgpiod库通过字符设备直接与内核交互,提供了更高效、更安全的API:
| 特性 | sysfs | libgpiod |
|---|---|---|
| 性能 | 低(ms级延迟) | 高(μs级延迟) |
| 并发安全 | 不安全 | 安全 |
| 中断支持 | 有限 | 完整支持 |
| 内核推荐程度 | 不推荐 | 推荐 |
| 代码可维护性 | 低 | 高 |
RK3588的GPIO控制器采用分层设计,理解这一架构有助于更好地使用libgpiod:
RK3588包含5个GPIO控制器(GPIO0-GPIO4),每个控制器管理32个GPIO:
code复制GPIO0: 0-31
GPIO1: 32-63
GPIO2: 64-95
GPIO3: 96-127
GPIO4: 128-159
在ArmSoM-W3开发板上安装必要的软件包:
bash复制sudo apt update
sudo apt install libgpiod-dev gpiod
验证安装:
bash复制gpiodetect # 列出可用GPIO控制器
gpioinfo # 查看GPIO状态
libgpiod提供了简洁而强大的API,以下是关键函数及其用途:
c复制struct gpiod_chip *gpiod_chip_open(const char *path);
void gpiod_chip_close(struct gpiod_chip *chip);
struct gpiod_line *gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);
c复制int gpiod_line_request_output(struct gpiod_line *line, const char *consumer, int default_val);
int gpiod_line_request_input(struct gpiod_line *line, const char *consumer);
c复制int gpiod_line_set_value(struct gpiod_line *line, int value);
int gpiod_line_get_value(struct gpiod_line *line);
下面是一个使用libgpiod实现按键检测和LED控制的完整示例:
c复制#include <gpiod.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#define BUTTON_PIN 38 // GPIO1_6 (32 + 6)
#define LED_PIN 39 // GPIO1_7 (32 + 7)
static volatile int running = 1;
void signal_handler(int sig) {
running = 0;
}
int main() {
struct gpiod_chip *chip;
struct gpiod_line *led_line, *button_line;
int ret;
// 注册信号处理
signal(SIGINT, signal_handler);
// 打开GPIO控制器
chip = gpiod_chip_open("/dev/gpiochip1");
if (!chip) {
perror("Open chip failed");
return 1;
}
// 获取LED和按键线路
led_line = gpiod_chip_get_line(chip, LED_PIN - 32);
button_line = gpiod_chip_get_line(chip, BUTTON_PIN - 32);
if (!led_line || !button_line) {
perror("Get line failed");
goto cleanup;
}
// 配置LED为输出,初始低电平
ret = gpiod_line_request_output(led_line, "led-demo", 0);
if (ret < 0) {
perror("Request LED output failed");
goto cleanup;
}
// 配置按键为输入,带上拉
ret = gpiod_line_request_input_flags(button_line, "btn-demo", GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
if (ret < 0) {
perror("Request button input failed");
goto cleanup;
}
printf("Press button to toggle LED (Ctrl+C to exit)...\n");
int last_val = 1;
while (running) {
int val = gpiod_line_get_value(button_line);
if (val != last_val) {
if (val == 0) { // 按键按下(低电平有效)
int led_val = gpiod_line_get_value(led_line);
gpiod_line_set_value(led_line, !led_val);
printf("LED %s\n", !led_val ? "ON" : "OFF");
}
last_val = val;
}
usleep(10000); // 10ms防抖
}
cleanup:
// 释放资源
if (led_line) gpiod_line_release(led_line);
if (button_line) gpiod_line_release(button_line);
if (chip) gpiod_chip_close(chip);
return 0;
}
编译命令:
bash复制gcc gpio_demo.c -o gpio_demo -lgpiod
libgpiod支持高效的事件监听,避免了轮询带来的CPU浪费:
c复制struct gpiod_line_request_config config = {
.consumer = "btn-irq",
.request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE,
};
ret = gpiod_line_request(button_line, &config, 0);
if (ret < 0) {
perror("Request event failed");
goto cleanup;
}
while (running) {
struct gpiod_line_event event;
ret = gpiod_line_event_wait(button_line, NULL);
if (ret < 0) {
perror("Event wait error");
break;
}
ret = gpiod_line_event_read(button_line, &event);
if (ret < 0) {
perror("Event read error");
break;
}
// 处理按键事件
int led_val = gpiod_line_get_value(led_line);
gpiod_line_set_value(led_line, !led_val);
}
对于需要同时控制多个GPIO的场景,libgpiod提供了批量操作API:
c复制struct gpiod_line_bulk bulk;
unsigned int offsets[] = {0, 1, 2}; // 控制GPIO1_0, GPIO1_1, GPIO1_2
int values[] = {1, 0, 1}; // 设置值
gpiod_line_bulk_init(&bulk);
for (int i = 0; i < 3; i++) {
struct gpiod_line *line = gpiod_chip_get_line(chip, offsets[i]);
gpiod_line_bulk_add(&bulk, line);
gpiod_line_request_output(line, "bulk-demo", 0);
}
gpiod_line_set_value_bulk(&bulk, values);
通过简单的翻转测试对比sysfs和libgpiod的性能差异:
c复制// sysfs方式翻转测试
void sysfs_toggle(int gpio) {
char path[50];
sprintf(path, "/sys/class/gpio/gpio%d/value", gpio);
int fd = open(path, O_WRONLY);
for (int i = 0; i < 1000; i++) {
write(fd, "1", 1);
write(fd, "0", 1);
}
close(fd);
}
// libgpiod方式翻转测试
void libgpiod_toggle(struct gpiod_line *line) {
for (int i = 0; i < 1000; i++) {
gpiod_line_set_value(line, 1);
gpiod_line_set_value(line, 0);
}
}
测试结果(RK3588 @ 2.4GHz):
| 方法 | 1000次翻转时间(ms) |
|---|---|
| sysfs | 1200 |
| libgpiod | 2.4 |