嵌入式Linux应用开发笔记:GPIO基础输入输出

目的

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;
}

在这里插入图片描述
上面操作时板子上对应的引脚上接的按钮在按下和松开时得到的值会不同。

外部中断(poll方式)

#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的操作非常简单,其实就是对文件的操作而已。

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naisu Xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值