使用 sysfs 方式操作 GPIO

和上一篇笔记 LED 应用编程一样,GPIO 也可以通过 sysfs 方式来控制。

在串口终端命令行进入 /sys/class/gpio 目录,

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程-LMLPHP

在该目录下可以看到 export、unexport 及几个 gpiochipx 文件(x 为 0,32,64,96,128,分别对应 GPIO0,GPIO1,GPIO2,GPIO3,GPIO4,即每一个 gpiochipx 对应一组 GPIO)

进入 gpiochip32 目录,

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程-LMLPHP

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 节点注释掉,

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程-LMLPHP

重新编译内核然后烧录到开发板后再次导出 GPIO0_B7,这次没有出现报错了,

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程-LMLPHP

GPIO0_B7 导出成功后,会在 /sys/class/gpio 目录下新增 gpio15 文件夹,进入该文件夹,会看到 active_low,direction,edge 和 value 等属性,

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程-LMLPHP

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

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程-LMLPHP

测试结构就是 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;
}

实验结果:

itop-3568 开发板系统编程学习笔记(19)GPIO 应用编程-LMLPHP

由于时间原因,中断功能没有做测试(代码中只有设置了中断触发的功能,没有做相应测试),原文测试代码在设置中断触发后,使用 poll 的方式轮询监测 GPIO0_B7(POLLPRI 事件),从而验证中断触发功能。

04-25 12:30