本系列使用的开发板为正点原子阿尔法IMX6ULL开发板,及根据正点原子所的提供教程学习
控制GPIO输出高低电平、输入及中断
这里通过sysyfs的方式
找到/sys/class/gpio目录,其中包含了七个文件(夹):export、unexport和五个gpiochipX。
base:表示这组GPIO引脚中最小的编号,gpiochipX中的“X”就是这组中的最小编号,每个GPIO都有一个对应的编号,Linux就是通过这个编号来操作对应的GPIO引脚。
获取GPIO的编号:gpiochipX中的“X”+gpiochipX中的第几个引脚 = GPIO的编号。
label:该组GPIO对应的标签,也就是名字
ngpio:该控制器所管理的GPIO引脚的数量,即:引脚编号范围是:base~base+ngpio-1
echo 3 > export
该命令会将 "3" 写入到 export 文件中。需要注意的是,如果 export 文件已经存在,这个命令会覆盖文件中的内容;如果 export 文件不存在,该命令会创建一个新的文件并将 "3" 写入其中。
命令的意思就是将编号为3的GPIO引脚导出,至于为什么导出3号,是因为开发板上唯一一个可控的灯在这
导出成功后会在/sys/class/gpio目录下生成了一个名为GPIO3的文件夹(和引脚编号对应)。
3. unexport:将导出的GPIO引脚删除。将引脚编号写入即可。
控制GPIO引脚主要是通过export导出的gpiox文件夹,这个文件夹中存在这一些可以配置GPIO参数的属性文件
。
注意:不是所有的GPIO引脚都可以导出,被内核使用的不能被导出,已经导出的GPIO引脚再次导出会报错。
上一步导出的gpiox文件夹中有如下几个文件:
direction:配置GPIO引脚为输入输出模式。可读可写,读表示查看当前GPIO是输入还是输出,写表示配置为输入还是输出,out输出,in输入
value:
在GPIO输出模式下:
写0:输出低电平
写1:输出高电平
在输入模式下:读取value文件就是获取当前GPIO的输入电平
非中断引脚:写入none
上升沿触发:写入rising
下降沿触发:写入falling
边沿触发:写入both
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define gpiox 3
static char gpio_path[100];
static int gpio_config(const char *attr, const char *val)
{
char file_path[100];
int len;
int fd;
sprintf(file_path, "%s/%s", gpio_path, attr);
if (0 > (fd = open(file_path, O_WRONLY))) {
perror("open error");
return fd;
}
len = strlen(val);
if (len != write(fd, val, len)) {
perror("write error");
close(fd);
return -1;
}
close(fd); //关闭文件
return 0;
}
int main(int argc, char *argv[])
{
if (2 != argc) {
fprintf(stderr, "error: %s <value>\n", argv[0]);
exit(-1);
}
/* 判断指定编号的 GPIO 是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", gpiox);
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
int fd;
int len;
if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
perror("open1 error");
exit(-1);
}
if (len != write(fd, gpiox, 1)) {//导出 gpio
perror("write1 error");
close(fd);
exit(-1);
}
close(fd); //关闭文件
}
/* 配置为输出模式 */
if (gpio_config("direction", "out"))
exit(-1);
/* 极性设置 */
if (gpio_config("active_low", "0"))
exit(-1);
/* 控制 GPIO 输出高低电平 */
if (gpio_config("value", argv[1]))
exit(-1);
/* 删除GPIO导出文件*/
if (0 > (fd = open("/sys/class/gpio/unexport", O_WRONLY))) {
perror("open2 error");
exit(-1);
}
if (len != write(fd, gpiox, 1)) {//导出 gpio
perror("write2 error");
close(fd);
exit(-1);
}
close(fd); //关闭文件
/* 退出程序 */
exit(0);
}
使用:./xxxxx x 第一个是路径,第二个是控制高低电平输出
和输出类似、略
配置略
获得value文件的文件描述符,使用poll来轮询。
其实就是通过读取/sys/class/gpio/gpiox/value的值来获取中断
。
poll() 和轮询读的概念有所不同。
poll() 是一个系统调用函数,在 Linux 系统中用于监视多个文件描述符上的事件。它可以同时监视多个文件描述符,并在有就绪事件时返回。通过 poll(),你可以一次性监视多个文件描述符的状态,而不需要使用多个线程或进程。
轮询读,通常是指在程序中使用循环来轮询(或检查)一个文件描述符是否有可读数据。这种方式下,你需要在循环中反复调用读取数据的操作(例如 read() 或 recv()),直到文件描述符上有数据可读取。
虽然 poll() 和轮询读都可以用于检查文件描述符上的事件或数据是否就绪,但它们的实现方式有些不同。
poll() 使用系统调用,在内核层面进行事件监视和等待操作。它能够监视多个文件描述符,并在有事件就绪时返回,以便你可以进行相应的处理。这种方式避免了不必要的循环检查,提高了效率。
轮询读是在应用程序中使用循环语句来反复检查文件描述符状态的方法,通常是通过使用非阻塞模式或超时机制,以确保没有数据可读时不会一直阻塞。在循环中,你需要手动地反复调用读取函数来获取数据,直到文件描述符上有数据可读。这种方式需要你自己管理循环和条件以及可能的超时逻辑。
总的来说,poll() 函数更为高效和灵活,适用于监视多个文件描述符上的事件,而轮询读通常用于单个文件描述符的状态检查和数据读取。选择使用哪种方法取决于你的需求和具体的应用场景。