如何在 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 输出;在输入模式下读该文件表示输入
"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);