本文记录了如何使用Linux上经典的sysfs接口控制GPIO。不同于灵眸官方文档介绍的较新的libgpiod接口,sysfs接口可以在shell环境下进行控制,非常方便进行测试和演示。本文会首先介绍一些背景知识,然后在shell中交互式演示,最后通过编写C语言程序读写sysfs进行GPIO控制。
本篇的硬件包含EASY EAI Nano开发板和三色LED灯,EASY EAI Nano开发板的40pin GPIO扩展接口参考官方文档。
EASY EAI Nano的GPIO硬件资源以及复用关系如下图所示。
EASY EAI Nano默认开启了下方三个GPIO引脚资源,对应的接口描述情况如下所示。
三色LED灯,外观长这样:
两者直接连接关系如下:
具体连接关系为:
首先需要介绍sysfs,其次是GPIO的sysfs接口。sysfs 是由 Linux 内核提供的伪文件系统(并不是在磁盘上真实存在的文件),它通过虚拟文件在用户空间中提供了各种内核子系统、硬件设备和设备驱动程序的信息。GPIO 设备通常也通过 sysfs 提供了一些接口。
和之前的实验类似,给EASY EAI Nano开发板接上电源并通过USB线连接到PC后,再将设备连接到VMWare Workstation Player的虚拟机中,我们就可以通过 adb shell登录到设备的shell会话中了:
首先执行如下命令:
ls /sys/class/gpio/
可以看到类似如下输出:
接下来我们将看看如何使用这个接口。注意,以“gpiochip”开头的设备名称是 GPIO 控制器,我们不会直接使用它们。
从 sysfs 接口使用 GPIO 引脚的基本步骤如下:
要导出引脚,我们需要将引脚编号写入伪文件 /sys/class/gpio/export。相应的取消导出引脚,也是将引脚编号写入到 /sys/class/gpio/unexport 文件。所以,开始之前,需要先了解一下引脚编号规则。
灵眸官网文档中已经提供了GPIO引脚编号的规则,具体如下图所示
在使用某一引脚前,需要先导出该引脚资源:
# 导出 GPIO3_B2 :
echo 106 > /sys/class/gpio/export
接着设置该引脚的方向,输入或者输出:
# 设置 GPIO3_B2 为输入:
echo in > /sys/class/gpio/gpio106/direction
# 设置 GPIO3_B2 为输出:
echo out > /sys/class/gpio/gpio106/direction
根据引脚的工作模式,做相应的控制,写入电平或读取电平:
# 输出高电平,此时三色灯应该会发出红色光
echo 1 > /sys/class/gpio/gpio106/value
# 输出低电平
echo 0 > /sys/class/gpio/gpio106/value
# 读取输入(如果为输入模式,则可以读取到引脚实际的电平状态):
# cat /sys/class/gpio/gpio106/value
引脚使用完毕后,需要手动向gpio管理器申请释放该引脚资源:
# 释放引脚
echo 106 > /sys/class/gpio/unexport
有了以上介绍后,我们就可以使用C语言编写一个控制三色LED灯闪烁的程序了:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define GPIO_PIN(m, p, n) ((m)*32 + (p)*8 + n)
#define DIR_IN "in"
#define DIR_OUT "out"
#define VAL_LOW "0"
#define VAL_HIGH "1"
bool file_write(const char *path, const char *text)
{
FILE *fptr = NULL;
if (!path || !text)
{
printf("%s: invalid argument!", __FUNCTION__);
return false;
}
fptr = fopen(path, "w");
if (!fptr)
{
printf("fopen %s failed!\n", path);
return false;
}
if (fputs(text, fptr) < 0)
{
printf("write %s failed: %s!\n", path, strerror(ferror(fptr)));
fclose(fptr);
}
fclose(fptr);
return true;
}
void msleep(int ms)
{
usleep(ms * 1000);
}
bool gpio_export(int gpio_pin)
{
char number[PATH_MAX];
snprintf(number, sizeof(number), "%d", gpio_pin);
return file_write("/sys/class/gpio/export", number);
}
bool gpio_unexport(int gpio_pin)
{
char number[PATH_MAX];
snprintf(number, sizeof(number), "%d", gpio_pin);
return file_write("/sys/class/gpio/unexport", number);
}
bool gpio_set_dir(int gpio_pin, const char *value)
{
char path[PATH_MAX] = {0};
snprintf(path, sizeof(path),
"/sys/class/gpio/gpio%d/direction", gpio_pin);
return file_write(path, value);
}
bool gpio_set_value(int gpio_pin, const char *value)
{
char path[PATH_MAX] = {0};
snprintf(path, sizeof(path),
"/sys/class/gpio/gpio%d/value", gpio_pin);
return file_write(path, value);
}
enum
{
A = 0,
B,
C,
D,
};
int led_pins[] = {
GPIO_PIN(3, B, 2), // GPIO3_B2
GPIO_PIN(3, B, 3), // GPIO3_B3
GPIO_PIN(3, C, 4), // GPIO3_C4
};
int main(int argc, char *argv[])
{
int i = 0;
int loops = argc > 1 ? atoi(argv[1]) : 10;
for (i = 0; i < ARRAY_SIZE(led_pins); i++)
{
gpio_export(led_pins[i]);
gpio_set_dir(led_pins[i], DIR_OUT);
}
while (loops--)
{
printf("loops: %d ...\n", loops);
for (i = 0; i < ARRAY_SIZE(led_pins); i++)
{
gpio_set_value(led_pins[i], VAL_HIGH);
msleep(250);
gpio_set_value(led_pins[i], VAL_LOW);
}
msleep(250);
}
for (i = 0; i < ARRAY_SIZE(led_pins); i++)
{
gpio_unexport(led_pins[i]);
}
return 0;
}
将其保存到~/workspace/blink目录,命名为blink.c文件。
接下来,编译该文件为blink可执行程序:
arm-linux-gnueabihf-gcc -Wall -o blink blink.c
编译成功后,将会生成blink文件。
接着,将blink文件推送到开发板的/home目录中:
adb push blink /home
推送到开发板上之后,我们就可以运行该程序了。
使用如下命令,运行开发板上的blink程序:
adb shell /home/blink
此时,应该可以看到LED灯开始交替闪烁三种颜色。
更多回帖