外设与接口:按键输入 (libgpiod)

1 API 介绍

以下是处理 GPIO 中断事件的核心 API。

1.1 事件请求与监听
c 复制代码
int gpiod_line_request_both_edges_events(struct gpiod_line *line, const char *consumer);
  • 参数
    • linegpiod_line 对象指针。
    • consumer:消费者名称(如 "button-listener")。
  • 作用:将引脚配置为输入模式,同时监听 上升沿(Rising Edge)和 下降沿(Falling Edge)。
  • 返回 :成功返回 0,失败返回 -1
1.2 等待事件(阻塞)
c 复制代码
int gpiod_line_event_wait(struct gpiod_line *line, const struct timespec *timeout);
  • 参数
    • line:已请求事件的 gpiod_line 对象指针。
    • timeout:超时时间。传 NULL 表示无限等待(直到事件发生才唤醒,CPU 占用率为 0)。
  • 作用:阻塞当前进程,等待中断事件。这是实现低功耗按键检测的核心。
  • 返回0表示超时,1表示有事件发生,-1表示发生错误。
1.3 读取事件详情

事件数据结构

c 复制代码
struct gpiod_line_event {
    struct timespec ts;  // 事件发生的时间戳(纳秒级精度)
    int event_type;      // 事件类型
};
  • event_type 常用值
    • GPIOD_LINE_EVENT_RISING_EDGE :上升沿。
    • GPIOD_LINE_EVENT_FALLING_EDGE :下降沿。
c 复制代码
int gpiod_line_event_read(struct gpiod_line *line, struct gpiod_line_event *event);
  • 参数
    • linegpiod_line 对象指针。
    • event:指向 struct gpiod_line_event 的指针,用于接收事件数据。
  • 返回 :成功返回 0,失败返回 -1

2 两种实现方案

2.1 轮询模式

代码如下:

c 复制代码
#include <gpiod.h>
#include <stdio.h>
#include <time.h> // for nanosleep

#define CHIP_NAME "gpiochip3"
#define LINE_OFFSET 5    // GPIO3_A5
#define DEBOUNCE_MS 20   // 消抖时间窗口

// 辅助函数:毫秒级延时
void msleep(long ms) {
    struct timespec ts = { .tv_sec = ms / 1000, .tv_nsec = (ms % 1000) * 1000000 };
    nanosleep(&ts, NULL);
}

int main() {
    struct gpiod_chip *chip = gpiod_chip_open_by_name(CHIP_NAME);
    struct gpiod_line *line = gpiod_chip_get_line(chip, LINE_OFFSET);
    int val, last_val = 1; // 假设初始状态为高(松开)

    // 1. 仅配置为输入模式 (不使用中断)
    if (gpiod_line_request_input(line, "debounce-worker") < 0) {
        perror("Request input failed");
        return -1;
    }

    while (1) {
        // 2. 主动轮询当前值
        val = gpiod_line_get_value(line); 

        // 3. 发现电平变化
        if (val != last_val) { 
            // 4. 暂停 20ms,让信号飞一会儿(过滤噪声)
            msleep(DEBOUNCE_MS); 
            
            // 5. 再次读取进行确认 (Double Check)
            int stable_val = gpiod_line_get_value(line); 

            if (stable_val == val) { // 如果20ms后电平依然没变,认为是有效按键
                if (stable_val == 0) 
                    printf(">>> Button PRESSED (Stable)\n");
                else 
                    printf("    Button RELEASED (Stable)\n");
                
                last_val = stable_val; // 更新状态机
            }
        }
        
        // 6. 降低轮询频率,保护 CPU (至关重要)
        msleep(10); 
    }

    gpiod_line_release(line);
    gpiod_chip_close(chip);
    return 0;
}
2.1 中断模式

利用内核中断机制,程序不需要时刻盯着引脚,而是挂起等待内核唤醒。

代码如下 :

c 复制代码
#include <gpiod.h>
#include <stdio.h>
#include <stdlib.h> // for system()

#define CHIP_NAME "gpiochip0"
#define LINE_OFFSET 14  // GPIO0_B6 (Group 1 * 8 + 6 = 14)

int main() {
    struct gpiod_chip *chip;
    struct gpiod_line *line;
    struct gpiod_line_event event;

    // 1. 基础初始化
    chip = gpiod_chip_open_by_name(CHIP_NAME);
    line = gpiod_chip_get_line(chip, LINE_OFFSET);

    // 2. 请求监测双边沿事件 (关键 API)
    if (gpiod_line_request_both_edges_events(line, "button-listener") < 0) {
        perror("Request events failed");
        return -1;
    }

    printf("Waiting for events on %s line %d...\n", CHIP_NAME, LINE_OFFSET);

    while (1) {
        // 3. 阻塞等待事件,无超时 (NULL),CPU 占用 0%
        if (gpiod_line_event_wait(line, NULL) > 0) {
            // 4. 读取具体事件
            gpiod_line_event_read(line, &event);

            // 5. 区分按下还是松开
            if (event.event_type == GPIOD_LINE_EVENT_FALLING_EDGE) {
                printf("[Timestamp: %ld] Button PRESSED\n", event.ts.tv_sec);
                // 业务逻辑:例如 system("/usr/bin/restart_app.sh &");
            } else {
                printf("[Timestamp: %ld] Button RELEASED\n", event.ts.tv_sec);
            }
        }
    }

    gpiod_line_release(line);
    gpiod_chip_close(chip);
    return 0;
}
相关推荐
tntxia13 小时前
linux curl命令详解_curl详解
linux
扛枪的书生15 小时前
Linux 网络管理器用法速查
linux
顺风尿一寸18 小时前
Java Socket 内核之旅:从 SocketChannel.read() 到 tcp_recvmsg 与 epoll 的完整调用链路
linux
XIAOHEZIcode1 天前
Ubuntu 终端美化全栈指南:Bash 到 Kitty 踩坑实录
linux·ubuntu·命令行
唐青枫1 天前
别再只会用 cron:Linux systemd Timer 定时任务实战详解
linux
AlfredZhao3 天前
生产环境里,为什么不建议把普通端口直接暴露到公网?
linux·https·443·80
戴为沐4 天前
Linux内存扩容指南
linux
zylyehuo4 天前
Linux 彻底且安全地删除文件
linux
用户805533698035 天前
主线 U-Boot 上 RK3506:和闭源 rkbin 拔河的三个隐性契约
linux·嵌入式
用户034095297915 天前
linux fcitx 5 雾凇拼音 设置在中文输入法下仍然输入英文标点
linux