学习笔记之Linux的GPIO控制

云雨歇 2024-08-18 15:07:03 阅读 56

目录

1. GPIO命名规则

2. GPIO的控制方式

2.1 使用GPIO sysfs接口控制IO

2.2 使用libgpiod控制IO 


      笔者在学习到点灯实验时,想到直接进行小灯的参数修改比较简单,于是想通过直接对GPIO进行操作从而达到相同的效果(这里可能需要修改设备树,这块笔者也不是很懂不敢乱说,于是借用另一个32开发板的小灯进行测试)。泰山派所采用的主控芯片是rk3566,包含40pinGPIO口,具体接口信息如下图所示:

1. GPIO命名规则

        在rk3566中,GPIO口的引脚按照“控制器+端口+索引序号”的形式进行命名,例如GPIO1_A4表示第一组控制器中A端口的4号引脚。在硬件设计中共有5个固定端口A,B,C和D(对应0,1,2,3),各端口有8个索引序号(0~7)。

2. GPIO的控制方式

2.1 使用GPIO sysfs接口控制IO

        在Linux中,最常见的读写GPIO方式就是用GPIO sysfs interface, 是通过操作 /sys/class/gpio 目录下的 export 、 unexport 、gpio{N}/directiongpio{N} /value (用实际引脚号替代{N})等文件实现的,经常出现shell脚本里面。这种方法不需要任何特殊的内核模块或驱动,只需通过用户空间的程序就可以访问和控制GPIO。

        以下是一个基本的步骤指南,用于通过sysfs接口控制GPIO:

1.导出GPIO

        首先,需要将GPIO从内核导出到用户空间。这可以通过向<code>/sys/class/gpio/export文件写入GPIO编号来实现。例如,我们需要控制GPIO3_B4  (32*3+1*8+4 = 108,详细规则可以看:2. GPIO控制 — 快速使用手册—基于LubanCat-RK356x系列板卡 文档)的GPIO,可以这样做:

echo 108 > /sys/class/gpio/export

2.设置GPIO方向

        接下来,需要设置GPIO的方向(输入或输出)。这可以通过修改/sys/class/gpio/gpioX/direction文件来实现,其中X是你之前导出的GPIO的编号。

对于输入GPIO:

echo in > /sys/class/gpio/gpio108/direction

        这里也可以先查看一下此时的IO口状态:

cat /sys/class/gpio/gpio108/value

        可以看到此时是低电平,且小灯是亮着的 

对于输出GPIO:

<code>echo out > /sys/class/gpio/gpio108/direction

3.控制输出GPIO

        如果你设置了GPIO为输出,你可以通过修改/sys/class/gpio/gpioX/value文件来控制其电平。写入1将设置GPIO为高电平,写入0将设置GPIO为低电平。 这里写入高电平,可以看到此时的小灯已经熄灭,证明我们实验成功了。

echo 1 > /sys/class/gpio/gpio108/value

4.复位GPIO

当你不再需要控制某个GPIO时,可以将其从用户空间取消导出。这可以通过向<code>/sys/class/gpio/unexport文件写入GPIO编号来实现。

echo 108 > /sys/class/gpio/unexpor

5.程序编写

    首先介绍两个常用的函数:access和open。

access 函数:

//用于检查进程是否有权限读取、写入或执行某个文件

#include <unistd.h>

int access(const char *pathname, int mode);

        其中:

pathname 是你想要检查的文件的路径。mode 是一个标志,用于指定你想要检查的访问权限。它可以是以下值之一或它们的组合:

R_OK:测试读权限W_OK:测试写权限X_OK:测试执行权限F_OK:测试文件是否存在

        如果调用成功,access 返回0;如果失败,则返回-1,并设置errno以指示错误。

open函数:

        open函数有两个或三个参数,具体形式如下:

//用于打开或创建文件

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);

        其中,pathname参数表示要打开或创建的文件名(含路径,缺省为当前路径)。flags参数用于指定打开文件的方式和权限,常用的标志位有:

O_RDONLY:只读方式打开文件。O_WRONLY:只写方式打开文件。O_RDWR:读写方式打开文件。O_CREAT:如果文件不存在,则创建文件。O_EXCL:与O_CREAT一起使用,如果文件已存在,则返回错误。O_TRUNC:如果文件已存在,将其截断为0字节。O_APPEND:在文件末尾追加写入。

        当使用两个参数的open函数时,仅通过flags参数指定打开文件的方式和权限。而使用三个参数的open函数时,还需要通过mode参数指定新文件的存取许可权限。

        open函数的返回值是一个文件描述符(file descriptor),它是一个非负整数,用于在后续的文件操作中标识该文件。如果操作成功,open函数将返回一个有效的文件描述符;如果操作失败,将返回-1。 

    在认识以上两个函数之后,我们就可以正式来写IO口的驱动函数了,这个过程执行的原理非常简单,这里以野火的程序为例 :

把GPIO的编号写入到export文件,导出GPIO设备。

修改GPIO设备属性direction文件值为out,把GPIO设置为输出方向。

修改GPIO设备属性文件value的值为1或0,控制GPIO高电平或低电平。

#include <string.h>

#include <sys/stat.h>

#include <unistd.h>

#include <fcntl.h>

#include <stdio.h>

#define GPIO_INDEX "42"

static char gpio_path[75];

int gpio_init(char *name)

{

int fd;

sprintf(gpio_path, "/sys/class/gpio/gpio%s", name);

//查询文件是否存在

if (access("gpio_path", F_OK)){

//只读方式打开

fd = open("/sys/class/gpio/export", O_WRONLY);

//打开文件失败

if(fd < 0)

return 1 ;

write(fd, name, strlen(name));

close(fd);

//初始化gpio使用的引脚为输出模式

sprintf(gpio_path, "/sys/class/gpio/gpio%s/direction", name);

fd = open(gpio_path, O_WRONLY);

if(fd < 0)

return 2;

write(fd, "out", strlen("out"));

close(fd);

}

return 0;

}

//向unexport文件写入编号,取消导出

int gpio_deinit(char *name)

{

int fd;

fd = open("/sys/class/gpio/unexport", O_WRONLY);

if(fd < 0)

return 1;

write(fd, name, strlen(name));

close(fd);

return 0;

}

//输出高电平

int gpio_high(char *name)

{

int fd;

sprintf(gpio_path, "/sys/class/gpio/gpio%s/value", name);

fd = open(gpio_path, O_WRONLY);

if(fd < 0){

printf("open gpio%s wrong\n",name);

return -1;

}

if(2 != write(fd, "1", sizeof("1")))

printf("wrong set \n");

close(fd);

return 0;

}

//输出低电平

int gpio_low(char *name)

{

int fd;

sprintf(gpio_path, "/sys/class/gpio/gpio%s/value", name);

fd = open(gpio_path, O_WRONLY);

if(fd < 0){

printf("open gpio%s wrong\n",name);

return -1;

}

if(2 != write(fd, "0", sizeof("0")))

printf("wrong set \n");

close(fd);

return 0;

}

int main(int argc, char *argv[])

{

char buf[10];

int res;

/* 校验传参 */

if (2 != argc) {

printf( "usage: %s <PinNum>\n",argv[0]);

return -1;

}

res = gpio_init(argv[1]);

if(res){

printf("gpio init error,code = %d",res);

return 0;

}

while(1){

printf("Please input the value : 0--low 1--high q--exit\n");

scanf("%10s", buf);

switch (buf[0]){

case '0':

gpio_low(argv[1]);

break;

case '1':

gpio_high(argv[1]);

break;

case 'q':

gpio_deinit(argv[1]);

printf("Exit\n");

return 0;

default:

break;

}

}

return 0;

}

2.2 使用libgpiod控制IO 

        4.8版本的Kernel加入了libgpiod的支持,这里笔者的kernel版本不支持就不继续编译了,可以简单了解一下,详细可以访问野火的:2. GPIO控制 — 快速使用手册—基于LubanCat-RK356x系列板卡 文档

        先输入进行插件安装:

sudo apt install gpiod

        下面是一些简单的常用命令: 

命令

作用

使用举例

说明

gpiodetect

列出所有的GPIO控制器

gpiodetect(无参数)

列出所有的GPIO控制器

gpioinfo

列出gpio控制器的引脚情况

gpioinfo 1

列出第一组控制器引脚组情况

gpioset

设置gpio

gpioset 1 20=0

设置第一组控制器编号4引脚为低电平

gpioget

获取gpio引脚状态

gpioget 1 20

获取第一组控制器编号4的引脚状态

gpiomon

监控gpio的状态

gpiomon 1 20

监控第一组控制器编号4的引脚状态

免责声明:本文所引用的各种资料均用于自己学习使用,这里感谢立创和正点原子官方的资料以及各位优秀的创作者。  



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。