8. GPIO控制

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

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

8.1. 使用gpio sysfs接口控制IO

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

以野火IMX8M MINI开发板上的GPIO5_IO17为例,使用GPIO sysfs接口控制IO

GPIO5_IO17物理接口

原理图

GPIO5_IO17原理图接口

GPIO设备目录

GPIO设备目录
  • export文件:导出GPIO,该文件只能写不能读,用户向该文件写 入GPIO的编号N可以向内核申请将该编号的GPIO导出到用户空间, 若内核本身没有把该GPIO用于其它功能,那么在/sys/class/gpio 目录下会新增一个对应编号的gpioN目录。

  • unexport文件:export的相反操作,取消导出GPIO,该文件同样只能写不能读。

  • gpiochipX目录:该目录是指GPIO控制器外设,Ubuntu主机上默认没有这样的功能。

  • gpioN目录:通过export导出的具体GPIO引脚的控制目录。

引脚编号转换

  • IMX8M MINI芯片GPIO引脚名格式通常为GPIOn_IOx,如此处的GPIO5_IO17, 其中n是端口号,x为该组端口的引脚号。

  • 本开发板中export文件使用的编号index与GPIO引脚名的转换关系如下: index = GPIOn_IOx = (n-1)*32 + x

  • 例如GPIO5_IO17使用的引脚编号为:index = GPIO5_IO17 = (5-1)*32 +17 = 145。

  • 要注意并不是所有的引脚都能通过export文件导出到用户空间的。

操作流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 #以下命令在开发板上执行
 #导出GPIO使用的GPIO到用户空间
 echo 145 > /sys/class/gpio/export
 #确认出现了gpio145设备目录
 ls /sys/class/gpio/
 #控制gpio145方向为输出
 echo out > /sys/class/gpio/gpio145/direction
 #实验时可使用电压表检测对应引脚的电平输出。
 #控制gpio145输出高电平
 echo 1 > /sys/class/gpio/gpio145/value
 #控制gpio145输出低电平
 echo 0 > /sys/class/gpio/gpio145/value

8.2. 控制gpio(系统调用)

类似地,也可以通过系统调用的文件操作方式操作GPIO。

相关的源码如下

  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
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
 #include <string.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <fcntl.h>

 int gpio_init(void)
 {
   int fd;
   fd = open("/sys/class/gpio/export", O_WRONLY);
   if(fd < 0){
     printf("gpio_init open export failed\n");
     return 1 ;
   }
   write(fd,"145", strlen("145"));
   close(fd);

   //direction config
   fd = open("/sys/class/gpio/gpio145/direction", O_WRONLY);
   if(fd < 0){
     printf("gpio_init open direction failed\n");
     return 2;
   }
   write(fd, "out", strlen("out"));
   close(fd);
   return 0;
 }

 int gpio_deinit(void)
 {
   int fd;
   fd = open("/sys/class/gpio/unexport", O_WRONLY);
   if(fd < 0){
     printf("gpio_deinit open unexport failed\n");
     return 1;
   }
   write(fd,"145", strlen("145"));
   close(fd);
   return 0;
 }

 int gpio_on(void)
 {
   int fd;
   fd = open("/sys/class/gpio/gpio145/value", O_WRONLY);
   if(fd < 0){
     printf("gpio_on open value failed\n");
     return 1;
   }
   write(fd, "1", 1);
   close(fd);
   return 0;
 }

 int gpio_off(void)
 {
   int fd;
   fd = open("/sys/class/gpio/gpio145/value", O_WRONLY);
   if(fd < 0){
     printf("gpio_off open value failed\n");
     return 1;
   }

   write(fd, "0", 1);
   close(fd);
   return 0;
 }

 int main(int argc, char *argv[])
 {
   char buf[10];
   int res;
   printf("This is the gpio_syscall demo\n");

   res = gpio_init();
   if(res){
     printf("gpio_init error,code = %d\n",res);
     return 0;
   }

   while(1){
     printf("Please input the value : 0--off 1--on q--exit\n");
     scanf("%10s", buf);

     switch (buf[0]){
       case '0':
           gpio_off();
           break;

       case '1':
           gpio_on();
           break;

       case 'q':
           gpio_deinit();
           printf("Exit\n");
           return 0;

       default:
           break;
     }
   }
 }
  • 该代码说明如下:

  • gpio_init函数:它使用了open、write、close等函数修改export和gpioN/direction文件, 初始化gpio使用的引脚为输出模式。

  • gpio_deinit函数:向unexport文件写入编号,取消导出。

  • gpio_on和gpio_off函数:往gpioN/value文件写入1和0,控制引脚输出高低电平。

  • 本代码要特别注意的是export和unexport文件是只有写权限的,所以通过open打开时要 使用“O_WRONLY”标志以写入方式打开,不能使用“O_RDWR”等带读模式的标志。

8.3. libgpiod控制IO

使用以下命令安装libgpiod库

1
2
 sudo apt update
 sudo apt install libgpiod-dev

开发板安装ligpiod-dev后,可以通过以下命令找到具体的头文件和库文件:

1
2
3
4
5
6
 # 在开发板上查找libgpiod库
 dpkg -L libgpiod-dev
 # 以下是输出
 /usr/include/gpiod.h
 /usr/lib/aarch64-linux-gnu/libgpiod.a
 /usr/lib/aarch64-linux-gnu/libgpiod.so

查找结果中的gpiod.h、libgpiod.so和libgpiod.a就是开发板使用的头文件、动态和静态链接库, 它是debian 10 buster默认apt安装的版本。

常用的libgpiod API(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
 //成员变量
 struct gpiod_chip;      //GPIO组句柄
 struct gpiod_line;      //GPIO引脚句柄

 //获取GPIO控制器(GPIO组)
 struct gpiod_chip *gpiod_chip_open(const char *path);

 //获取GPIO引脚
 struct gpiod_line * gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);

 //设置引脚方向为输入模式
 int gpiod_line_request_input(struct gpiod_line *line,const char *consumer);

 //设置引脚为输出模式
 int gpiod_line_request_output(struct gpiod_line *line,const char *consumer, int default_val)

 //设置引脚的高低电平
 int gpiod_line_set_value(struct gpiod_line *line, int value);

 //读取引脚状态
 int gpiod_line_get_value(struct gpiod_line *line);

 //释放GPIO引脚
 void gpiod_line_release(struct gpiod_line *line);

 //关闭GPIO组句柄并释放所有分配的资源。
 void gpiod_chip_close(struct gpiod_chip *chip);

我们可以直接在开发板上进行创建c文件来编译。

1
 vim libgpio_demo.c

我们将控制gpio的程序(引脚为GPIO5_IO17)复制进去,其源码如下:

 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
54
55
56
 #include <gpiod.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>

 int main(int argc, char **argv)
 {
     int i;
     int ret;

     struct gpiod_chip * chip;      //GPIO控制器句柄
     struct gpiod_line * line;      //GPIO引脚句柄

     /*获取GPIO控制器*/
     chip = gpiod_chip_open("/dev/gpiochip4");
     if(chip == NULL)
     {
         printf("gpiod_chip_open error\n");
         return -1;
     }

     /*获取GPIO引脚*/
     line = gpiod_chip_get_line(chip, 17);
     if(line == NULL)
     {
         printf("gpiod_chip_get_line error\n");
         goto release_line;
     }

     /*设置GPIO为输出模式*/
     ret = gpiod_line_request_output(line,"gpio",0);
     if(ret < 0)
     {
         printf("gpiod_line_request_output error\n");
         goto release_chip;
     }

     for(i = 0;i<10;i++)
     {
         gpiod_line_set_value(line,1);
         usleep(2000000);  //延时2s
         gpiod_line_set_value(line,0);
         usleep(2000000);
     }

     release_line:
     /*释放GPIO引脚*/
     gpiod_line_release(line);

     release_chip:
     /*释放GPIO控制器*/
     gpiod_chip_close(chip);


     return 0;
 }

编译程序时需指定libgpiod库及路径头文件路径(不使用交叉编译时并不需要指定路径)。

然后我们在开发板上执行方式一

1
2
3
 sudo aarch64-linux-gnu-gcc libgpio_demo.c -lgpiod -L./libgpiod/ -I./libgpiod/ -o libgpio_demo
 sudo chmod 777 libgpio_demo
 ./libgpio_demo

此时我们的测量电压可以发现电压变化。