GPIO编程应用开发

发布时间 2023-05-04 10:48:22作者: 阿风小子

1. GPIO编程基础介绍

GPIO(General-Purpose IO Ports),即通用IO接口。GPIO的使用较为简单,主要分为输入和输出两种功能。GPIO主要用于实现一些简单设备的控制。在作为输入型GPIO的情况下,我们可以将该IO连接外部按键或者传感器,用于检测外部状态。当作为输出时,我们可以通过输出高低电平来控制外部设备的运转。

由于GPIO的功能多种多样,我们需要首先将引脚设置为GPIO。设置为GPIO之后,我们需要设置GPIO的方向。当设置为输出时,我们可以控制输出高电平或者低电平。当设置为输入时,我们可以读取GPIO的电平来判断外部输入电平的高低。

2. GPIO编程软件接口

GPIO编程有多种实现方式,在这里,我们通过sysfs方式来实现GPIO的控制实现。

如果要通过sysfs方式控制gpio,首先需要底层内核的支持。为了实现内核对sysfs gpio的支持,我们需要在内核中进行设置。在编译内核的时候,加入 Device Drivers-> GPIO Support ->/sys/class/gpio/… (sysfs interface)。作为GPIO的引脚,不允许在内核中被用作其他用途。

在系统正常运行之后,我们可以在/sys/class/gpio下看到sysfs控制相关的接口。有三种类型的接口, 分别是控制接口,GPIO信号和GPIO控制器三种接口。这部分的具体介绍可参考《kernel\Documentation\gpio\sysfs.txt》。

2.1 控制接口

控制接口用于实现在用户空间对GPIO的控制,主要包括/sys/class/gpio/export和/sys/class/gpio/unexport两个接口。这这两个控制接口都是只写的,/sys/class/gpio/export实现将GPIO控制从内核空间导出到用户空间,/sys/class/gpio/unexport用于实现取消GPIO控制从内核空间到用户空间的导出。

下面以引脚编号为19的GPIO为例进行说明,在/sys/class/gpio/目录下,我们执行"echo 19 > export"之后,将会产生一个”gpio19”节点来控制引脚编号为19的GPIO。我们执行"echo 19 > unexport"之后,将会删除之前通过export产生的”gpio19”节点。为了使用gpio,我们需要首先使用/sys/class/gpio/export导出gpio引脚编号。完成使用之后,通过/sys/class/gpio/unexport删除掉之前导出的gpio引脚。

2.2 GPIO信号

GPIO信号,即为GPIO本身,其路径为/sys/class/gpio/gpioN/,拥有多个属性。通过对这些属性进行控制,就可以实现对GPIO的控制。

  • “direction”属性,读取的值为”in”或者”out”。通过对该属性写入”in”或者”out”可以设置该GPIO为输入或者输出。如果直接写入”out”,则会使GPIO直接输出低电平。也可以通过写入”low”或者”high”来直接设置输出低电平或者高电平。

  • ”value”属性,用于读取输入电平或者控制输出电平。如果GPIO为输出,则对value写入0为输出低电平,写入非0为输出高电平。如果设置为输入的话,则读到0表示输入为低电平,1为高电平。

  • ”edge”属性,用于设置触发电平,只有在GPIO可以设置为中断输入引脚时才会出现该属性。

2.3 GPIO控制器

GPIO控制器,用于表示GPIO 控制实现的初始GPIO,其路径为/sys/class/gpio/gpiochipN/。比如/sys/class/gpio/gpiochip42/ 则表示实现GPIO控制器的初始化编号为42。GPIO控制器的属性为只读属性,包括base、label和ngpio等多个。

  • ”base”属性,和gpiochipN的N代表的含义相同,表示被该组GPIO控制器实现的第一个GPIO.

  • ” ngpio”属性,用于表示该控制器支持多少个GPIO,支持的GPIO编号为从N到N+ngpio-1

  • ” label”属性,用于判断控制器,并不总是唯一的

3. RV1126 开发板GPIO编号的确定

 

 

每个芯片可以有N组GPIO,每组GPIO最多有32个GPIO,分别是GPIOX_Ax,GPIOX_Bx,GPIOX_Cx,GPIOX_Dx(x表示07)即最多有N*32个GPIO。但是在实际设计中,每组的GPIO数量各有不同。实际GPIO编号计算中,第一组GPIO0对应的编号为031。以此类推,RV1126 的GPIOX_YZ(X=04,Y=AD,Z=0~7对应的编号实际为(X)32+(8(Y-A))+Z+1。接下来,我们以板载的LED计算GPIO编号。

3.1 LED的GPIO编号计算

 

 


 

 

从原理图中找到对应LED的设计,具体的连接如下图所示。从图中我们可以看到,LED连接到的GPIO为GPIO0_A2,其对应的GPIO编号实际为(0)32+('A'-'A')8 + 2 +1 = 3。因此,我们如果要在sys_gpio中操作LED,我们就需要将编号3的GPIO进行导出。

4 实际编程操作

在实际操作中,我们使用LED输出,相关的实验过程和相关代码如下。
4.1 导出GPIO口

为了导出GPIO口,我们需要向/sys/class/gpio/export写入需要导出的引脚编号。在使用之后,我们也可以使用/sys/class/gpio/unexport取消导出引脚编号。

导出引脚编号的实现代码如下所示的sysfs_gpio_export()函数。

 int sysfs_gpio_export(unsigned int gpio)
 {
     int fd, len;
     char buf[MAX_BUF];
    // /sys/class/gpio/export
     fd = open( "/sys/class/gpio/export", O_WRONLY);//打开文件
     if (fd < 0) {
         perror("gpio/export");
         return fd;
     }
  
     len = snprintf(buf, sizeof(buf), "%d", gpio);//从数字变换为字符串,即1 变为”1“
     write(fd, buf, len);//将需要导出的GPIO引脚编号进行写入
    close(fd);//关闭文件
 
     return 0;
 }

取消导出引脚编号的实现代码如下所示。

 int sysfs_gpio_unexport(unsigned int gpio)
{
     int fd, len;
     char buf[MAX_BUF];
    // /sys/class/gpio/unexport
     fd = open("/sys/class/gpio/unexport", O_WRONLY);//打开文件
    if (fd < 0) {
         perror("gpio/export");
         return fd;
     }
  
     len = snprintf(buf, sizeof(buf), "%d", gpio);//从数字变换为字符串,即1 变为”1“
     write(fd, buf, len);//将需要取消导出的GPIO引脚编号进行写入
     close(fd);//关闭文件
     return 0;
 }

在实现导出和取消导出引脚编号的函数之后,我们来实现具体的引脚编号的导出。LED对应的引脚编号如下所示

 #define GPIO_LED 3     

在确定了各自对应的引脚编号,我们就可以进行导出了。

 int main(int argc, char **argv) {
     unsigned int i;
     unsigned int value1,value2;

    printf("gpio begin to export gpio\r\n");
    sysfs_gpio_export(GPIO_LED);//export gpio led
     printf("gpio export gpio ok\r\n");
 
     return 0;
 }

在将代码编译之后,我们将代码在板卡上进行运行。

4.2 设置GPIO方向

为了实现导出的引脚的方向设置,我们需要对/sys/class/gpio/gpioN/direction写入不同的值。写入“in”则表示设置为输入,写入“out”则表示设置为输出。设置引脚编号的的实现代码如下所示。

 int sysfs_gpio_set_dir(unsigned int gpio, unsigned int out_flag)
 {
     int fd, len;
     char buf[MAX_BUF];
    // /sys/class/gpio/gpioN/direction
     len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR  "/gpio%d/direction", gpio);
  
     fd = open(buf, O_WRONLY);//打开文件
     if (fd < 0) {
         perror(buf);
         return fd;
     }
  
     if (out_flag)//为1,则写入“out",即设置为输出
         write(fd, "out", 4);
    else//为0,则写入“in",即设置为输入
         write(fd, "in", 3);
  
     close(fd);//关闭文件
     return 0;
 }


int sysfs_gpio_set_value(unsigned int gpio, unsigned int value)
{
     int fd, len;
     char buf[MAX_BUF];
    // /sys/class/gpio/gpioN/value
     len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/value", gpio);
  
     fd = open(buf, O_WRONLY);//打开文件
     if (fd < 0) {
         perror(buf);
         return fd;
     }
  
    if (value)//为1,则写入“1",即设置为输出高电平
         write(fd, "1", 2);
     else//为0,则写入“0",即设置为输出低电平
         write(fd, "0", 2);
  
     close(fd);//关闭文件
    return 0;
 }

在实现引脚输出电平的控制函数之后,我们来实现LED的控制。我们通过将“1”或“0”写入value来控制GPIO输出高电平或者低电平,下为对应代码部分。

 int main(int argc, char **argv) {
     unsigned int i;
     unsigned int value1,value2;
        
    printf("led begin to init\r\n");
    sysfs_gpio_export(GPIO_LED);//export gpio led
 
     sysfs_gpio_set_dir(GPIO_LED, 1);//set as output
     printf("led init ok\r\n");

    while(1)
    {
            
        sysfs_gpio_set_value(GPIO_LED, 1);//output high 
        printf("led off\r\n");
        usleep(500000); //delay 
        sysfs_gpio_set_value(GPIO_LED, 0);//output low 
        printf("led on\r\n");
        usleep(500000);//delay
     }  
    sysfs_gpio_unexport(GPIO_LED);//unexport gpio led
     return 0;
 }

5 shell操作

echo 3 > /sys/class/gpio/export
echo high > /sys/class/gpio/gpio3/direction  //gpio3 输出高电平