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
:表示无限期等待,直到有事件发生。ret
:poll
函数的返回值。成功时返回正值,表示有事件发生;返回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退出调试。