如何在 Linux 用户空间中进行类似 GPIO 中断的处理

How to handle GPIO interrupt-like handling in Linux userspace

文件节点

/sys/class/gpio/gpio666# ls
active_low  device  direction  edge  subsystem  uevent  value

只用对应 GPIO 有 irq 功能时才会有 edge 文件。

  • /sys/class/gpio/export:是一个只写文件,用于导出需要使用的 GPIO 引脚
  • /sys/class/gpio/gpiox/: 是一个文件夹,在引脚导出后自动在 / sys/class/gpio / 目录下生成的
  • /sys/class/gpio/gpiox/active_low:是一个文件,用来控制电平的极性(写 1 是高电平还是写 0 是高电平),默认写 1 是高电平,这个文件不用去管它
  • /sys/class/gpio/gpiox/direction:是一个文件,用来控制 GPIO 是输入还是输出,往direction写 out 就是输出引脚,往direction写 in 就是输入引脚
  • /sys/class/gpio/gpiox/edge:是一个文件,在输入模式下,写 edge 文件,配置 gpio 为外部中断引脚
  • 非中断引脚: none
  • 上升沿触发: rising
  • 下降沿触发: falling
  • 边沿触发: both
  • /sys/class/gpio/gpiox/value: 是一个文件,在输出模式下,写该文件表示 gpio 输出;在输入模式下读该文件表示输入

sysfs.txt

"value" ... reads as either 0 (low) or 1 (high). If the GPIO
  is configured as an output, this value may be written;
  any nonzero value is treated as high.

  If the pin can be configured as interrupt-generating interrupt
  and if it has been configured to generate interrupts (see the
  description of "edge"), you can poll(2) on that file and
  poll(2) will return whenever the interrupt was triggered. If
  you use poll(2), set the events POLLPRI and POLLERR. If you
  use select(2), set the file descriptor in exceptfds. After
  poll(2) returns, either lseek(2) to the beginning of the sysfs
  file and read the new value or close the file and re-open it
  to read the value.

"edge" ... reads as either "none", "rising", "falling", or
  "both". Write these strings to select the signal edge(s)
  that will make poll(2) on the "value" file return.

  This file exists only if the pin can be configured as an
  interrupt generating input pin.

demo

一坨屎样代码

#define GPIO_PIN_NUMBER 666

void* gpio_irq(void *arg)
{
    int fd;
    fd_set readFds;
    char value;

    // 如果没有GPIO 导出文件,则打开 GPIO 导出文件
    int exportFd = -1;
    struct stat dir_stat;
    char gpioDir[64];
    sprintf(gpioDir, "/sys/class/gpio/gpio%d", GPIO_PIN_NUMBER);
    if (stat(gpioDir, &dir_stat) != 0) {
        exportFd = open("/sys/class/gpio/export", O_WRONLY);
        if (exportFd == -1) {
            perror("Error opening export file");
            return EXIT_FAILURE;
        }

        // 将 GPIO 口编号写入到导出文件中
        if (write(exportFd, "666", 3) != 3) {
            perror("Error writing to export file");
            close(exportFd);
            return EXIT_FAILURE;
        }
        close(exportFd);
    }

    // 等待一小段时间,以确保 GPIO 口创建完成
    usleep(10000);

    // 打开 GPIO 方向文件
    char gpioDirectionPath[64];
    sprintf(gpioDirectionPath, "/sys/class/gpio/gpio%d/direction", GPIO_PIN_NUMBER);
    fd = open(gpioDirectionPath, O_WRONLY);
    if (fd < 0) {
        perror("Error opening direction file");
        return NULL;
    }

    // 将 GPIO 口方向设置为输入
    if (write(fd, "in", 2) != 2) {
        perror("Error setting GPIO direction");
        close(fd);
        return NULL;
    }
    close(fd);


    // 打开 GPIO edge 文件
    char gpioEdgePath[64];
    sprintf(gpioEdgePath, "/sys/class/gpio/gpio%d/edge", GPIO_PIN_NUMBER);
    fd = open(gpioEdgePath, O_WRONLY);
    if (fd < 0) {
        perror("Error opening edge file");
        return NULL;
    }

    // 将 GPIO edge both
    if (write(fd, "rising", 6) != 6) {
        perror("Error setting GPIO edge");
        close(fd);
        return NULL;
    }
    close(fd);






    // 打开 GPIO 值文件
    char gpioValuePath[64];
    sprintf(gpioValuePath, "/sys/class/gpio/gpio%d/value", GPIO_PIN_NUMBER);
    int value_fd = open(gpioValuePath, O_RDONLY);
    if (value_fd < 0) {
        perror("Error opening value file");
        return NULL;
    }


    // 创建文件描述符集合
    FD_ZERO(&readFds);
    FD_SET(value_fd, &readFds);

    // 监听 GPIO 的下降沿触发事件
    while (true) {
        // 重新设置文件描述符集合
        fd_set tempFds = readFds;

        // 监听文件描述符变化
        int selectResult = select(value_fd + 1, NULL, NULL, &tempFds, NULL);
        if (selectResult < 0) {
            perror("Error in select");
            break;
        } else if (selectResult > 0) {
            // 检查 GPIO 的文件描述符是否可读
            if (FD_ISSET(value_fd, &tempFds)) {
                DBG_PRINT("====================\n");
                // 读取 GPIO 的值
                lseek(value_fd, 0, SEEK_SET);
                read(value_fd, &value, 1);
                DBG_PRINT("read value: %c\n", value);
                if (value == '0') {
                    DBG_PRINT("+++++++++++++++ Falling edge detected! ++++++++++++++\n");
                    // 在这里执行 GPIO 下降沿触发事件的处理
                }
                else {
                    DBG_PRINT("--------------------Rising edge detected!--------------\n");
                    // 在这里执行 GPIO 上升沿触发事件的处理
                }
            }
            // usleep(1000*20);
        }
    }

    close(value_fd);

    // 关闭 GPIO 导出文件
    int unexportFd = open("/sys/class/gpio/unexport", O_WRONLY);
    if (unexportFd < 0) {
        perror("Error opening unexport file");
        return NULL;
    }

    // 将 GPIO 口编号写入到取消导出文件中
    if (write(unexportFd, "124", 3) != 3) {
        perror("Error writing to unexport file");
    }
    close(unexportFd);

    return NULL;
}
  • edge: “none”, “rising”, “falling”,“both”
  • 虽然申请的是 rising edge 中断,但是 select 返回中还是会响应上升沿,下降沿,请根据 read 结果做进一步判断
  • 注意代码中 tempFds 的参数位置: int selectResult = select(value_fd + 1, NULL, NULL, &tempFds, NULL);