imx6ull/linux应用编程学习(2)GPIO编程(下)

1.GPIO输入

加入了些自己的注释

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

// 全局变量用于存储GPIO路径
static char gpio_path[100];

// 配置GPIO属性的函数
static int gpio_config(const char *attr, const char *val)
{
    char file_path[100];  // 用于存储文件路径
    int len;  // 字符串长度
    int fd;  // 文件描述符

    // 生成属性文件的完整路径
    sprintf(file_path, "%s/%s", gpio_path, attr);

    // 打开属性文件
    if (0 > (fd = open(file_path, O_WRONLY))) {
        perror("open error");  // 打开文件出错时输出错误信息
        return fd;
    }

    len = strlen(val);  // 获取值字符串的长度

    // 写入值到属性文件
    if (len != write(fd, val, len)) {
        perror("write error");  // 写入出错时输出错误信息
        close(fd);  // 关闭文件
        return -1;
    }

    close(fd);  // 关闭文件
    return 0;
}

int main(int argc, char *argv[])
{
    char file_path[100];  // 用于存储文件路径
    char val;  // 用于存储读取的值
    int fd;  // 文件描述符

    // 校验传参个数,必须是2个
    if (2 != argc) {
        fprintf(stderr, "usage: %s <gpio>\n", argv[0]);  // 提示正确用法
        exit(-1);
    }

    // 生成GPIO路径
    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);

    // 检查GPIO目录是否存在
    if (access(gpio_path, F_OK)) {  // 如果目录不存在,则需要导出

        int len;  // 字符串长度

        // 打开导出文件
        if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
            perror("open error");  // 打开文件出错时输出错误信息
            exit(-1);
        }

        len = strlen(argv[1]);  // 获取GPIO编号字符串的长度

        // 写入GPIO编号到导出文件
        if (len != write(fd, argv[1], len)) {  // 导出GPIO
            perror("write error");  // 写入出错时输出错误信息
            close(fd);  // 关闭文件
            exit(-1);
        }

        close(fd);  // 关闭文件
    }

    // 配置GPIO为输入模式
    if (gpio_config("direction", "in"))
        exit(-1);

    // 配置GPIO极性为默认(不反转)
    if (gpio_config("active_low", "0"))
        exit(-1);

    // 配置GPIO为非中断方式
    if (gpio_config("edge", "none"))
        exit(-1);

    // 读取GPIO电平状态
    sprintf(file_path, "%s/%s", gpio_path, "value");

    // 打开值文件
    if (0 > (fd = open(file_path, O_RDONLY))) {
        perror("open error");  // 打开文件出错时输出错误信息
        exit(-1);
    }

    // 读取值
    if (0 > read(fd, &val, 1)) {
        perror("read error");  // 读取出错时输出错误信息
        close(fd);  // 关闭文件
        exit(-1);
    }

    // 输出读取的值
    printf("value: %c\n", val);

    // 关闭文件并退出程序
    close(fd);
    exit(0);
}

这段代码和输出差不多,详细解释可以看我上一篇文章:

imx6ull/linux应用编程学习(2)GPIO编程(上)-CSDN博客

我这里只讲增加的那部分:

1.sprintf(file_path, "%s/%s", gpio_path, "value");意思是将file_path路径写为gpio_path/value,gpio_path的路径在上面函数中定义。

2.

 // 读取值
    if (0 > read(fd, &val, 1)) {
        perror("read error");  // 读取出错时输出错误信息
        close(fd);  // 关闭文件
        exit(-1);
    }

读取fd指向的文件的值并且存入val。

最后输出读取的值
 

还有一点不同的是传参长度从3变成了2。

测试:

关于${CC}环境变量配置,可以看我另外一篇文章。

之后连入开发板测试:

scp命令传到开发板

 scp -r book@192.168.5.12:/home/book/project/APP/app/16_gpio/gpio_in /home/root/app

scp -r 【用户名】@【ubuntu的ip地址】:【unbuntu上要传入文件的路径】  【开发板要存入的路径】

目前GPIO1_IO1输入为0,根据原理图,GPIO1_IO1在mini板对应的是7脚,我们将其接入3.3V电压,在执行命令。

成功读取到了,测试成功。

2.GPIO中断

依然是源码加入了些注释

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>

// 全局变量用于存储GPIO路径
static char gpio_path[100];

// 配置GPIO属性的函数
static int gpio_config(const char *attr, const char *val)
{
    char file_path[100];  // 用于存储文件路径
    int len;  // 字符串长度
    int fd;  // 文件描述符

    // 生成属性文件的完整路径
    sprintf(file_path, "%s/%s", gpio_path, attr);

    // 打开属性文件
    if (0 > (fd = open(file_path, O_WRONLY))) {
        perror("open error");  // 打开文件出错时输出错误信息
        return fd;
    }

    len = strlen(val);  // 获取值字符串的长度

    // 写入值到属性文件
    if (len != write(fd, val, len)) {
        perror("write error");  // 写入出错时输出错误信息
        return -1;
    }

    close(fd);  // 关闭文件
    return 0;
}

int main(int argc, char *argv[])
{
    struct pollfd pfd;  // 定义pollfd结构体
    char file_path[100];  // 用于存储文件路径
    int ret;  // poll函数的返回值
    char val;  // 用于存储读取的值

    // 校验传参个数,必须是2个
    if (2 != argc) {
        fprintf(stderr, "usage: %s <gpio>\n", argv[0]);  // 提示正确用法
        exit(-1);
    }

    // 生成GPIO路径
    sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);

    // 检查GPIO目录是否存在
    if (access(gpio_path, F_OK)) {  // 如果目录不存在,则需要导出

        int len;  // 字符串长度
        int fd;  // 文件描述符

        // 打开导出文件
        if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
            perror("open error");  // 打开文件出错时输出错误信息
            exit(-1);
        }

        len = strlen(argv[1]);  // 获取GPIO编号字符串的长度

        // 写入GPIO编号到导出文件
        if (len != write(fd, argv[1], len)) {  // 导出GPIO
            perror("write error");  // 写入出错时输出错误信息
            exit(-1);
        }

        close(fd);  // 关闭文件
    }

    // 配置GPIO为输入模式
    if (gpio_config("direction", "in"))
        exit(-1);

    // 配置GPIO极性为默认(不反转)
    if (gpio_config("active_low", "0"))
        exit(-1);

    // 配置中断触发方式: 上升沿和下降沿
    if (gpio_config("edge", "both"))
        exit(-1);

    // 打开value属性文件
    sprintf(file_path, "%s/%s", gpio_path, "value");

    if (0 > (pfd.fd = open(file_path, O_RDONLY))) {
        perror("open error");  // 打开文件出错时输出错误信息
        exit(-1);
    }

    // 调用poll
    pfd.events = POLLPRI;  // 只关心高优先级数据可读(中断)

    read(pfd.fd, &val, 1);  // 先读取一次清除状态
    for ( ; ; ) {

        ret = poll(&pfd, 1, -1);  // 调用poll
        if (0 > ret) {
            perror("poll error");  // poll函数出错时输出错误信息
            exit(-1);
        }
        else if (0 == ret) {
            fprintf(stderr, "poll timeout.\n");  // 超时处理
            continue;
        }

        // 校验高优先级数据是否可读
        if (pfd.revents & POLLPRI) {
            if (0 > lseek(pfd.fd, 0, SEEK_SET)) {  // 将读位置移动到头部
                perror("lseek error");  // lseek函数出错时输出错误信息
                exit(-1);
            }

            if (0 > read(pfd.fd, &val, 1)) {  // 读取GPIO值
                perror("read error");  // read函数出错时输出错误信息
                exit(-1);
            }

            // 输出GPIO中断触发时的值
            printf("GPIO中断触发<value=%c>\n", val);
        }
    }

    // 退出程序
    exit(0);
}

1.其中用到了poll函数中的pollfd结构体,其形式为:

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

fd 是一个文件描述符, struct pollfd 结构体中的 events 和 revents 都是位掩码,调用者初始化 events 来指定需要为文件描述符 fd 做检查的事件。当 poll()函数返回时, revents 变量由 poll()函数内部进行设置,用于说明文件描述符 fd 发生了哪些事件(注意, poll()没有更改 events 变量) ,我们可以对 revents 进行检查,判断文件描述符 fd 发生了什么事件。

2. (pfd.fd = open(file_path, O_RDONLY),将文件路径指向了pfd.fd,根据表可得:

  • pfd.events:指定我们感兴趣的事件类型。
  • POLLPRI:表示高优先级数据可读事件(通常是中断事件)。

其中read(pfd.fd, &val, 1)是从文件描述符 pfd.fd(即GPIO的 value 文件)中读取一个字节的数据到 val 中,这一步的目的是先读取一次GPIO的值,以清除任何可能的旧的中断状态。

3.

ret = poll(&pfd, 1, -1);  // 调用poll
  • poll 函数用于监视文件描述符上的事件。
  • &pfd:指向 pollfd 结构体数组的指针。
  • 1:数组的长度,即只有一个文件描述符需要监视。
  • -1:表示无限期等待,直到有事件发生。
  • retpoll 函数的返回值。成功时返回正值,表示有事件发生;返回0表示超时;返回负值表示出错。

其功能就是不断监视文件符上的事件,直到有事件发生,成功返回正值

4.

if (pfd.revents & POLLPRI) 
  • pfd.revents:实际发生的事件类型。
  • POLLPRI:高优先级数据可读事件(通常是中断事件)。
  • if (pfd.revents & POLLPRI):检查 pfd.revents 中是否包含 POLLPRI 事件,如果有,执行后续代码。

如果POLLPRI没有数据,就说明没有可读事件,那么其就为0,就不会执行语句。

5.lseek 是一个用于移动文件指针位置的系统调用(非常常见)。

off_t lseek(int fd, off_t offset, int whence);

其中

  • d:文件描述符,指定要操作的文件。
  • offset:偏移量,根据 whence 的不同含义不同。
  • whence:基准位置,用于指定 offset 的基准。常用的值有:
    • SEEK_SET:文件开头。
    • SEEK_CUR:文件当前指针位置。
    • SEEK_END:文件末尾。

说白了就是移动写入指针的,来指定你从什么地方写入。

6.如果触发,则执行以下语句:

if (0 > lseek(pfd.fd, 0, SEEK_SET)) {  // 将读位置移动到头部
                perror("lseek error");  // lseek函数出错时输出错误信息
                exit(-1);
            }

            if (0 > read(pfd.fd, &val, 1)) {  // 读取GPIO值
                perror("read error");  // read函数出错时输出错误信息
                exit(-1);
            }

            // 输出GPIO中断触发时的值
            printf("GPIO中断触发<value=%c>\n", val);
        }

   其中lseek(pfd.fd, 0, SEEK_SET) 的目的是将文件指针移动到文件的开头,以便重新读取文件内容。偏移量为0,就是开头开始读。

      read(pfd.fd, &val, 1)。然后读取文件描述符的值,&val:存储读取数据的缓冲区,就是将读取的数据存入。

因为poll函数,所以有内容最后会使用 printf 函数输出GPIO中断触发时的值。

测试:

后连接开发板:

scp命令传输

 scp -r book@192.168.5.12:/home/book/project/APP/app/16_gpio/gpio_intr /home/root/app

执行命令:因为程序问题,所以这就直接测试GPIO1_IO1口了,所以后面直接输入1就行,如下。

当执行命令之后,我们可以使用杜邦线将 GPIO1_IO01 引脚连接到 GND 或 3.3V 电源引脚上,来回切换,使得 GPIO1_IO01 引脚的电平状态发生由高到低或由低到高的状态变化。测试成功

注意,利用CTRL+C退出调试。


 

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值