文章目录
- 目的
- 基础说明
- 代码示例
- 数字输出
- 数字输入
- 外部中断
- 总结
目的
GPIO嵌入式设备中最基础的外设,使用上也是非常频繁的。这篇文章将记录下应用程序中GPIO操作相关内容。
这篇文章中内容均在下面的开发板上进行测试:
《新唐NUC980使用记录:自制开发板(基于NUC980DK61YC)》
开发板中提供了两组共四个直连到GPIO口上的轻触按钮和发光二极管,可以方便地进行GPIO功能测试:
这篇文章是在下面文章基础上进行的:
新唐NUC980使用记录(5.10.y内核):在用户应用中使用GPIO
基础说明
通常Linux内核中会启用 Device Drivers -> GPIO Support -> /sys/class/gpio/...(sysfs interface)
功能。这样的话系统启动后可以通过 /sys/class/gpio/
目录下的文件来操作GPIO,基本的一些操作如下:
- 启用GPIO口
向/sys/class/gpio/export
文件写入GPIO编号
即可启用对应GPIO口;
启用成功后会生成/sys/class/gpio/gpio编号/
目录,之后通过该目录中的文件对该特定的GPIO口进行操作; - 设置GPIO口方向(输入或输出)
向/sys/class/gpio/gpioX/direction
文件写数据可以设置端口方向,in
表示输入、out
表示输出; - 设置或读取GPIO口电平
/sys/class/gpio/gpioX/value
文件可以设置或者读取端口电平值,默认情况下0
表示低电平、1
表示高电平; - 设置GPIO口外部中断触发方式
向/sys/class/gpio/gpioX/edge
文件写数据可以设置外部中断触发方式,none
无、rising
上升沿触发、falling
下降沿触发、both
双边触发;(有没有对应方式还得看硬件和设置等) - 取消使用GPIO口
向/sys/class/gpio/unexport
文件写入GPIO编号
即可取消使用对应端口; /sys/class/gpio/gpioX/active_low
用于设置逻辑值翻转,默认为0,即低电平逻辑值为0,高电平逻辑值为1,该文件值设置为1时将翻转逻辑值;
对于本文演示用的开发板上的GPIO口而言,其编号计算如下:
PB13 = 32 x 1(PA) + 13 = 45
PF10 = 32 x 5(PA/PB/PC/PD/PE) + 10 = 170
PE10 = 32 x 4(PA/PB/PC/PD) + 10 = 138
PE12 = 32 x 4(PA/PB/PC/PD) + 12 = 140
基于上面这些基础内容,嵌入式Linux中对于GPIO口的操作就是对上上面一些文件的操作。
代码示例
进入并创建相关目录:
cd ~/nuc980-sdk/
mkdir -p apps/gpio
cd apps/gpio/
gedit main.c
# 或者使用VS Code
# code .
# 创建 main.c 文件
编写代码后使用下面方式编译,然后拷贝程序到开发板上:
export PATH=$PATH:/home/nx/nuc980-sdk/buildroot-2023.02/output/host/bin
arm-linux-gcc main.c
# 开发板启用了SSH的话可以使用SCP命令将程序通过网络拷贝到开发板中
scp a.out root@192.168.31.142:/root/
数字输出
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int open_write(const char *file, const char *value)
{
int fd, len;
fd = open(file, O_WRONLY); // 以只写的方式打开文件
if (fd < 0)
{
return -1;
}
len = write(fd, value, strlen(value)); // 向文件写数据
if (len != strlen(value))
{
return -2;
}
close(fd);
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
printf("Usage: %s <gpio> <value>\n", argv[0]);
return -1;
}
char *file_export = "/sys/class/gpio/export";
char file_gpiox_direction[64];
char file_gpiox_value[64];
sprintf(file_gpiox_direction, "%s/gpio%s/direction", "/sys/class/gpio", argv[1]);
sprintf(file_gpiox_value, "%s/gpio%s/value", "/sys/class/gpio", argv[1]);
if (access(file_gpiox_value, F_OK)) // 检查文件是否存在
{
if (open_write(file_export, argv[1])) // 导出GPIO
{
printf("open_write %s %s failed!\n", file_export, argv[1]);
}
}
if (open_write(file_gpiox_direction, "out")) // 设置为输出模式
{
printf("open_write %s %s failed!\n", file_gpiox_direction, argv[1]);
}
if (open_write(file_gpiox_value, argv[2])) // 设置输出值
{
printf("open_write %s %s failed!\n", file_gpiox_value, argv[2]);
}
return 0;
}
上面操作时板子上对应的引脚上接的LED灯会有响应。
数字输入
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int open_write(const char *file, const char *value)
{
int fd, len;
fd = open(file, O_WRONLY); // 以只写的方式打开文件
if (fd < 0)
{
return -1;
}
len = write(fd, value, strlen(value)); // 向文件写数据
if (len != strlen(value))
{
return -2;
}
close(fd);
return 0;
}
int open_read(const char *file, char *value, int nbytes)
{
int fd, len;
fd = open(file, O_RDONLY); // 以只读的方式打开文件
if (fd < 0)
{
return -1;
}
len = read(fd, value, nbytes); // 从文件读取数据
if (len != nbytes)
{
return -2;
}
close(fd);
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("Usage: %s <gpio>\n", argv[0]);
return -1;
}
char *file_export = "/sys/class/gpio/export";
char file_gpiox_direction[64];
char file_gpiox_value[64];
sprintf(file_gpiox_direction, "%s/gpio%s/direction", "/sys/class/gpio", argv[1]);
sprintf(file_gpiox_value, "%s/gpio%s/value", "/sys/class/gpio", argv[1]);
if (access(file_gpiox_value, F_OK)) // 检查文件是否存在
{
if (open_write(file_export, argv[1])) // 导出GPIO
{
printf("open_write %s %s failed!\n", file_export, argv[1]);
}
}
if (open_write(file_gpiox_direction, "in")) // 设置为输入模式
{
printf("open_write %s %s failed!\n", file_gpiox_direction, argv[1]);
}
char value[2] = {0};
if (open_read(file_gpiox_value, value, 1)) // 读取端口值
{
printf("open_read %s failed!\n", file_gpiox_value);
}
else
{
printf("open_read %s value is %s \n", file_gpiox_value, value);
}
return 0;
}
上面操作时板子上对应的引脚上接的按钮在按下和松开时得到的值会不同。
外部中断
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <stdlib.h>
int open_write(const char *file, const char *value)
{
int fd, len;
fd = open(file, O_WRONLY); // 以只写的方式打开文件
if (fd < 0)
{
return -1;
}
len = write(fd, value, strlen(value)); // 向文件写数据
if (len != strlen(value))
{
return -2;
}
close(fd);
return 0;
}
int open_wait_read(const char *file, char *value, int nbytes, int timeout)
{
int ret, len;
struct pollfd fds;
nfds_t nfds = 1;
fds.fd = open(file, O_RDONLY); // 以只读的方式打开文件
if (fds.fd < 0)
{
return -1;
}
read(fds.fd, value, nbytes); // 先读取一次,以免触发第一次不期望的中断
lseek(fds.fd, 0, SEEK_SET); // 移动文件指针到头部
fds.events = POLLPRI; // 有紧急数据需要读取
ret = poll(&fds, nfds, timeout); // 等待事件触发, timeout 为 -1 时将不会超时
if ((ret > 0) && (fds.revents & POLLPRI))
{
len = read(fds.fd, value, nbytes); // 从文件读取数据
if (len != nbytes)
{
return -2;
}
}
else if (ret == 0)
{
return -3; // timeout
}
else
{
return -4; // poll error
}
close(fds.fd);
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 4)
{
printf("Usage: %s <gpio> <edge> <timeout>\n", argv[0]);
printf(" <edge>: none, rising, falling, both\n");
return -1;
}
char *file_export = "/sys/class/gpio/export";
char file_gpiox_direction[64];
char file_gpiox_value[64];
char file_gpiox_edge[64];
sprintf(file_gpiox_direction, "%s/gpio%s/direction", "/sys/class/gpio", argv[1]);
sprintf(file_gpiox_value, "%s/gpio%s/value", "/sys/class/gpio", argv[1]);
sprintf(file_gpiox_edge, "%s/gpio%s/edge", "/sys/class/gpio", argv[1]);
if (access(file_gpiox_value, F_OK)) // 检查文件是否存在
{
if (open_write(file_export, argv[1])) // 导出GPIO
{
printf("open_write %s %s failed!\n", file_export, argv[1]);
}
}
if (open_write(file_gpiox_direction, "in")) // 设置为输入模式
{
printf("open_write %s %s failed!\n", file_gpiox_direction, argv[1]);
}
if (open_write(file_gpiox_edge, argv[2])) // 设置中断触发方式
{
printf("open_write %s %s failed!\n", file_gpiox_edge, argv[2]);
}
char value[2] = {0};
int ret = open_wait_read(file_gpiox_value, value, 1, atoi(argv[3])); // 等待事件触发读取数据
if (ret == 0)
{
printf("open_wait_read %s value is %s \n", file_gpiox_value, value);
}
else if (ret == -3)
{
printf("open_wait_read timeout!\n", file_gpiox_value);
}
else
{
printf("open_wait_read %s failed!\n", file_gpiox_value);
}
return 0;
}
总结
嵌入式Linux中对基础GPIO的操作非常简单,其实就是对文件的操作而已。