GPIO 应用编程
应用层如何操控 GPIO
GPIO 控制器管理
I.MX6UL/I.MX6ULL 包含 5 个 GPIO 控制器GPIO1、GPIO2、GPIO3、GPIO4、GPIO5,分别对应 gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128
每个 gpiochipX 文件夹管理一组 GPIO 引脚,包含 base、label、ngpio 属性文件
base:表示该控制器管理的GPIO引脚中最小的编号。这个基数值与gpiochipX中的X是相同的,通过这个编号系统可以索引到具体的GPIO引脚
label:提供该组GPIO的标签或名称,方便用户识别不同的GPIO组
ngpio:显示该控制器所管理的GPIO引脚的总数。引脚的编号范围从base开始,到base+ngpio-1结束
GPIO 引脚编号计算
- 例如,GPIO4_IO16 对应编号为 96 + 16 = 112
GPIO 引脚导出与删除
使用 export 文件导出 GPIO 引脚在/sys/class/gpio 目录下生成 gpioX 文件夹
export 文件是只写文件
已被内核使用的引脚无法导出
echo 0 > export # 导 出 编 号 为 0 的 GPIO 引 脚
使用 unexport 文件删除导出的 GPIO 引脚
unexport 文件是只写文件
echo 0 > unexport # 删除导出的编号为 0 的 GPIO 引脚
GPIO 引脚控制(文件可读可写)
在 gpioX 文件夹中,通过 direction 文件设置输入/输出模式
该文件可读、可写
“out”(输出模式)
“in”(输入模式)
通过 value 文件控制输出电平或读取输入电平
输出模式下
"0"控制 GPIO 引脚输出低电平
"1"控制 GPIO 引脚输出高电平
输入模式下
- 读取 value 文件获取 GPIO 引脚当前的输入电平状态
active_low 文件控制极性
echo “0” > active_low
echo “out” > direction
echo “1” > value #输出高
echo “0” > value #输出低
active_low 等于 1 时
$ echo “1” > active_low
$ echo “out” > direction
$ echo “1” > value #输出低
$ echo “0” > value #输出高
edge 文件设置中断触发模式
该文件可读可写
非中断引脚:echo “none” > edge
上升沿触发:echo “rising” > edge
下降沿触发:echo “falling” > edge
边沿触发:echo “both” > edge当引脚被配置为中断后可以使用 poll()函数监听引脚的电平状态变化
GPIO 应用编程之输出
开始:程序的入口点
参数检查是否为三个
是则构造GPIO路径
通过路径检查GPIO是否已导出
否则打开"/sys/class/gpio/export"
写入GPIO编号,导出GPIO
关闭文件
否则输出用法信息,程序退出
配置GPIO为输出模式
配置为输出模式
配置极性
控制GPIO输出高低电平
正常退出:程序正常退出,返回状态 0
#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[])
{
/* 校验传参 */
if (3 != argc) {
fprintf(stderr, "usage: %s <gpio> <value>\n", argv[0]);
exit(-1);
}
/* 判断指定编号的GPIO是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
//如果路径存在,access 返回 0
if (access(gpio_path, F_OK)) {//如果目录不存在 则需要导出
int fd;
int len;
if (0 > (fd = open("/sys/class/gpio/export", O_WRONLY))) {
//perror调用此函数不需要传入 errno,会直接将错误提示字符串打印出来,
//除此之外还可以在输出的错误提示字符串之前加入自己的打印信息
perror("open error");
exit(-1);
}
len = strlen(argv[1]);
//如果写入长度与值长度不符
if (len != write(fd, argv[1], len)) {//导出gpio
perror("write error");
close(fd);
exit(-1);
}
close(fd); //关闭文件
}
/* 配置为输出模式 */
if (gpio_config("direction", "out"))
exit(-1);
/* 极性设置 */
if (gpio_config("active_low", "0"))
exit(-1);
/* 控制GPIO输出高低电平 */
if (gpio_config("value", argv[2]))
exit(-1);
/* 退出程序 */
exit(0);
}
GPIO 应用编程之输入
开始:程序的入口点
参数检查是否为两个
是则构造GPIO路径
通过路径检查GPIO是否已导出
否则打开"/sys/class/gpio/export"
写入GPIO编号,导出GPIO
关闭文件
否则输出用法信息,程序退出
配置GPIO为输出模式
配置为输入模式
配置极性
配置为非中断方式
读取GPIO电平状态
拼接value文件的路径
尝试打开value文件
- 如果打开失败,打印错误信息并退出程序
尝试读取文件内容(GPIO的当前值)
- 如果读取失败,打印错误信息并退出程序
打印读取到的GPIO值
关闭打开的文件
正常退出:程序正常退出,返回状态 0
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
static char gpio_path[100];
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;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
exit(-1);
}
/* 判断指定编号的GPIO是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
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]);
if (len != write(fd, argv[1], len)) {//导出gpio
perror("write error");
close(fd);
exit(-1);
}
close(fd); //关闭文件
}
/* 配置为输入模式 */
if (gpio_config("direction", "in"))
exit(-1);
/* 极性设置 */
if (gpio_config("active_low", "0"))
exit(-1);
/* 配置为非中断方式 */
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);
}
GPIO 应用编程之中断
开始:程序的入口点
参数检查是否为两个
是则构造GPIO路径
通过路径检查GPIO是否已导出
否则打开"/sys/class/gpio/export"
写入GPIO编号,导出GPIO
关闭文件
否则输出用法信息,程序退出
配置GPIO为输出模式
配置为输入模式
配置极性
配置中断触发方式: 上升沿和下降沿
打开value属性文件
- 如果打开失败,显示错误信息并退出程序
初始化poll结构体
- 设置关心的事件为POLLPRI
首次读取状态以清除
循环等待GPIO中断
调用poll等待事件
如果poll出错,显示错误信息并退出程序
如果poll超时,继续循环
如果检测到中断(POLLPRI事件)
将读位置移动到头部
- 如果移动失败,显示错误信息并退出程序
读取新的GPIO值
- 如果读取失败,显示错误信息并退出程序
打印GPIO中断信息
结束:程序正常执行不会到达此步骤,因为它处于无限循环中等待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>
static char gpio_path[100];
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;
char file_path[100];
int ret;
char val;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <gpio>\n", argv[0]);
exit(-1);
}
/* 判断指定编号的GPIO是否导出 */
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
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]);
if (len != write(fd, argv[1], len)) {//导出gpio
perror("write error");
exit(-1);
}
close(fd); //关闭文件
}
/* 配置为输入模式 */
if (gpio_config("direction", "in"))
exit(-1);
/* 极性设置 */
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 */
//POLLPRI标志表示有高优先级事件发生,通常与中断相关
pfd.events = POLLPRI; //只关心高优先级数据可读(中断)
read(pfd.fd, &val, 1);//先读取一次清除状态
for ( ; ; ) {
ret = poll(&pfd, 1, -1); //调用poll
//如果返回值ret小于0,表示poll()调用失败
if (0 > ret) {
perror("poll error");
exit(-1);
}
//如果返回值ret等于0,表示poll()调用在指定的超时时间内没有检测到任何事件
else if (0 == ret) {
fprintf(stderr, "poll timeout.\n");
//continue语句跳过当前循环的剩余部分,直接开始下一次循环迭代
continue;
}
/* 校验高优先级数据是否可读 */
if(pfd.revents & POLLPRI) {//检查revents字段中是否设置了POLLPRI位
//lseek控制文件读写位置
//文件的开始(SEEK_SET)
if (0 > lseek(pfd.fd, 0, SEEK_SET)) {//将读位置移动到头部
perror("lseek error");
exit(-1);
}
if (0 > read(pfd.fd, &val, 1)) {
perror("read error");
exit(-1);
}
printf("GPIO中断触发<value=%c>\n", val);
}
}
/* 退出程序 */
exit(0);
}
在开发板上测试
GPIO 输出测试
./testApp 1 1 #控制 GPIO1_IO01 输出高电平
./testApp 1 0
#控制 GPIO1_IO01 输出低电平
GPIO 输入测试
- ./testApp 1
#读取 GPIO1_IO01 引脚此时的电平状态
GPIO 中断测试
- ./testApp 1
# 监测 GPIO1_IO01 引脚中断触发