2. GPIO 硬件编程
相比于 Linux 2.4, 2.6 及以上的内核可以使用系统中的 GPIOLIB 模块在用户空间提供
的 sysfs 接口,实现应用层对 GPIO 的独立控制。
2.1 GPIO 和 sysfs 操作接口
Linux开发平台实现了通用 GPIO的驱动,用户通过 Shell命令或系统调用即能控制 GPIO
的输出和读取其输入值。其属性文件均在/sys/class/gpio 目录下,如:
root@EasyARM-iMX283 /# ls /sys/class/gpio/
export gpiochip128 gpiochip64 unexport
gpiochip0 gpiochip32 gpiochip96
属性文件有 export 和 unexport。其余五个文件为符号链接( gpiochip0, gpiochip32,
gpiochip64, gpiochip96, gpiochip128),指向管理对应设备的目录,以 gpiochip0 为例,
此目录下文件有:
root@EasyARM-iMX283 /# ls /sys/class/gpio/gpiochip0
base label ngpio power subsystem uevent
以上文件用途如表 2.1 所示。
表 2.1 gpio 目录下默认属性文件用途
文件名 路径 作用
---------------------------------------------------------------
export /sys/class/gpio/export 导出 GPIO
unexport /sys/class/gpio/unexport 将导出的 GPIO 从 sysfs 中清除
gpiochipN
/sys/class/gpio/gpiochipN/base 设备所管理的 GPIO 初始编号
/sys/class/gpio/gpiochipN/label 设备信息
/sys/class/gpio/gpiochipN/ngpio 设备所管理的 GPIO 总数
/sys/class/gpio/gpiochipN/power 设备供电方面的相关信息
/sys/class/gpio/gpiochipN/subsystem 符号链接,指向父目录
/sys/class/gpio/gpiochipN/uevent 内核与 udev(自动设备发现程序)之间的通信接口
向 export 文件写入需要操作的 GPIO 排列序号 N,就可以导出对应的 GPIO 设备目录。
操作命令如下:
# echo N > /sys/class/gpio/export
例如,导出序号为 68 的 GPIO 的操作接口,在 Shell 下,可以用如下命令:
# echo 68 > /sys/class/gpio/export
通过以上操作后在/sys/class/gpio 目录下生成 gpioN 目录, 通过读写该目录下的属性文
件就可以操作这个 GPIO 的输入和输出。以此类推可以导出其它 GPIO 设备目录。如果 GPIO
已经被系统占用,导出时候会提示资源占用。
以排列序号为 68 的 GPIO 为例, 设备目录下有如下属性文件:
#ls /sys/class/gpio/gpio68/
active_low edge subsystem value direction power uevent
各个文件用途如表 2.2 所示。
表 2.2 GPIO 属性文件用途
文件名 路径 作用
-----------------------------------------------------------------------------------------------
active_low /sys/class/gpio/gpioN/active_low 具有读写属性。用于决定 value 中的值是否翻转。
0 不翻 转, 1 翻转。
edge /sys/class/gpio/gpioN/edge 具有读写属性。设置 GPIO 中断,或检测中断是否发生。
subsystem /sys/class/gpio/gpioN/subsystem 符号链接,指向父目录。
value /sys/class/gpio/gpioN/value 具有读写属性。 GPIO 的电平状态设置或读取。
direction /sys/class/gpio/gpioN/direction 具有读写属性。用于查看或设置 GPIO 输入输出
power /sys/class/gpio/gpioN/power 设备供电方面的相关信息
uevent /sys/class/gpio/gpioN/uevent 内核与 udev(自动设备发现程序)之间的通信接口
2.2 GPIO 基本操作
在应用层我们可以通过 Shell 命令操作 GPIO。通过以下步骤,就可以控制 GPIO 输入输
出。 下面步骤是以 GPIO 的输入输出功能进行介绍。
1. 输入输出设置
GPIO 导出后默认为输入功能。向 direction 文件写入“ in”字符串,表示设置为输入功能;
向 direction 文件写入“ out”字符串,表示设置为输出功能。读 direction 文件,会返回
in/out 字符串, in 表示当前 GPIO 作为输入, out 表示当前 GPIO 作为输出。
方向查看和设置命令如下:
# cat /sys/class/gpio/gpioN/direction #查看方向
# echo out > /sys/class/gpio/gpioN/direction #设置为输出
# echo in > /sys/class/gpio/gpioN/direction #设置为输入
例如, 查看排列序号为 68 的 GPIO 的方向, 在 Shell 下, 可以用如下命令:
# cat /sys/class/gpio/gpio68/direction
2. 输入读取
当 GPIO 被设为输入时, value 文件记录 GPIO 引脚的输入电平状态:
1 表示输入的是高电平;
0 表示输入的是低电平。
通过查看 value 文件可以读取 GPIO 的电平,查看命令如下:
# echo in > /sys/class/gpio/gpioN/direction #设置 GPIO 排列序号为 N 的 GPIO 方向为输入
# cat /sys/class/gpio/gpioN/value #查看 GPIO 排列序号为 N 的 GPIO 电平
例如, 查看排列序号为 68 的 GPIO 的电平状态, 在 Shell 下, 可以用如下命令:
# echo in > /sys/class/gpio/gpio68/direction
# cat /sys/class/gpio/gpio68/value
3. 输出控制
当 GPIO 被设为输出时,通过向 value 文件写入 0 或 1( 0 表示输出低电平; 1 表示输出
高电平)可以设置输出电平的状态,输出命名如下:
# echo out > /sys/class/gpio/gpioN/direction #设置 GPIO 排列序号为 N 的 GPIO 方向为输出
# echo 0 > /sys/class/gpio/gpioN/value #输出低电平
# echo 1 > /sys/class/gpio/gpioN/value #输出高电平
例如, 设置排列序号为 68 的 GPIO 的电平为高电平, 在 Shell 下, 可以用如下命令:
# echo out > /sys/class/gpio/gpio68/direction
# echo 0 > /sys/class/gpio/gpio68/value
2.3 在 C 程序中操作 GPIO
使用系统调用实现 GPIO 输入输出操作时, 首先需要使用 export 属性文件导出 GPIO,
#define EXPORT_PATH "/sys/class/gpio/export" //GPIO 设备导出设备
#define GPIO "68" //GPIO2_4
int fd_export = open(EXPORT_PATH, O_RDWR); //打开 GPIO 设备导出设备
...
write(fd_export, GPIO, strlen(GPIO)); //向 export 文件写入 GPIO 排列序号字符串
可以调用 write 函数向 direction 设备写入方向 in/out 字符串,将 GPIO 设置为输入(输出), 如:
#define DIRECT_PATH "/sys/class/gpio/gpio68/direction" //GPIO 输入输出控制设备
int fd_dir, ret ;
fd_dir = open(DIRECT_PATH, O_RDWR); //打开 GPIO 输入输出控制设备
...
ret = write(fd_dir, direction, sizeof(direction)); //写入 GPIO 输入( in)输出( out)方向
GPIO 设置为输入时使用 read 系统调用读取 value 属性文件,就可以读取 GPIO 电平值。
GPIO 设置为输出时, 使用 write 系统调用向 value 属性文件写入 0 或 1 字符串,就可以设置
GPIO 电平值。如:
#define DEV_PATH "/sys/class/gpio/gpio68/value" //输入输出电平值设备
int fd_dev, ret ;
fd_dev = open(DEV_PATH, O_RDWR); //打开输入输出电平值设备
...
ret = read(fd_dev, buf, sizeof(buf)); //读取 GPIO 输入电平值
2.4 EasyARM-i.MX283A GPIO 应用编程
1. GPIO 资源汇总
下面介绍在 EasyARM-i.MX283A 开发板上操作 GPIO 的示例。 EasyARM-i.MX283A 底
板 GPIO 通过扩展接口 1/2 引出。
EasyARM-i.MX283A 没有直接给出 GPIO 排列序号,需要通过计算得出。 其序号计算
公式如下:
GPIO排列序号 = BANK × 32 + N
BANK 为 GPIO 引脚所在的 BANK, N 为引脚所在的 BANK 的序号。比如管脚:P2.4为例,
BANK 值为 2,N 为 4。其排列序号为 2*32+4=68。
2. Shell 命令操作范例
按照通用 GPIO 操作方法,我们以 EasyARM-i.MX283A 排列序号为 68 的 GPIO 为例进
行 GPIO 输出操作,步骤如下:
导出对应的 GPIO 设备目录:
root@EasyARM-iMX283x # cd /sys/class/gpio
root@EasyARM-iMX283x /sys/class/gpio# echo 68 >export
输出方向设置:
root@EasyARM-iMX283x /sys/devices/virtual/gpio/gpio68# echo out >direction
通过向 value 文件写入 0 或 1( 0 表示输出低电平; 1 表示输出高电平)可以设置输出电
平的状态:
root@EasyARM-iMX283x /sys/devices/virtual/gpio/gpio68# echo 0 >value
root@EasyARM-iMX283x /sys/devices/virtual/gpio/gpio68# echo 1 >value
3. C 代码操作范例
范例代码以 GPIO2_4 为例,实现 GPIO 的输入读取。首先通过 open()和 write()系统调用
导出 GPIO,然后设置 GPIO 为输入,读取 GPIO 的输入值。操作范例如所程序清单 2.1所示。
程序清单 2.1 C 语言操作 GPIO 输入示例
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <fcntl.h>
-
#include <termios.h>
-
#include <errno.h>
-
#include <string.h>
-
-
#define DEV_PATH "/sys/class/gpio/gpio68/value" //输入输出电平值设备
-
#define EXPORT_PATH "/sys/class/gpio/export" //GPIO 设备导出设备
-
#define DIRECT_PATH "/sys/class/gpio/gpio68/direction" //GPIO 输入输出控制设备
-
#define OUT "out"
-
#define IN "in"
-
#define GPIO "68" // GPIO2_4
-
#define HIGH_LEVEL "1"
-
#define LOW_LEVEL "0"
-
-
int main(int argc, char ** argv)
-
{
-
static int fd_dev, fd_export, fd_dir, ret;
-
char buf[10], direction[4];
-
fd_export = open(EXPORT_PATH, O_WRONLY); //打开 GPIO 设备导出设备
-
if(fd_export < 0) {
-
perror("open export:");
-
return -1;
-
}
-
// 相当于输入命令: echo 68 > /sys/class/gpio/export
-
// 导出GPIO68
-
write(fd_export, GPIO, strlen(GPIO));
-
-
fd_dev = open(DEV_PATH, O_RDWR); //打开输入输出电平值设备
-
if(fd_dev < 0) {
-
perror("export write:");
-
return -1;
-
}
-
-
fd_dir = open(DIRECT_PATH, O_RDWR); //打开 GPIO 输入输出控制设备
-
if(fd_dir < 0) {
-
perror("export write:");
-
return -1;
-
}
-
-
// 读取默认的gpio输入输出方向
-
ret = read(fd_dir, direction, sizeof(direction)); //读取 GPIO2_4 输入输出方向
-
if(ret < 0) {
-
perror("dir read:");
-
close(fd_export);
-
close(fd_dir);
-
close(fd_dev);
-
return -1;
-
}
-
printf("default directions: %s", direction);
-
-
// 设置管脚的方向:输入引脚
-
strcpy(buf, IN);
-
ret = write(fd_dir, buf, strlen(IN));
-
if(ret < 0) {
-
perror("dir read:");
-
close(fd_export);
-
close(fd_dir);
-
close(fd_dev);
-
return -1;
-
}
-
// 读取管脚输入输出方向,看是否设置成功
-
ret = read(fd_dir, direction, sizeof(direction));
-
if(ret < 0) {
-
perror("dir read:");
-
close(fd_export);
-
close(fd_dir);
-
close(fd_dev);
-
return -1;
-
}
-
-
// 读取管脚的电平值
-
ret = read(fd_dev, buf, sizeof(buf)); //读取 GPIO2_4 输入电平值
-
if(ret < 0) {
-
perror("dir read:");
-
close(fd_export);
-
close(fd_dir);
-
close(fd_dev);
-
return -1;
-
}
-
printf("now directions:%sinput level:%s", direction, buf);
-
close(fd_export);
-
close(fd_dir);
-
close(fd_dev);
-
return 0;
-
}
编译目标代码:
root@ubuntu:~# arm-fsl-linux-gnueabi-gcc test.c -o gpio_test
将编译好的代码下载到开发板,运行代码前,先使用杜邦线将开发板 GND 或 Vcc 接入
GPIO2_4,然后运行代码,代码会在串口打印当前输入电平值。
测试:
(1) GPIO2_4接上高电平:
root@EasyARM-iMX283 /mnt/app_for_hardware/2nd_gpio# ./gpio
default directions: in
now directions:in
input level:1
(2) GPIO2_4接上低电平:
root@EasyARM-iMX283 /mnt/app_for_hardware/2nd_gpio# ./gpio
default directions: in
now directions:in
input level:0