本文记录了如何使用Linux上经典的sysfs接口控制GPIO。不同于灵眸官方文档介绍的较新的libgpiod接口,sysfs接口可以在shell环境下进行控制,非常方便进行测试和演示。本文会首先介绍一些背景知识,然后在shell中交互式演示,最后通过编写C语言程序读写sysfs进行GPIO控制。
一、硬件部分
本篇的硬件包含EASY EAI Nano开发板和三色LED灯,EASY EAI Nano开发板的40pin GPIO扩展接口参考官方文档。
1.1 开发板的GPIO扩展接口
EASY EAI Nano的GPIO硬件资源以及复用关系如下图所示。
EASY EAI Nano默认开启了下方三个GPIO引脚资源,对应的接口描述情况如下所示。
1.2 GRB三色LED灯
三色LED灯,外观长这样:
1.3 硬件连接
两者直接连接关系如下:
具体连接关系为:
- R —— GPIO3_B2
- G —— GPIO3_B3
- B —— GPIO3_C4
- GND —— GND(39)
二、软件部分
2.1 背景知识
首先需要介绍sysfs,其次是GPIO的sysfs接口。sysfs 是由 Linux 内核提供的伪文件系统(并不是在磁盘上真实存在的文件),它通过虚拟文件在用户空间中提供了各种内核子系统、硬件设备和设备驱动程序的信息。GPIO 设备通常也通过 sysfs 提供了一些接口。
2.2 shell中读写sysfs控制GPIO
和之前的实验类似,给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引脚编号的规则,具体如下图所示
在使用某一引脚前,需要先导出该引脚资源:
echo 106 > /sys/class/gpio/export
接着设置该引脚的方向,输入或者输出:
echo in > /sys/class/gpio/gpio106/direction
echo out > /sys/class/gpio/gpio106/direction
根据引脚的工作模式,做相应的控制,写入电平或读取电平:
echo 1 > /sys/class/gpio/gpio106/value
echo 0 > /sys/class/gpio/gpio106/value
引脚使用完毕后,需要手动向gpio管理器申请释放该引脚资源:
echo 106 > /sys/class/gpio/unexport
2.3 C语言读写sysfs控制GPIO
有了以上介绍后,我们就可以使用C语言编写一个控制三色LED灯闪烁的程序了:
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文件。
2.4 编译、运行C语言控制三色灯程序
接下来,编译该文件为blink可执行程序:
arm-linux-gnueabihf-gcc -Wall -o blink blink.c
编译成功后,将会生成blink文件。
接着,将blink文件推送到开发板的/home目录中:
adb push blink /home
推送到开发板上之后,我们就可以运行该程序了。
使用如下命令,运行开发板上的blink程序:
adb shell /home/blink
此时,应该可以看到LED灯开始交替闪烁三种颜色。
三、参考链接
- 【灵眸官方文档】“EASY-EAI-Toolkit|硬件外设组件|GPIO”篇: https://www.easy-eai.com/document_details/3/142
- 【Linux内核文档】GPIO sysfs接口文档: https://www.kernel.org/doc/html/latest/admin-guide/gpio/sysfs.html
- 【关于树莓派的一篇文章】GPIO编程——使用sysfs接口: https://www.ics.com/blog/gpio-programming-using-sysfs-interface