GPIO 设备
GPIO(General Purpose Input/Output),即通用输入/输出端口,以下简称 I/O 端口。一个 I/O 端口可提供输入、输出或中断三类功能,SylixOS 中的 GPIO 设备管理整个硬件系统上所有可用的 GPIO 端口,使应用程序能够通过标准接口使用 GPIO 的三类功能。GPIO 设备的相关定义位于 <sys/gpiofd.h> 文件,相关 API 描述如下。
#include <sys/gpiofd.h>
int gpiofd(unsigned int gpio, int flags, int gpio_flags);
int gpiofd_read(int fd, uint8_t *value);
int gpiofd_write(int fd, uint8_t value);
函数 gpiofd 原型分析:
- 此函数成功时返回对应 GPIO 端口的文件描述符,失败时返回负数。
- 参数 gpio 为 GPIO 端口的唯一编号,该编号与具体的系统硬件相关,应用程序应该参考 BSP 包对 GPIO 端口编号的定义来正确选择。
- 参数 flags 与 open 函数的第二个参数意义相似,即可以是 O_RDONLY、O_RDWR 等。
- 参数 gpio_flags 是与 GPIO 特性相关的标识,它可以是多个位标识的组合。参考下表GPIO 功能标识。
位标识名称 | 解释 |
---|---|
GPIO_FLAG_DIR_OUT | 设置GPIO为输出功能 |
GPIO_FLAG_DIR_IN | 设置GPIO为输入功能 |
GPIO_FLAG_IN | 与GPIO_FLAG_DIR_IN相同 |
GPIO_FLAG_OUT_INIT_LOW | 设置GPIO为输出功能,同时初始化输出低电平 |
GPIO_FLAG_OUT_INIT_HIGH | 设置GPIO为输出功能,同时初始化输出高电平 |
GPIO_FLAG_OPEN_DRAIN | 设置GPIO输出为漏极输出模式 |
GPIO_FLAG_OPEN_SOURCE | 设置GPIO输出为源极输出模式 |
GPIO_FLAG_PULL_DEFAULT | 使用默认上拉/下拉模式 |
GPIO_FLAG_PULL_UP | 使用上拉电阻模式 |
GPIO_FLAG_PULL_DOWN | 使用下拉电阻模式 |
GPIO_FLAG_PULL_DISABLE | 禁止上拉/下拉模式 |
GPIO_FLAG_TRIG_FALL | 设置GPIO为中断功能,并且下降沿触发中断 |
GPIO_FLAG_TRIG_RISE | 设置GPIO为中断功能,并且上升沿触发中断 |
GPIO_FLAG_TRIG_LEVEL | 设置GPIO为中断功能,并且电平触发中断 |
注意:
当使用了 GPIO_FLAG_TRIG_LEVE 标识时,我们仅能用 GPIO_FLAG_TRIG_FALL 与 GPIO_FLAG_TRIG_RISE 中的一个与其组合使用,分别表示低电平触发和高电平触发。当没有使用 GPIO_FLAG_TRIG_LEVE 时,我们可以将 GPIO_FLAG_TRIG_FALL 和 GPIO_FLAG_TRIG_RISE 组合使用表示双边沿触发。
函数 gpiofd_read 读取一个 GPIO 端口的电平状态,只有 0 和 1 两个值,原型分析如下:
- 此函数成功返回 0,失败返回错误码。
- 参数 fd 为 GPIO 端口对应的文件描述符。
- 输出参数 value 保存读取到的电平值,0 表示低电平,1 表示高电平。
函数 gpiofd_write 设置一个 GPIO 端口的电平状态,只有 0 和 1 两个值,原型分析如下:
- 此函数成功返回 0,失败返回错误码。
- 参数 fd 为 GPIO 端口对应的文件描述符。
- 参数 value 为需要设置的电平值,0 表示低电平,1 表示高电平。
上面的函数仅能处理 GPIO 的输入输出,但无法使用其中断功能。因为在驱动程序中使用 GPIO 的中断功能的方式是调用系统内核 API 注册相应的中断服务程序,但应用程序不能调用这些函数。
我们知道,I/O 多路复用(select)允许任务阻塞地等待一个或多个文件描述符满足指定状态(可读、可写或异常)。SylixOS 利用这一点,为应用程序提供了使用 GPIO 中断功能的方法。当一个具有中断功能的 GPIO 产生中断时,内核会唤醒所有通过调用 select 等待该 GPIO 文件描述符可读状态的线程,通知线程中断产生。当 select 正确返回时,线程处理相应的事务,这类似于完成了一次中断服务。
以下程序展示了 GPIO 设备的一般使用方法。
#include <stdio.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/gpiofd.h>
#define LED_GPIO_NUM 52
#define KEY_GPIO_NUM 32
int main (int argc, char *argv[])
{
fd_set fdset;
int led_fd;
int key_fd;
int ret;
uint8_t value = 1;
led_fd = gpiofd(LED_GPIO_NUM, O_WRONLY, GPIO_FLAG_OUT_INIT_LOW);
if (led_fd < 0) {
fprintf(stderr, "open led gpiofd failed.\n");
return (-1);
}
key_fd = gpiofd(KEY_GPIO_NUM, O_RDONLY, GPIO_FLAG_TRIG_FALL);
if (key_fd < 0) {
fprintf(stderr, "open key gpiofd failed.\n");
return (-1);
}
while (1) {
FD_ZERO(&fdset);
FD_SET(key_fd, &fdset);
/*
* 等待按键中断产生。
*/
ret = select(key_fd + 1, &fdset, NULL, NULL, NULL);
if (ret != 1) {
fprintf(stderr, "select error.\n");
break;
}
gpiofd_write(led_fd, value);
value = !value;
}
close(led_fd);
close(key_fd);
return (0);
}
上面的程序中,我们假设控制 LED 的端口编号为 52,检测按键中断的端口编号为 32,并假设当按键按下时会产生一个下降沿中断。我们将 fdset 设置为 select 函数的读等待文件描述符集,即等待按键 GPIO 文件可读,实际上就是等待按键中断产生。上面程序的效果是,每当按下一次按键,LED 灯的状态就会改变一次(从点亮到熄灭或从熄灭到点亮)。