使用 sysfs 方式操作 GPIO
和上一篇笔记 LED 应用编程一样,GPIO 也可以通过 sysfs 方式来控制。
在串口终端命令行进入 /sys/class/gpio 目录,
在该目录下可以看到 export、unexport 及几个 gpiochipx 文件(x 为 0,32,64,96,128,分别对应 GPIO0,GPIO1,GPIO2,GPIO3,GPIO4,即每一个 gpiochipx 对应一组 GPIO)
进入 gpiochip32 目录,
base 就是 gpiochipx 的 x 值,
label 为 gpiochipx 所对应的 GPIO 组,比如 gpiochip32 的 label 为 “gpio1”,
ngpio 为 gpiochipx 所管理的 gpio 数量,该属性为 32(一个 gpio 组管理 32 个 gpio)。
接下来进入正题——如何操作某一个 gpio,
要实现 sysfs 操作 gpio, 需要用上 /sys/class/gpio 下的 export 文件,
export 用来导出指定的 GPIO,只有 GPIO 被导出到 /sys/class/gpio 目录后才可以操作它,export 是一个只写文件,导出方法为 echo num > export
(num 为 gpio 编号)
GPIO0_B7 也是我们需要用到的 IO,对应开发板上的 LED,接下来我们使用 echo 15 > export
导出 GPIO0_B7,不过这里我失败了,报了错误:write error: Device or resource busy
(因为 GPIO0_B7 已经被用在 /sys/class/leds 中)
解决办法是将设备树里的 leds 节点注释掉,
重新编译内核然后烧录到开发板后再次导出 GPIO0_B7,这次没有出现报错了,
GPIO0_B7 导出成功后,会在 /sys/class/gpio 目录下新增 gpio15 文件夹,进入该文件夹,会看到 active_low,direction,edge 和 value 等属性,
direction 为 GPIO 的输入输出模式,“in” 为输入模式,“out” 为输出模式;
value 为 gpio 电平状态,默认 1 表示高电平,0 表示低电平;
active_low 用于控制极性,该属性默认为 0,当 active_low 为 1 时,value 极性反向,即 value 为 1 表示引脚为低电平,value 为 0 表示引脚为高电平;
edge 用于控制中断触发方式,只有 GPIO 为输入模式时,中断才可用。可取值包括:
比如现在要让 GPIO0_B7 对应的 LED 点亮,只需要在 /sys/class/gpio/gpio15 目录下输入:
echo out > direction
echo 1 > value
测试结构就是 LED 被点亮,开发板效果就不展示了。
GPIO 应用编程
实验代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char** argv)
{
int fd1, fd2, fd3;
int ret = 0;
int mode = 0, val = 0, edge = 0;
char gpio_path[50] = {0};
char tmp_path[60] = {0};
char buff[20] = {0};
// 判断输入的参数
if(argc >= 3 && argc <= 4)
{
// GPIO 编号不符合要求
if((atoi(argv[1]) < 0 )|| (atoi(argv[1]) > 159))
{
ret = 1;
goto format_err;
}
// 输出模式
if(!strcmp(argv[2], "out"))
{
mode = 0; // 输出模式
// 参数有误
if(argc != 4)
{
ret = 2;
goto format_err;
}
if((atoi(argv[3]) != 0) && (atoi(argv[3]) != 1))
{
ret = 3;
goto format_err;
}
}
// 输入模式
else if(!strcmp(argv[2], "in"))
{
// 无中断
if(argc == 3)
{
mode = 1; // 无中断输入模式
}
// 有中断
else if(argc == 4)
{
mode = 2; // 有中断触发模式
// 判断中断触发模式
if(!strcmp(argv[3], "none"))
edge = 0;
else if(!strcmp(argv[3], "rising"))
edge = 1;
else if(!strcmp(argv[3], "falling"))
edge = 2;
else if(!strcmp(argv[3], "both"))
edge = 3;
else // 格式有误
{
ret = 4;
goto format_err;
}
}
}
else // 模式不符合要求
{
ret = 5;
goto format_err;
}
}
else // 参数数量不符合要求
{
ret = 6;
goto format_err;
}
format_err:
if(ret != 0) // 格式有误
{
printf("The right format: ./app <GPIO_NUM> <DIR> [VAL] [EDGE]\n\
MODE1: ./app <0~159> out <0/1>\n\
MODE2: ./app <0~159> in\n\
MODE3: ./app <0~159> in <none/rising/falling/both>\n");
switch(ret)
{
case 1: printf("ERROR: gpio num is invalid.\n");break;
case 2: printf("ERROR: value not specified.\n");break;
case 3: printf("ERROR: value is invalid.\n");break;
case 4: printf("ERROR: edge is invalid.\n");break;
case 5: printf("ERROR: direction is invalid.\n");break;
}
return 0;
}
// 合成路径,如 "/sys/class/gpio/gpio15"
sprintf(gpio_path , "/sys/class/gpio/gpio%s", argv[1]);
// gpio_path 不存在
if(access(gpio_path, F_OK))
{
int fd, ret;
fd = open("/sys/class/gpio/export", O_WRONLY);
if(fd < 0)
{
printf("/sys/class/gpio/export open failed.\n");
return 0;
}
printf("/sys/class/gpio/export open successfully.\n");
ret = write(fd, argv[1], strlen(argv[1]));
if(ret < 0)
{
printf("/sys/class/gpio/export write failed.\n");
return 0;
}
close(fd);
}
// 打开 /sys/class/gpio/gpiox/direction
sprintf(tmp_path, "%s/direction", gpio_path);
fd1 = open(tmp_path, O_WRONLY);
if(fd1 < 0)
{
printf("%s open failed.\n", tmp_path);
return 0;
}
printf("%s open successfully.\n", tmp_path);
// 打开 /sys/class/gpio/gpiox/value
sprintf(tmp_path, "%s/value", gpio_path);
fd2 = open(tmp_path, O_RDWR);
if(fd2 < 0)
{
printf("%s open failed.\n", tmp_path);
return 0;
}
printf("%s open successfully.\n", tmp_path);
// 打开 /sys/class/gpio/gpiox/edge
sprintf(tmp_path, "%s/edge", gpio_path);
fd3 = open(tmp_path, O_WRONLY);
if(fd3 < 0)
{
printf("%s open failed.\n", tmp_path);
return 0;
}
printf("%s open successfully.\n", tmp_path);
// 输出模式
if(mode == 0)
{
// 设置输出方向
ret = write(fd1, argv[2], strlen(argv[2]));
if(ret < 0)
{
printf("%s/direction write failed.\n", gpio_path);
return 0;
}
printf("%s/direction write successfully.\n", gpio_path);
// 设置电平
ret = write(fd2, argv[3], strlen(argv[3]));
if(ret < 0)
{
printf("%s/value write failed.\n", gpio_path);
return 0;
}
printf("%s/value write successfully.\n", gpio_path);
}
// 无中断输入模式
if(mode == 1)
{
// 设置输出方向
ret = write(fd1, argv[2], strlen(argv[2]));
if(ret < 0)
{
printf("%s/direction write failed.\n", gpio_path);
return 0;
}
printf("%s/direction write successfully.\n", gpio_path);
// 获取 IO 电平
ret = read(fd2, buff, 1);
if(ret < 0)
{
printf("%s/value read failed.\n", gpio_path);
return 0;
}
printf("%s/value is %s.\n", gpio_path, buff);
}
// 中断触发模式
if(mode == 2)
{
// 设置输出方向
ret = write(fd1, argv[2], strlen(argv[2]));
if(ret < 0)
{
printf("%s/direction write failed.\n", gpio_path);
return 0;
}
printf("%s/direction write successfully.\n", gpio_path);
// 设置触发模式
ret = write(fd3, argv[3], strlen(argv[3]));
if(ret < 0)
{
printf("%s/edge write failed.\n", gpio_path);
return 0;
}
printf("%s/edge write successfully.\n", gpio_path);
}
// 关闭设备文件
close(fd1);
close(fd2);
close(fd3);
return 0;
}
实验结果:
由于时间原因,中断功能没有做测试(代码中只有设置了中断触发的功能,没有做相应测试),原文测试代码在设置中断触发后,使用 poll 的方式轮询监测 GPIO0_B7(POLLPRI 事件),从而验证中断触发功能。