2. GPIO控制

GPIO是General Purpose I/O的缩写,即通用输入输出端口,简单来说就是MCU/CPU可控制的引脚, 这些引脚通常有多种功能,最基本的是高低电平输入检测和输出,部分引脚还会与主控器的片上外设绑定, 如作为串口、I2C、网络、电压检测的通讯引脚。

Linux提供了GPIO子系统驱动框架,使用该驱动框架即可灵活地控制板卡上的GPIO。

2.1. GPIO性能

  • 灌电流与拉电流为0-20mA可配置

2.2. GPIO命名

Allwinner Pin的ID按照 控制器端口(port)+索引序号(pin) 组成。H618的GPIO资源如下:

  • 控制器端口有 PC、PF、PG、PH、PI、PL

  • 控制器端口与具体控制器端口有关,比如PG就有20个索引序号

注意

下表多数引脚已经被系统使用,测试时请使用26Pin排针中引脚

控制器端口

索引序号(索引号)

PC

0,1,2,3,······,15,16

PF

0,1,2,3,4,5,6

PG

0,1,2,3,······,18,19

PH

0,1,2,3,······,9,10

PI

0,1,2,3,······,15,16

PL

0,1

2.3. 引脚号计算

2.3.1. 计算公式

引脚号的计算公式为:32 x (控制器端口) + (索引号)

如:PH2表达的意思为GPIO控制器为PH,索引号为2。该引脚号的计算公式为32 x 7 + 2 = 226

2.3.2. AllwinnerPin

AllwinnerPin是一款全志芯片引脚计算器软件。

获取方式: 网盘资料下载链接 -> 6-开发软件 -> AllwinnerPin.zip

正向计算(GPIO->引脚号):

../../../_images/AllwinnerPin-1.png

反向计算(引脚号->GPIO):

../../../_images/AllwinnerPin-2.png

超过最大引脚数量报错:

../../../_images/AllwinnerPin-3.png

2.4. 使用GPIO sysfs接口控制IO

2.4.1. 命令行的方式

在Linux中,最常见的读写GPIO方式就是用GPIO sysfs interface, 是通过操作 /sys/class/gpio 目录下的 exportunexportgpio{N}/direction, gpio{N} /value (用实际引脚号替代{N})等文件实现的,经常出现shell脚本里面。 在kernel 4.8开始,加入了libgpiod的支持;而原有基于sysfs的访问方式,将被逐渐放弃。

GPIO举例计算

引脚

控制器

索引号

计算结果

PC3

PC

3

67 (32 x 2 + 3)

PG16

PG

16

208 (32 x 6 + 16)

PH7

PH

7

231 (32 x 7 + 7)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#以下所有操作均需要打开管理者权限使用
#使能引脚PC3
echo 67 > /sys/class/gpio/export

#设置引脚为输入模式
echo in > /sys/class/gpio/gpio67/direction
#读取引脚的值
cat /sys/class/gpio/gpio67/value

#设置引脚为输出模式
echo out > /sys/class/gpio/gpio67/direction
#设置引脚为低电平
echo 0 > /sys/class/gpio/gpio67/value
#设置引脚为高电平
echo 1 > /sys/class/gpio/gpio67/value

#复位引脚
echo 67 > /sys/class/gpio/unexport

2.5. 使用libgpiod控制IO

从Linux 4.8版本开始,Linux引入了新的gpio操作方式,GPIO字符设备。libgpiod是一种字符设备接口,GPIO访问控制是通过操作字符设备文件(比如 /dev/gpiodchip0 )实现的, 并通过libgpiod提供一些命令工具、c库以及python封装。

想要使用libgpiod,需要在板卡上安装libgpiod库。

1
2
#安装gpiod 命令行工具
sudo apt install -y gpiod
  • gpiod工具的使用方法与sysfs接口的不同,gpiod是以控制器为单位,然后再详细到端口号和索引号,即gpiod使用两个数据确定引脚

GPIO举例计算

引脚

控制器

索引号

计算结果

PC3

PC

3

67 (32 x 2 + 3)

PG16

PG

16

208 (32 x 6 + 16)

PH7

PH

7

231 (32 x 7 + 7)

重要

Allwinner Pin的ID按照 控制器端口(port)+索引序号(pin) 组成。其中端口号和索引号会合并成一个数值传入到gpiod里去,并不是所有的引脚都能够使用libgpiod控制,因为有些引脚已经被系统占用。

常用的命令行如下,可使用 -h 查看命令相对应的使用说明,-h 查看版本信息。

2.5.1. gpiodetect

gpiodetect 命令用于列出系统上存在的所有 gpiochip,以及它们的名称、标签和 GPIO lines。

语法:

1
gpiodetect [OPTIONS]

选项[OPTIONS]:

  • -h, –help :查看帮助并退出

  • -v, –version :查看版本信息并退出

示例:

1
2
3
cat@lubancat:~$ sudo gpiodetect
gpiochip0 [7022000.pinctrl] (32 lines)
gpiochip1 [300b000.pinctrl] (288 lines)

三列数据分别是 gpiochip 的名称(name)、标签(label)和行数(lines)。其中 gpiochip1 为后面需要用到的GPIO 控制器组。

2.5.2. gpioinfo

gpioinfo 命令用于列出指定 gpiochip 的所有 line,以及它们的名称、使用者、方向、活动状态和其他标志。

语法:

1
gpioinfo [OPTIONS] <gpiochip> ...

选项[OPTIONS]:

  • -h, –help :查看帮助并退出

  • -v, –version :查看版本信息并退出

参数:

  • <gpiochip>:指定 gpiochip,如 gpiochip0、gpiochip1,可同时输入多个参数。如果没有参数,则查询所有 gpiochip 的所有 line 的信息。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#查询gpiochip1
cat@lubancat:~$ sudo gpioinfo gpiochip1
gpiochip1 - 288 lines:
      line   0:      unnamed       unused   input  active-high
      line   1:      unnamed       unused   input  active-high
      line   2:      unnamed       unused   input  active-high
      ······

#可简化成sudo gpioinfo 1
cat@lubancat:~$ sudo gpioinfo 1
gpiochip1 - 288 lines:
      line   0:      unnamed       unused   input  active-high
      line   1:      unnamed       unused   input  active-high
      line   2:      unnamed       unused   input  active-high
      ······

2.5.3. gpioset

gpioset 命令用于设置指定的 GPIO line 的值。

语法:

1
gpioset [OPTIONS] <chip name/number> <offset1>=<value1> <offset2>=<value2> ...

选项[OPTIONS]:

  • -l, –active-low :设置低电平为有效电平

  • -B, –bias=[as-is|disable|pull-down|pull-up] :设置 bias(默认使用 as-is)

  • -D, –drive=[push-pull|open-drain|open-source] :设置驱动模式(默认使用 push-pull)

  • -m, –mode=[exit|wait|time|signal] :设置完成后的动作模式

  • -s, –sec=SEC :当使用 –mode=time 选项时,指定等到的时间(单位:秒)

  • -u, –usec=USEC :当使用 –mode=time 选项时,指定等到的时间(单位:微秒)

  • -b, –background :设置完成后与控制终端分离

  • -h, –help :查看帮助并退出

  • -v, –version :查看版本信息并退出

参数:

  • <chip name/number>:指定 gpiochip,要使用板卡GPIO时需指定为gpiochip1/1。

  • <offset>:行内偏移量,这里参数应该为上述GPIO的计算结果,如PC3引脚为67。

  • <value>:参数为0和1,当高电平为有效电平(默认)时,1为高,0为低。

示例:

1
2
3
4
5
#设置PF6、PC3、PH7为高电平
cat@lubancat:~$ sudo gpioset 1 166=1 67=1 231=1

#设置PF6、PC3、PH7为低电平
cat@lubancat:~$ sudo gpioset 1 166=0 67=0 231=0

2.5.4. gpioget

gpioget 命令用于读取指定 GPIO line 的值。

语法:

1
gpioget [OPTIONS] <chip name/number> <offset 1> <offset 2> ...

选项[OPTIONS]:

  • -l, –active-low :设置低电平为有效电平

  • -B, –bias=[as-is|disable|pull-down|pull-up] :设置 bias(默认使用 as-is)

  • -h, –help :查看帮助并退出

  • -v, –version :查看版本信息并退出

参数:

  • <chip name/number>:指定 gpiochip,要使用板卡GPIO时需指定为gpiochip1/1。

  • <offset>:行内偏移量,这里参数应该为上述GPIO的计算结果,如PC3引脚为67。

示例:

1
2
3
#查看PF6、PC3、PH7引脚电平
cat@lubancat:~$ sudo gpioget 1 166 67 231
1 1 1

2.5.5. gpiomon

gpiomon 命令用于等待指定 GPIO line 上的事件,或指定要监视的事件。

语法:

1
gpiomon [OPTIONS] <chip name/number> <offset 1> <offset 2> ...

选项[OPTIONS]:

  • -l, –active-low :设置低电平为有效电平

  • -B, –bias=[as-is|disable|pull-down|pull-up] :设置 bias(默认使用 as-is)

  • -n, –num-events=NUM :处理完 NUM 个事件后退出

  • -s, –silent :不打印事件信息

  • -r, –rising-edge :只处理上升沿事件

  • -f, –falling-edge :只处理下降沿事件

  • -b, –line-buffered :将标准输出设置为行缓冲

  • -F, –format=FMT :指定输出格式(%o 为 GPIO 行内偏移量,%e 为事件类型,%s 为事件时间戳秒数部分,%n 为事件时间戳纳秒部分)

  • -h, –help :查看帮助并退出

  • -v, –version :查看版本信息并退出

参数:

  • <chip name/number>:指定 gpiochip,要使用板卡GPIO时需指定为gpiochip1/1。

  • <offset>:行内偏移量,这里参数应该为上述GPIO的计算结果,如PC3引脚为67。

示例:

1
2
3
4
5
6
7
8
#监控PF6、PC3、PH7引脚
cat@lubancat:~$ sudo gpiomon 1 166 67 231
event:  RISING EDGE offset: 166 timestamp: [1706690108.777860215]
event:  RISING EDGE offset: 67 timestamp: [1706690108.778006581]
event: FALLING EDGE offset: 67 timestamp: [1706690128.227108044]
event:  RISING EDGE offset: 67 timestamp: [1706690130.955410448]
event: FALLING EDGE offset: 231 timestamp: [1706690155.876559287]
event:  RISING EDGE offset: 231 timestamp: [1706690161.451626611]

2.6. C程序点灯

下面点灯程序以调用libgpiod库为例,演示板卡为LubanCat-A1。

2.6.1. 代码获取

1
2
3
4
5
6
#如之前有获取则可跳过
#获取仓库
git clone https://gitee.com/LubanCat/lubancat_allwinner_code_storage

#代码所在的位置
lubancat_allwinner_code_storage/quick_start/gpio

2.6.2. 安装libgpiod库

1
sudo apt install libgpiod-dev

2.6.3. 示例代码

代码较长复制粘贴容易乱序,可以下载我们提供的源码 led.c

quick_start/gpio/led.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <unistd.h>
#include <gpiod.h>

#define msleep(t) usleep((t)*1000)

int main(int argc, char const *argv[])
{
    struct gpiod_chip *gpiochip1;
    struct gpiod_line *led;
    struct gpiod_line_request_config config;
    int req;
    /* 板载LED灯:PF6=(F-1)*32+6=(6-1)*32+6=166 */
    int PF6=166;

    /* 打开 GPIO 控制器 */
    gpiochip1 = gpiod_chip_open("/dev/gpiochip1");
    if (!gpiochip1)
    {
        printf("gpiochip open error\n");
        return -1;
    }

    /* 获取PF6引脚 */
    led = gpiod_chip_get_line(gpiochip1, PF6);
    if (!led)
    {
        gpiod_chip_close(gpiochip1);
        printf("led get error.\n");
        return -1;
    }
    
    /* 配置引脚  输出模式 name为“led666” 初始电平为high*/
    req = gpiod_line_request_output(led, "led666", 1);
    if (req)
    {
        fprintf(stderr, "led request error.\n");
        return -1;
    }

    while (1)
    {
        /* 设置引脚电平 */
        gpiod_line_set_value(led, 1);
        printf("led close\n"); //板载LED为低电平触发
        msleep(500);
        gpiod_line_set_value(led, 0);
        printf("led open\n");
        msleep(500);
    }

    return 0;
}

2.6.4. 编译运行

1
2
3
4
#编译,需要指明所需静态链接库,加上“-lgpiod”或者“-l gpiod”都行
gcc led.c -o led -lgpiod
#运行
./led

演示效果如下:

../../../_images/led_test.png

小技巧

此时板载LED灯会以1秒为闪烁周期进行闪烁

2.7. FAQs

Q1:当使用GPIO时出现 gpioset: error setting the GPIO line values: Device or resource busy 或者 -bash: echo: 写错误: 设备或资源忙

A1:说明GPIO被占用了,占用的原因可能是设备树里把该引脚作为gpio或者其他复用功能被使用了。