正点原子 发表于 2021-8-21 16:00:07

《I.MX6U嵌入式Linux C应用编程指南》第十七章 GPIO应用编程

1)实验平台:正点原子i.MX6ULL Linux阿尔法开发板
2)章节摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南
3)购买链接:https://item.taobao.com/item.htm?&id=603672744434
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyz-i.mx6ull.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子Linux技术交流群:1027879335







第十七章 GPIO应用编程
本章介绍应用层如何控制GPIO,譬如控制GPIO输出高电平、或输出低电平。

1.1应用层如何操控GPIO
       与LED设备一样,GPIO同样也是通过sysfs方式进行操控,进入到/sys/class/gpio目录下,如下所示:

图 17.1.1 /sys/class/gpio目录
       可以看到该目录下包含两个文件export、unexport以及5个gpiochipX(X等于0、32、64、96、128)命名的文件夹。
       gpiochipX:当前SoC所包含的GPIO控制器,我们知道I.MX6UL/I.MX6ULL一共包含了5个GPIO控制器,分别为GPIO1、GPIO2、GPIO3、GPIO4、GPIO5,在这里分别对应gpiochip0、gpiochip32、gpiochip64、gpiochip96、gpiochip128这5个文件夹,每一个gpiochipX文件夹用来管理一组GPIO。随便进入到其中某个目录下,可以看到这些目录下包含了如下文件:

图 17.1.2 gpiochip0目录下的文件
         在这个目录我们主要关注的是base、label、ngpio这三个属性文件,这三个属性文件均是只读、不可写。
         base:与gpiochipX中的X相同,表示该控制器所管理的这组GPIO引脚中最小的编号。每一个GPIO引脚都会有一个对应的编号,Linux下通过这个编号来操控对应的GPIO引脚。

图 17.1.3 base属性文件
         label:该组GPIO对应的标签,也就是名字。

图 17.1.4 label属性文件
         ngpio:该控制器所管理的GPIO引脚的数量(所以引脚编号范围是:base ~ base+ngpio-1)。

图 17.1.5 ngpio属性文件
       对于给定的一个GPIO引脚,如何计算它在sysfs中对应的编号呢?其实非常简单,譬如给定一个GPIO引脚为GPIO4_IO16,那它对应的编号是多少呢?首先我们要确定GPIO4对应于gpiochip96,该组GPIO引脚的最小编号是96(对应于GPIO4_IO0),所以GPIO4_IO16对应的编号自然是96 + 16 = 112;同理GPIO3_IO20对应的编号是64 + 20 = 84。
       export:用于将指定编号的GPIO引脚导出。在使用GPIO引脚之前,需要将其导出,导出成功之后才能使用它。注意export文件是只写文件,不能读取,将一个指定的编号写入到export文件中即可将对应的GPIO引脚导出,譬如:
echo 0 > export                # 导出编号为0的GPIO引脚(对于I.MX6UL/I.MX6ULL来说,也就是GPIO1_IO0)
图 17.1.6 导出成功
导出成功之后会发现在/sys/class/gpio目录下生成了一个名为gpio0的文件夹(gpioX,X表示对应的编号),如图 16.1.7所示。这个文件夹就是导出来的GPIO引脚对应的文件夹,用于管理、控制该GPIO引脚,稍后再给大家介绍。

图 17.1.7 GPIO引脚对应的文件夹
      unexport:将导出的GPIO引脚删除。当使用完GPIO引脚之后,我们需要将导出的引脚删除,同样该文件也是只写文件、不可读,譬如:
echo 0 > unexport                # 删除导出的编号为0的GPIO引脚
       删除成功之后,之前生成的gpio0文件夹就会消失!
       以上就给大家介绍了/sys/class/gpio目录下的所有文件和文件夹,控制GPIO引脚主要是通过export导出之后所生成的gpioX(X表示对应的编号)文件夹,在该文件夹目录下存在一些属性文件可用于控制GPIO引脚的输入、输出以及输出的电平状态等。
       Tips:需要注意的是,并不是所有GPIO引脚都可以成功导出,如果对应的GPIO已经在内核中被使用了,那便无法成功导出,打印如下信息:

图 17.1.8 导出失败
       那也就是意味着该引脚已经被内核使用了,譬如某个驱动使用了该引脚,那么将无法导出成功!
gpioX
       将指定的编号写入到export文件中,可以导出指定编号的GPIO引脚,导出成功之后会在/sys/class/gpio目录下生成对应的gpioX(X表示GPIO的编号)文件夹,以前面所生成的gpio0为例,进入到gpio0目录,该目录下的文件如下所示:

图 17.1.9 gpioX文件夹
       我们主要关心的文件是active_low、direction、edge以及value这四个属性文件,接下来分别介绍这四个属性文件的作用:
       direction:配置GPIO引脚为输入或输出模式。该文件可读、可写,读表示查看GPIO当前是输入还是输出模式,写表示将GPIO配置为输入或输出模式;读取或写入操作可取的值为"out"(输出模式)和"in"(输入模式),如下所示:

图 17.1.10 direction属性文件
      value:在GPIO配置为输出模式下,向value文件写入"0"控制GPIO引脚输出低电平,写入"1"则控制GPIO引脚输出高电平。在输入模式下,读取value文件获取GPIO引脚当前的输入电平状态。譬如:
# 获取GPIO引脚的输入电平状态
echo "in" > direction
cat value

# 控制GPIO引脚输出高电平
echo "out" > direction
echo "1" > value
         active_low:这个属性文件用于控制极性,可读可写,默认情况下为0,譬如:
# active_low等于0时
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                #输出高
       由此看出,active_low的作用已经非常明显了,对于输入模式来说也同样适用。
       edge:控制中断的触发模式,该文件可读可写。在配置GPIO引脚的中断触发模式之前,需将其设置为输入模式:
      非中断引脚:echo "none" > edge
      上升沿触发:echo "rising" > edge
      下降沿触发:echo "falling" > edge
      边沿触发:echo "both" > edge
      当引脚被配置为中断后可以使用poll()函数监听引脚的电平状态变化,在后面的示例中将向大家介绍。
1.2编写应用程序
      本例程源码对应的路径为:开发板光盘->11、Linux C应用编程例程源码->17_gpio->gpio.c。
      上一小节已经向大家介绍了如何通过sysfs方式控制开发板上的GPIO引脚,本小节我们编写一个简单地测试程序,控制开发板上的某一个GPIO输出PWM波形,其示例代码如下所示:
示例代码 17.2.1 控制GPIO输出PWM
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#defineEXPORT   "/sys/class/gpio/export"

int main(int argc, char *argv[])
{
    char gpio_dir = "/sys/class/gpio/gpio";
    char file_path = {0};
    int fd;

    /* 校验传参 */
    if (2 != argc) {
      fprintf(stderr, "usage: %s <gpio>\n", argv);
      exit(-1);
    }

    /* 判断指定编号的GPIO是否导出 */
    strcat(gpio_dir, argv);
    if (0 > access(gpio_dir, F_OK)) {//如果目录不存在 则需要导出
      if (0 > (fd = open(EXPORT, O_WRONLY))) {
            perror("open error");
            exit(-1);
      }

      if (0 > write(fd, argv, strlen(argv))) {//导出gpio
            perror("write error");
            exit(-1);
      }

      close(fd);//关闭文件
    }

    /* 配置为输出模式 */
    strcpy(file_path, gpio_dir);
    strcat(file_path, "/direction");
    if (0 > (fd = open(file_path, O_WRONLY))) {
      perror("open error");
      exit(-1);
    }

    if (0 > write(fd, "out", 3)) {//配置为输出模式
      perror("write error");
      exit(-1);
    }

    close(fd);

    /* 极性设置 */
    memset(file_path, 0x0, sizeof(file_path));
    strcpy(file_path, gpio_dir);
    strcat(file_path, "/active_low");
    if (0 > (fd = open(file_path, O_WRONLY))) {
      perror("open error");
      exit(-1);
    }

    if (0 > write(fd, "0", 1)) {
      perror("write error");
      exit(-1);
    }

    close(fd);

    /* 控制GPIO输出高低电平 */
    memset(file_path, 0x0, sizeof(file_path));
    strcpy(file_path, gpio_dir);
    strcat(file_path, "/value");
    if (0 > (fd = open(file_path, O_WRONLY))) {
      perror("open error");
      exit(-1);
    }

    for ( ; ; ) {   //产生PWM50Hz
      write(fd, "1", 1);                  //拉高
      usleep(10 * 1000);          //休眠10ms
      write(fd, "0", 1);                  //拉低
      usleep(10 * 1000);          //休眠10ms
    }
}
         执行程序时需要传入一个参数,该参数指定一个编号,程序会控制指定编号的GPIO引脚产生PWM。上述代码中首先使用access()函数判断指定编号的GPIO引脚是否已经导出,也就是判断相应的gpioX目录是否存在,如果不存在则表示未导出,则通过"/sys/class/gpio/export"文件将其导出;导出之后先配置了GPIO引脚为输出模式,也就是向direction文件中写入"out";接着再配置极性,通过向active_low文件中写入"0";最后再控制GPIO引脚输出相应的高低电平,以使其产生PWM,通过对value文件写入"1"或"0"来拉高或拉低GPIO。
使用交叉编译工具编译应用程序,如下所示:

图 17.2.1 编译应用程序
1.3在开发板上测试
       将上小节编译得到的可执行文件testApp拷贝到开发板根文件系统中,譬如拷贝到开发板Linux系统的家目录下,接着执行应用程序:

图 17.3.1 执行应用程序
执行应用程序时,笔者指定的编号为1,也就是对应I.MX6UL/I.MX6ULL的GPIO1_IO01,在阿尔法开发板底板上已经将该引脚引出了,如下所示:

图 17.3.2 GPIO1_IO01引脚
程序执行之后,大家可以使用示波器查看该引脚是否输出了PWM波形,下图是笔者使用示波器所测试的结果:

图 17.3.3 测试结果
从图中可以看出,由应用程序所控制产生的PWM波其周期为50Hz,占空比为50%,与程序实际设置的情况是相符合的。
页: [1]
查看完整版本: 《I.MX6U嵌入式Linux C应用编程指南》第十七章 GPIO应用编程