RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP,内置NPU,支持INT4/INT8/INT16/FP16混合运算能力,支持安卓12和、Debian11、Build root、Ubuntu20和22版本登系统。了解更多信息可点击迅为官网
【粉丝群】824412014
【实验平台】:迅为RK3588开发板
【内容来源】《iTOP-3588开发板系统编程手册》
【全套资料及网盘获取方式】联系淘宝客服加入售后技术支持群内下载
【视频介绍】:【强者之芯】 新一代AIOT高端应用芯片 iTOP -3588人工智能工业AI主板
第14章 GPIO应用编程
由于本章节要使用GPIO的方式来控制LED,所以需要在设备树中注释掉LED节点的相关内容,为了方便起见,已经将修改好的内核设备树放到了“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\62”目录下,如下图所示:
将提供的boot.img文件根据07_【北京迅为】itop-3588开发板快速烧写手册.pdf手册中的“单独烧写 Linux 固件”小节进行单独烧写,烧写完成之后就可以进行本章节的学习了。
14.1应用层如何操控GPIO
与 LED 设备一样,GPIO 同样也是通过 sysfs 方式进行操控的,首先使用以下命令进入到/sys/class/gpio 目录下,如下所示:
可以看到在当前目录下有两个文件 export、unexport 以及 5个 gpiochipX(X 等于 0、32、64、96、128)命名的文件夹。接下来将分别对 gpiochipX、export和unexport进行讲解:
gpiochipX:当前 SoC 所包含的 GPIO 控制器,iTOP-RK3588一共包含了 5 个 GPIO控制器,分别为 RK_GPIO0、RK_GPIO1、RK_GPIO2、RK_GPIO3、RK_GPIO4,在这里分别对应 gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128 这 5 个文件夹,每一个 gpiochipX 文件夹用来管理一组GPIO。
以gpiochip0文件夹为例,对gpiochipX目录中的内容进行讲解,进入gpiochip0目录下,gpiochip0目录内容如下图所示:
分别为base、device、label、ngpio、power、subsystem、uevent,需要了解的是 base、label、ngpio 这三个属性文件,这三个属性文件均是只读、不可写。
base:与 gpiochipX 中的 X 相同,表示该控制器所管理的这组 GPIO 引脚中最小的编号。每一个 GPIO 引脚都会有一个对应的编号,Linux 下通过这个编号来操控对应的 GPIO 引脚。
使用cat命令对base文件信息进行查看如下图所示:
label:对应该组 GPIO 标签,使用cat命令对label文件进行查看,如下图所示:
ngpio:该控制器所管理的 GPIO 引脚的数量(所以引脚编号范围是:base ~ base+ngpio-1),使用cat命令对ngpio文件进行查看,如下图所示:
iTOP-RK3588有 5 组 GPIO bank:GPIO0~GPIO4,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分,常用以下公式计算引脚:
GPIO pin脚计算公式:pin = bank * 32 + number //bank为组号,number为小组编号
GPIO 小组编号计算公式:number = group * 8 + X
iTOP-RK3588有 5 组 GPIO bank:GPIO0~GPIO4,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分,常用以下公式计算引脚:
GPIO pin脚计算公式:pin = bank * 32 + number //bank为组号,number为小组编号
GPIO 小组编号计算公式:number = group * 8 + X
下面演示gpio座子5号引脚GPIO2_PC4 pin脚计算方法:
bank = 2; //GPIO0_B7=> 2, bank ∈ [0,4]
group = 2; //GPIO0_B7 => 2, group ∈ {(A=0), (B=1), (C=2), (D=3)}
X = 4; //GPIO4_D7 => 4, X ∈ [0,7]
number = group * 8 + X = 2 * 8 + 4 =20
pin = bank*32 + number= 2 * 32 + 20 = 84;
export:用于将指定编号的 GPIO 引脚导出。在使用 GPIO 引脚之前,需要将其导出,导出成功之后才能使用它。注意 export 文件是只写文件,不能读取,将一个指定的编号写入到 export 文件中即可将对应的 GPIO 引脚导出,以GPIO2_PC4为例(pin计算值为84)使用export 文件进行导出(如果没有更换本章开始部分的内核设备树镜像,会导出不成功),导出成功如下图所示:
echo 84 > export
会发现在/sys/class/gpio 目录下生成了一个名为 gpio84 的文件夹(gpioX,X 表示对应的编号),该文件夹就是导出来的 GPIO 引脚对应的文件夹,用于管理、控制该 GPIO 引脚。
unexport:将导出的 GPIO 引脚删除。当使用完 GPIO 引脚之后,需要将导出的引脚删除,同样该文件也是只写文件、不可读,使用unexport 文件进行删除GPIO2_PC4,删除成功如下图所示:
echo 84 > unexport
可以看到之前生成的 gpio84 文件夹就会消失!
需要注意的是,并不是所有 GPIO 引脚都可以成功导出,如果对应的 GPIO 已经被导出或者在内核中被使用了,那便无法成功导出,导出失败如下图所示:
再次使用以下命令导出GPIO2_PC3引脚,导出成功之后进入gpio84文件夹如下图所示:
echo 84 > export
可以看到gpio84文件夹下分别有active_low、device、direction、edge、power、subsystem、uevent、value八个文件,需要关心的文件是 active_low、direction、edge 以及 value 这四个属性文件,接下来分别介绍这四个属性文件的作用:
direction:配置 GPIO 引脚为输入或输出模式。该文件可读、可写,读表示查看 GPIO 当前是输入还是输出模式,写表示将 GPIO 配置为输入或输出模式;读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式)。
在“/sys/class/gpio/gpio84”目录下使用cat命令查看direction输入输出模式,如下图所示:
cat direction
默认状态下的输入输出状态为“in”,由于direction为可读可写,可以使用以下命令将模式配置为输出,配置完成如下图所示
echo out > direction
cat direction
active_low:用于控制极性得属性文件,可读可写,默认情况下为 0,使用cat命令进行文件内容的查看,如下图所示 :
cat active_low
当 active_low 等于 0 时, value 值若为1则引脚输出高电平,value 值若为0则引脚输出低电平。当 active_low 等于 1 时 ,value 值若为0则引脚输出高电平,value 值若为1则引脚输出低电平。
edge:控制中断的触发模式,该文件可读可写。在配置 GPIO 引脚的中断触发模式之前,需将其设置为输入模式,四种触发模式的设置如下所示:
非中断引脚:echo "none" > edge 上升沿触发:echo "rising" > edge 下降沿触发:echo "falling" > edge 边沿触发: echo "both" > edge |
至此,关于GPIO的控制相关的知识就讲解完成了,下面将分别进行GPIO的输入和输出实验。
14.2 GPIO输出应用编程
本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\62”目录下,如下图所示:
实验要求:
通过GPIO输出应用程序控制GPIO口输出高低电平,以此来控制LED灯的亮灭。
14.2.1编写应用程序
实验步骤:
首先进入ubuntu的终端界面输入以下命令来创建 demo62_gpioout.c文件,如下图所示:
vim demo62_gpioout.c
然后向该文件中添加以下内容:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
char gpio_path[100]; // 存放GPIO路径
int gpio_ctrl(char *arg, char *val) // 控制GPIO输出高低电平
{
char file_path[100]; // 存放文件路径
int fd, len, ret;
sprintf(file_path, "%s/%s", gpio_path, arg); // 将文件路径拼接起来
fd = open(file_path, O_WRONLY); // 以只写方式打开文件
if (fd < 0)
{
printf("无法打开文件: %s\n", file_path); // 打开文件失败
return -1;
}
len = strlen(val);
ret = write(fd, val, len); // 向文件中写入val内容
if (ret < 0)
{
printf("无法写入文件: %s\n", file_path); // 写入文件失败
close(fd); // 关闭文件描述符
return -1;
}
close(fd); // 关闭文件描述符
return 0;
}
int main(int argc, char *argv[]) // 主函数
{
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 将GPIO路径存放到gpio_path中
// 如果 GPIO 没有被导出,则导出 GPIO
if (access(gpio_path, F_OK)) // 判断是否存在gpio_path指向的路径
{
int fd, len, ret;
fd = open("/sys/class/gpio/export", O_WRONLY); // 打开export文件
if (fd < 0)
{
printf("无法打开文件: /sys/class/gpio/export\n"); // 打开文件失败
return -1;
}
len = strlen(argv[1]);
ret = write(fd, argv[1], len); // 向文件中写入argv[1]的内容
if (ret < 0)
{
printf("无法写入文件: /sys/class/gpio/export\n"); // 写入文件失败
close(fd); // 关闭文件描述符
return -1;
}
close(fd); // 关闭文件描述符
}
gpio_ctrl("direction", "out"); // 配置GPIO为输出模式
gpio_ctrl("active_low", "0"); // 设置极性
gpio_ctrl("value", argv[2]); // 控制GPIO输出高低电平
return 0; // 返回0表示程序正常退出
}
第35行首先会判断相应的gpio文件是否存在,不存在则进行gpio的导出,存在就继续运行,第67-79行为了方便通过gpio_ctrl函数进行属性的配置。
保存退出之后,使用以下命令设置交叉编译器环境,并对demo62_gpioout.c进行交叉编译,编译完成如下图所示:
export PATH=/usr/local/arm64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH
aarch64-none-linux-gnu-gcc -o demo62_gpioout demo62_gpioout.c
最后将交叉编译生成的demo62_gpioout文件拷贝到/home/nfs共享目录下即可。
14.2.2开发板测试
Buildroot系统启动之后,首先使用以下命令进行nfs共享目录的挂载(其中192.168.1.7为作者ubuntu的ip地址,需要根据自身ubuntu的ip来设置),如下图所示:
mount -t nfs -o nfsvers=3,nolock 192.168.1.7:/home/nfs /mnt
nfs共享目录挂载到了开发板的/mnt目录下,进入到/mnt目录下,如下图所示:
可以看到/mnt目录下demo62_gpioout文件已经存在了,然后使用以下命令导出LED的GPIO2_PC4 pin脚,并设置为高电平,如下图所示:
./demo62_gpioout 84 1
使用命令之后会发现LED灯会亮起,然后使用以下命令关闭led灯,如下图所示:
./demo62_gpioout 84 0
命令运行成功之后,可以看到LED灯又熄灭了。至此,GPIO输出应用程序在开发板的测试就完成了。
14.3 GPIO输入应用编程
本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\63”目录下,如下图所示:
实验要求:
通过GPIO输入应用程序来打印GPIO口当前输入的电平。
14.3.1编写应用程序
实验步骤:
首先进入到ubuntu的终端界面输入以下命令来创建 demo63_gpioin.c文件,如下图所示:
vim demo63_gpioin.c
然后向该文件中添加以下内容:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
char gpio_path[100]; // GPIO文件路径
int gpio_ctrl(char *arg, char *val)
{
char file_path[100]; // 文件路径
int fd, len, ret; // 文件描述符,写入字节数,返回值
sprintf(file_path, "%s/%s", gpio_path, arg); // 将GPIO文件路径和文件名拼接成文件路径
fd = open(file_path, O_WRONLY); // 以只写方式打开文件
if (fd < 0)
{
printf("open error\n"); // 打开文件错误
return -1;
}
len = strlen(val); // 获取字符串的长度
ret = write(fd, val, len); // 向文件中写入数据
if (ret < 0)
{
printf("write error\n"); // 写入数据错误
return -1;
}
close(fd); // 关闭文件
return 0;
}
int main(int argc, char *argv[])
{
char file_path[100], buf[1]; // 文件路径,读取缓冲区
int fd, len, ret; // 文件描述符,写入字节数,返回值
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 将GPIO文件路径和GPIO号拼接成GPIO文件路径
if (access(gpio_path, F_OK)) // 检查GPIO文件是否存在
{
fd = open("/sys/class/gpio/export", O_WRONLY); // 打开export文件
if (fd < 0)
{
printf("open error\n"); // 打开文件错误
return -1;
}
len = strlen(argv[1]); // 获取字符串的长度
ret = write(fd, argv[1], len); // 向文件中写入GPIO号
if (ret < 0)
{
printf("write error\n"); // 写入数据错误
return -1;
}
close(fd); // 关闭文件
}
gpio_ctrl("direction", "in"); // 配置为输入模式
gpio_ctrl("active_low", "0"); // 极性设置
gpio_ctrl("edge", "none"); // 设置非中断输入
sprintf(file_path, "%s/%s", gpio_path, "value"); // 将GPIO文件路径和value文件名拼接成文件路径
fd = open(file_path, O_RDONLY); // 以只读方式打开文件
if (fd < 0)
{
printf("open error\n"); // 打开文件错误
return -1;
}
ret = read(fd, buf, 1); // 从文件中读取1个字节的数据
if (ret < 0)
{
printf("write error\n"); // 读取数据错误
return -1;
}
if (!strcmp(buf, "1")) // 判断读取的数据是否为1
{
printf("the value is high\n"); // 数据为1,输出高电平
}
else if (!strcmp(buf, "0")) // 判断读取的数据是否为0
{
printf("the value is low\n"); // 数据为0,输出低电平
}
close(fd);
return 0;
}
第41行首先会判断相应的gpio文件是否存在,不存在则进行gpio的导出,存在就继续运行,第59-61行为了方便通过gpio_ctrl函数进行属性的配置。
保存退出之后,使用以下命令设置交叉编译器环境,并对demo63_gpioin.c进行交叉编译,编译完成如下图所示:
export PATH=/usr/local/arm64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH
aarch64-none-linux-gnu-gcc -o demo63_gpioin demo63_gpioin.c
最后将交叉编译生成的demo63_gpioin文件拷贝到/home/nfs共享目录下即可。
14.3.2开发板测试
Buildroot系统启动之后,首先使用以下命令进行nfs共享目录的挂载(其中192.168.1.7为作者ubuntu的ip地址,需要根据自身ubuntu的ip来设置),如下图所示:
mount -t nfs -o nfsvers=3,nolock 192.168.1.7:/home/nfs /mnt
nfs共享目录挂载到了开发板的/mnt目录下,进入到/mnt目录下,如下图所示:
可以看到/mnt目录下demo63_gpioin文件已经存在了,然后使用以下命令导出LED的GPIO2_PC4 pin脚,如下图所示:
./demo63_gpioin 84
可以看到LED的GPIO2_PC4 pin脚打印的high值为高,而此时LED灯为点亮状态,所以gpio的状态打印正确。
为了测试输入底电平的状况,作者使用了杜邦线将GND接到了GPIO2_PC4 pin脚上,然后再次使用以下命令来进行状态的检测,如下图所示:
./demo63_gpioin 84
可以看到LED的GPIO2_PC4 pin脚打印的low值为低,而此时LED灯为熄灭状态,所以gpio的状态打印正确。
至此GPIO输入应用程序在开发板的测试就完成了。
14.4 GPIO输入中断编程
本小节代码在配套资料“iTOP-3588开发板\03_【iTOP-RK3588开发板】指南教程\03_系统编程配套程序\64”目录下,如下图所示:
实验要求:
通过GPIO的输入中断程序,将中断触发方式设置为边沿触发,每当触发中断会打印“get interrupt”字符串。
14.4.1编写应用程序
实验步骤:
首先进入到ubuntu的终端界面输入以下命令来创建 demo64_interrupt.c文件,如下图所示:
vim demo64_interrupt.c
然后向该文件中添加以下内容:
#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>
char gpio_path[100];
// 控制GPIO的函数
int gpio_ctrl(char *arg, char *val)
{
char file_path[100];
int fd, len, ret;
// 根据参数和GPIO路径生成要控制的文件路径
sprintf(file_path, "%s/%s", gpio_path, arg);
// 打开文件
fd = open(file_path, O_WRONLY);
if (fd < 0)
{
printf("open error\n");
return -1;
}
// 计算要写入的值的长度,并将值写入文件
len = strlen(val);
ret = write(fd, val, len);
if (ret < 0)
{
printf("write error\n");
return -1;
}
// 关闭文件并返回成功
close(fd);
return 0;
}
int main(int argc, char *argv[])
{
char file_path[100], buff[10];
int fd, len, ret;
struct pollfd fds[1];
// 根据命令行参数生成GPIO路径
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]);
// 如果该GPIO未导出,则导出该GPIO
if (access(gpio_path, F_OK))
{
fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd < 0)
{
printf("open error\n");
return -1;
}
len = strlen(argv[1]);
ret = write(fd, argv[1], len);
if (ret < 0)
{
printf("write error\n");
return -1;
}
close(fd);
}
// 配置GPIO为输入模式、极性为正、边沿触发中断
gpio_ctrl("direction", "in");
gpio_ctrl("active_low", "0");
gpio_ctrl("edge", "both");
// 打开GPIO的值文件并将其文件描述符赋给pollfd结构体
sprintf(file_path, "%s/%s", gpio_path, "value");
fd = open(file_path, O_RDONLY);
if (fd < 0)
{
printf("open error\n");
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLPRI; // 只关心高优先级数据可读(中断)
read(fd, buff, 10); // 先读取一次清除状态
// 进入循环,等待GPIO中断
while (1)
{
ret = poll(fds, 1, -1); // 等待事件发生
if (ret == -1)
{
printf("poll fail\n");
}
if (fds[0].revents & POLLPRI)
{ // 如果高优先级数据可读(中断触发)
printf("get interrupt\n");
}
}
// 关闭文件并返回成功
close(fd);
return 0;
}
第53行首先会判断相应的gpio文件是否存在,不存在则进行gpio的导出,存在就继续运行,第72-74行为了方便通过gpio_ctrl函数进行属性的配置,将中断模式设置为边沿触发,之后使用poll函数进行中断的判断,并打印相应的信息。
保存退出之后,使用以下命令设置交叉编译器环境,并对demo99_gpioin.c进行交叉编译,编译完成如下图所示:
export PATH=/usr/local/arm64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH
aarch64-none-linux-gnu-gcc -o demo64_interrupt demo64_interrupt.c
最后将交叉编译生成的demo64_interrupt文件拷贝到/home/nfs共享目录下即可。
14.4.2开发板测试
Buildroot系统启动之后,首先使用以下命令进行nfs共享目录的挂载(其中192.168.1.7为作者ubuntu的ip地址,需要根据自身ubuntu的ip来设置),如下图所示:
mount -t nfs -o nfsvers=3,nolock 192.168.1.7:/home/nfs /mnt
nfs共享目录挂载到了开发板的/mnt目录下,进入到/mnt目录下,如下图所示:
可以看到/mnt目录demo64_interrupt文件已经存在了,然后使用以下命令导出LED的GPIO2_PC4 pin脚,并设置中断触发模式,如下图所示:
./demo100_interrupt 84
由于中断并没有被触发,所以程序会阻塞,等待中断的进行,然后使用杜邦线将GND接到GPIO2_PC4 pin脚,进行中断的测试,如下图所示:
可以看到中断就被触发了,相应的字符串也被打印了。至此GPIO输入中断应用程序在开发板的测试就完成了。