文件 | 作用 |
---|---|
block | 包含所有的块设备 |
devices | 包含系统的所有设备并根据设备挂接的总线类型组织成层次结构 |
bus | 包含系统中的所有总线类型 |
drivers | 包含内核中已经注册的设备驱动程序 |
class | 包含系统中设备类型(网卡设备、声卡设备、输入设备等)。 |
Linux2.6内核引入了sysfs(虚拟文件系统),sysfs的作用是将注册进系统中的设备、总线和驱动组织成一个个分级的文件,并直观地将驱动和设备的层次结构以文件的形式展示在用户空间,通过操作这些文件,系统向用户空间导出内核数据结构以及他们的属性,这样一来用户空间可以通过修改sysfs的文件属性来修改设备的属性值,进而改变设备的工作状态。比如通过sysfs文件系统可以直接向指定的gpio引脚输入和输出高低电平。
给出一个通过sysfs控制led的实例,以下是led的原理图和引脚说明:
从原理图上可以看出,led低电平被点亮,高电平被熄灭。另外,对于sysfs文件系统,gpio的操作具有特殊的方式。首先计算出gpio对应的编号,编号等于group * 32 + pin,如:
描述 | 名称 | 计算 | 编号 |
---|---|---|---|
LED1 | GPIO3_26 | 3*32+26 | 122 |
LED2 | GPIO3_22 | 3*32+22 | 118 |
LED3 | GPIO3_20 | 3*32+20 | 116 |
LED4 | GPIO2_7 | 2*32+7 | 71 |
一、终端模式下操作gpio
进入到/sys/class/gpio/目录下
,ls查看该目录下的文件。
文件说明:
1、export
用于获取gpio的控制接口,比如echo 71 > export
获取GPIO2_7的控制接口。
进入gpio71下,该目录下的文件是控制GPIO2_7属性的接口,比如输入输出方向、电平、触发方式、电源、有效电平等。
通过echo
命令可以改变gpio的属性,向direction文件写入in/out
改变gpio输入输出方向;向edge文件写入none/rising/falling/both
改变gpio的触发方式;向value文件写入0/1
就是向gpio写入高低电平,假如gpio为输入模式,gpio就是输入0/1
,假如gpio为输出模式,gpio就是输出0/1
。比如:想让GPIO2_7输出高电平,则echo out > direction
、echo 1 > value
。
2、unexport
用于取消gpio的控制接口,比如echo 71 > unexport
取消GPIO2_7的控制接口,此时gpio71这个文件会消失。
3、gpiochipX
保存系统中GPIO寄存器的信息,包括每个寄存器控制引脚的起始编号base、寄存器名称、引脚总数。每个寄存器控制32个引脚,一共有4组寄存器。
二、c语言操作sysfs gpio实现流水灯
为实现这个功能,程序的整体设计思想其实很简单,我们不需要关心led底层驱动是如何实现的,我们只需要关心如何将gpio控制接口导出,然后对其进行一般的I/O操作即可。需要注意的是,不同版本的Linux系统,gpio控制接口的命名可能不同,但是对sysfs下gpio控制接口的操作是一样的。下文先给出操作gpio的各个功能函数,然后给出实现流水灯的测试函数,并附上整个工程的源码地址。
1、判断gpio控制接口是否被导出
/*
功能:判断gpio控制接口是否已经被导出
参数:
gpio:传入gpio引脚编号(GPIO3_26 = 3*32 + 26 = 122)
返回值:
-1 : 没有被导出
0 :已经被导出
*/
int gpio_is_exported(int gpio)
{
char buf[50];//class/gpio下的gpio文件
sprintf(buf,"/sys/class/gpio/gpio%d",gpio);
if(access(buf,F_OK) != -1)
{
return 0;
}
else
{
return -1;
}
}
2、导出gpio控制接口
/*
功能:在sys下导出gpio控制接口
参数:
gpio:传入gpio引脚编号
返回值:
0:导出成功
-1:导出失败
*/
int gpio_export(int gpio)
{
int fd = -1;
char buf[4];
int rv = -1;
fd = open("/sys/class/gpio/export",O_RDWR);
if(fd < 0)
{
return -1;
}
sprintf(buf,"%d",gpio);
rv = write(fd,buf,strlen(buf));
if(rv < 0)
{
return -1;
}
close(fd);
return 0;
}
3、取消gpio控制接口
/*
功能:在sys下取消gpio
参数:
gpio:gpio编号
返回值:
成功返回0
失败返回-1
*/
int gpio_unexport(int gpio)
{
int fd = -1;
char buf[4];
int rv = -1;
fd = open("/sys/class/gpio/unexport",O_RDWR);
if(fd < 0)
{
return -1;
}
sprintf(buf,"%d",gpio);
rv = write(fd,buf,strlen(buf));
if(rv < 0)
{
return -1;
}
close(fd);
return 0;
}
4、设置gpio的方向
/*
功能:设置gpio的方向
参数:
gpio:gpio编号
direction:gpio的方向(out/in)
返回值:
-1:失败
0:成功
*/
int gpio_set_direction(int gpio,char *direction)
{
int len = 0;
char buf[50];
int fd = -1;
int rv = -1;
if(direction == NULL)
{
//字符串指针为空
return -1;
}
len = strlen(direction);
if(len == 0)
{
//字符串长度为0
return -1;
}
sprintf(buf,"/sys/class/gpio/gpio%d/direction",gpio);
fd = open(buf,O_RDWR);
if(fd < 0)
{
return -1;
}
rv = write(fd,direction,len);
if(rv < 0)
{
//写入方向出错
return -1;
}
close(fd);
return 0;
}
5、设置触发方式
/*
功能:设置触发方式
参数:
gpio:gpio编号
edge:
none表示引脚为输入,不是中断引脚
rising表示引脚为中断输入,上升沿触发
falling表示引脚为中断输入,下降沿触发
both表示引脚为中断输入,边沿触发
0-->none, 1-->rising, 2-->falling, 3-->both
返回值:
成功返回0
失败返回-1
*/
int gpio_set_edge(int gpio,int edge)
{
char buf[50];
int fd = -1;
sprintf(buf,"/sys/class/gpio/gpio%d/edge",gpio);
fd = open(buf,O_RDWR);
if(fd < 0)
{
return -1;
}
switch(edge)
{
case 0:
write(fd,"none",4);
break;
case 1:
write(fd,"rising",6);
break;
case 2:
write(fd,"falling",7);
break;
case 3:
write(fd,"both",4);
default:
break;
}
close(fd);
return 0;
}
6、设置gpio电平
功能:向gpio写入高低电平(0/1)
参数:
gpio:引脚编号
value:高低电平(0/1)
返回值:
成功返回0
失败返回-1
*/
int gpio_set_value(int gpio,int value)
{
char buf[50];
int rv = -1;
int fd = -1;
sprintf(buf,"/sys/class/gpio/gpio%d/value",gpio);
fd = open(buf,O_RDWR);
if(fd < 0)
{
return -1;
}
if(value)
{
write(fd,"1",1);
}
else
{
write(fd,"0",1);
}
close(fd);
return 0;
}
7、获取gpio电平
/*
功能:获取gpio的电平
参数:
gpio:gpio编号
返回值:
成功返回gpio的值
失败返回-1
*/
int gpio_get_value(int gpio)
{
char buf[50];
int rv = -1;
int fd = -1;
char value;
sprintf(buf,"/sys/class/gpio/gpio%d/value",gpio);
fd = open(buf,O_RDWR);
if(fd < 0)
{
return -1;
}
rv = read(fd,&value,1);
if(rv < 0)
{
return -1;
}
close(fd);
return (value - '0');
}
8、主函数
#include <stdio.h>
#include <signal.h>
#include <sys/select.h>
#include "imx283A_gpio.h"
//定义LED数组
int led[5] =
{
0,
3*32+26,//LED1
3*32+22,//LED2
3*32+20,//LED3
2*32+7//LED4
};
static int g_stop = 0;
//信号函数
void sig_stop(int signo)
{
if(signo == SIGINT)
{
g_stop = 1;
}
}
//毫秒级延时
static void sleep_ms(unsigned int ms)
{
struct timeval t;
t.tv_sec=ms/1000;
t.tv_usec=(ms*1000)%1000000;
select(0,NULL,NULL,NULL,&t);
}
int main(int argc,char **argv)
{
int rv = -1;
int i = 0;
//安装信号
signal(SIGINT,sig_stop);
//导入gpio
for(i = 1; i < 5; i++)
{
if(gpio_is_exported(led[i]) < 0)
{
gpio_export(led[i]);
}
}
//设置gpio输出方向
for(i = 1; i < 5; i++)
{
gpio_set_direction(led[i],"out");
gpio_set_value(led[i],1);//关掉所有led
}
//循环流水灯
i = 1;
while(!g_stop)
{
sleep_ms(500);
gpio_set_value(led[i],0);
sleep_ms(500);//延时500ms
gpio_set_value(led[i],1);
if( i == 4)
{
i = 0;
}
i++;
}
//导出gpio
for(i = 1; i < 5; i++)
{
gpio_set_value(led[i],1);
gpio_unexport(led[i]);
}
return 0;
}
9、交叉编译Makefile
使用开发板对应的交叉编译器编译上面程序,make
之后会在当前目录下生成可执行程序led
,然后通过tftp -gr
命令下载到开发板上运行。需要注意的是在开发板上运行led
之前,需要给/sys
目录以及子目录赋给可读写权限,否则会出现权限限制错误。
objects = $(patsubst %.c,%.o,$(wildcard *.c))
CC = /opt/xtools/EasyArm-imax280A/gcc-4.4.4-glibc-2.11.1-multilib-1.0/arm-fsl-linux-gnueabi/bin/arm-linux-cc
CFLAGS = -g
TARGET = led
$(TARGET):$(objects)
$(CC) $(CFLAGS) -o $@ $^
#声明为伪目标,防止出现史料未及的错误,比如硬盘中存在一个名为clean的文件。
.PHONY:clean
clean:
rm -rf *.o
10、效果
gpio的应用不仅仅是点亮几个led而已,它可以焊接很多传感器,通过gpio的输入输出控制传感器,进而获取传感器的数据。引入sysfs文件系统后,Linux应用开发不需要关心底层驱动的实现,使得用户空间操作gpio变得简单。
工
程
源
码
:
\color{red}{工程源码:}
工程源码:git地址
微信公众号: