01、GPIO 的调试方法(一)
一、内核配置
使用 sysfs 方式控制 gpio, 首先需要底层驱动的支持, 需要在 make menuconfig 图形化配置界面中加入以下配置:
::: Device Drivers
->GPIO Support
->/sys/class/gpio/xxxx :::
二、GPIO sysfs节点介绍
Linux
开发平台实现了通用GPIO
的驱动,用户通过Shell
命令或系统调用即能控制GPIO
的输出和读取其输入值。其属性文件均在/sys/class/gpio
目录下,如:
属性文件有 export
和 unexpor
t。其余几个个文件为符号链接(gpiochip0
,gpiochip32
,gpiochip64
,gpiochip96
…),指向管理对应设备的目录。
2.1、导出与去导出:
导出GPIO引脚需要这样做:
导出(export):
使用前必须先导出引脚才能使用
操作方式:向/sys/class/gpio/export文件写入引脚编号 例:导出编号14的引脚 →
echo 14 > /sys/class/gpio/export
注意事项:
- 这个文件只能写入不能读取
- 如果引脚已被占用(已被导出或被系统使用),操作会失败
取消导出(unexport):
- 使用完引脚后需要删除导出状态
- 操作方式:向/sys/class/gpio/unexport文件写入引脚编号 例:取消导出GPIO0_PB6 → 先查清它的实际编号(比如是8),然后
echo 8 > /sys/class/gpio/unexport
- 同样只能写入不能读取
::: 简单总结: 导出=给export文件写编号 → 开启引脚使用 取消导出=给unexport文件写编号 → 关闭引脚使用 操作前要确保知道引脚对应的实际编号,且引脚未被其他程序占用 :::
2.2、导出成功后:
1. 方向设置(direction)
作用:设置GPIO为输入或输出模式。
操作:
- 读取:查看当前模式(输入或输出)。
- 写入:设置模式("in"或"out")。
默认值:输入模式("in")。
2. 极性反转(active_low)
作用:反转GPIO的高低电平逻辑。
默认值:0(不反转)。
操作:
- 读取:查看当前设置(0或1)。
- 写入:设置为0或1。
效果:
当值为 0 时:
value=1
→ 输出高电平(3.3V/5V)value=0
→ 输出低电平(0V)
当值为 1 时:
value=0
→ 输出高电平value=1
→ 输出低电平
3. 电平控制(value)
作用:
- 输出模式:设置GPIO输出高低电平。
- 输入模式:读取外部输入的电平状态。
操作:
- 写入(仅输出模式有效):
- 读取(输入或输出模式均可):
4. 中断触发模式(edge)
作用:设置GPIO的中断触发方式(需先设为输入模式)。
操作:
- 写入触发模式:
注意:必须先将GPIO设为输入模式(
direction
设为"in")才能配置中断。
:::
总结
方向:先用direction
设置输入或输出。
极性:通过active_low
反转电平逻辑。
电平:在输出模式下用value
控制高低,在输入模式下读取value
。
中断:需先设为输入模式,再通过edge
设置触发方式。 :::
2.3、总结
文件 | 作用 |
---|---|
active_low | 具有读写属性。用于决定 value 中的值是否翻转。0 不翻转,1 翻转。 |
edge | 具有读写属性。设置 GPIO 中断,或检测中断是否发生。 |
subsystem | 符号链接,指向父目录。 |
value | 具有读写属性。GPIO 的电平状态设置或读取。 |
direction | 具有读写属性。用于查看或设置 GPIO 输入输出 |
power | 设备供电方面的相关信息 |
uevent | 内核与 udev(自动设备发现程序)之间的通信接口 |
三、命令行控制GPIO
在应用层我们可以通过 Shell
命令操作 GPIO
。通过以下步骤,就可以控制 GPIO
输入输出。下面步骤是以 GPIO
的输入输出功能进行介绍。
对GPIO0_B6 进行操作:14(gpio)
- 导出GPIO
向export
文件写入需要操作的GPIO
排列序号N
,就可以导出对应的GPIO
设备目录。 操作命令如下:
echo 14 > /sys/class/gpio/export
通过以上操作后在/sys/class/gpio
目录下生成 gpio4
目录,通过读写该设备目录下的属性文件(位于 gpio14
下)就可以操作这个 GPIO
的输入和输出。以此类推可以导出其它 GPIO
设备目录。如果 GPIO 已经被系统占用,导出时候会提示资源占用。
- 设置GPIO方向
GPIO
导出后默认为输入功能。向 direction
文件写入“in
”字符串,表示设置为输入功能;向 direction
文件写入“out
”字符串,表示设置为输出功能。读 direction
文件,会返回 in/out
字符串,in
表示当前 GPIO
作为输入,out
表示当前 GPIO
作为输出。方向查看和设置命令如下:
# cat /sys/class/gpio/gpioN/direction #查看方向
# echo out > /sys/class/gpio/gpioN/direction #设置为输出
# echo in > /sys/class/gpio/gpioN/direction #设置为输入
2
3
例如,查看排列序号为 14
的 GPIO
的方向,在 Shell
下,可以用如下命令:
# cat /sys/class/gpio/gpio14/direction
- GPIO输入电平读取
当 GPIO
被设为输入时,value
文件记录 GPIO
引脚的输入电平状态:1
表示输入的是高电平;0
表示输入的是低电平。通过查看 value
文件可以读取 GPIO
的电平,查看命令如下:
# echo in >/sys/class/gpio/gpioN/direction #设置 GPIO 排列序号为 N 的 GPIO 方向为输入
# cat /sys/class/gpio/gpioN/value #查看 GPIO 排列序号为 N 的 GPIO 电平
2
例如,查看排列序号为 14
的 GPIO
的电平状态,在 Shell
下,可以用如下命令:
# echo in > /sys/class/gpio/gpio14/direction
# cat /sys/class/gpio/gpio14/value
2
- GPIO输出电平设置
当 GPIO
被设为输出时,通过向 value
文件写入 0
或 1
(0
表示输出低电平;1
表示输出高电平)可以设置输出电平的状态,输出命名如下:
# echo out > /sys/class/gpio/gpioN/direction #设置GPIO排列序号为 N 的 GPIO 方向为输出
# echo 0 > /sys/class/gpio/gpioN/value #输出低电平
# echo 1 > /sys/class/gpio/gpioN/value #输出高电平
2
3
例如,设置排列序号为 14 的 GPIO
的电平为高电平,在 Shell
下,可以用如下命令:
# echo out > /sys/class/gpio/gpio14/direction
# echo 0 > /sys/class/gpio/gpio14/value
2
四、Linux 应用控制GPIO方法
4.1、控制输出
- 输入“./gpioctrl 14 1” 命令 LED 灯点亮
- 输入“./gpioctrl 14 0” 命令 LED 灯熄灭。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int fd; // 文件描述符
int ret; // 返回值
char gpio_path[100]; // GPIO路径
int len; // 字符串长度
// 导出GPIO引脚
int gpio_export(char *argv)
{
fd = open("/sys/class/gpio/export", O_WRONLY); // 打开export文件
if (fd < 0)
{
printf("open /sys/class/gpio/export error \n"); // 打开文件失败
return -1;
}
len = strlen(argv); // 获取参数字符串的长度
//echo 8 > /sys/class/gpio/export
ret = write(fd, argv, len); // 将参数字符串写入文件,导出GPIO引脚
if (ret < 0)
{
printf("write /sys/class/gpio/export error \n"); // 写入文件失败
return -2;
}
close(fd); // 关闭文件
}
// 取消导出GPIO引脚
int gpio_unexport(char *argv)
{
fd = open("/sys/class/gpio/unexport", O_WRONLY); // 打开unexport文件
if (fd < 0)
{
printf("open /sys/class/gpio/unexport error \n"); // 打开文件失败
return -1;
}
len = strlen(argv); // 获取参数字符串的长度
ret = write(fd, argv, len); // 将参数字符串写入文件,取消导出GPIO引脚
if (ret < 0)
{
printf("write /sys/class/gpio/unexport error \n"); // 写入文件失败
return -2;
}
close(fd); // 关闭文件
}
// 控制GPIO引脚的属性
int gpio_ctrl(char *arg, char *val)
{
char file_path[100]; // 文件路径
sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径,格式为“gpio_path/arg”
fd = open(file_path, O_WRONLY); // 打开文件
if (fd < 0)
{
printf("open file_path error \n"); // 打开文件失败
return -1;
}
len = strlen(val); // 获取参数字符串的长度
ret = write(fd, val, len); // 将参数字符串写入文件,控制GPIO引脚的属性
if (ret < 0)
{
printf("write file_path error\n"); // 写入文件失败
return -2;
}
close(fd); // 关闭文件
}
int main(int argc, char *argv[]) // 主函数
{
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 构建GPIO路径,格式为“/sys/class/gpio/gpio引脚号”
if (access(gpio_path, F_OK)) // 检查GPIO路径是否存在
{
gpio_export(argv[1]); // 不存在则导出GPIO引脚
}
else
{
gpio_unexport(argv[1]); // 存在则取消导出GPIO引脚
}
gpio_ctrl("direction", "out"); // 配置GPIO为输出模式
gpio_ctrl("value", argv[2]); // 控制GPIO输出高低电平
gpio_unexport(argv[1]); // 最后取消导出GPIO引脚
return 0; // 返回0表示程序正常退出
} ·
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
4.2、输入检测
使用以下命令来进行状态的检测
./gpioctrl 14
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int fd; // 文件描述符
int ret; // 返回值
char gpio_path[100]; // GPIO路径
int len; // 字符串长度
char file_path[100]; // 文件路径
char buf[2]; // 用于读取 GPIO 值的缓冲区
// 导出 GPIO 引脚
int gpio_export(char *argv)
{
fd = open("/sys/class/gpio/export", O_WRONLY); // 打开 export 文件
if (fd < 0)
{
printf("open /sys/class/gpio/export error\n"); // 打开文件失败
return -1;
}
len = strlen(argv); // 获取参数字符串的长度
ret = write(fd, argv, len); // 将参数字符串写入文件,导出 GPIO 引脚
if (ret < 0)
{
printf("write /sys/class/gpio/export error\n"); // 写入文件失败
return -2;
}
close(fd); // 关闭文件
}
// 取消导出 GPIO 引脚
int gpio_unexport(char *argv)
{
fd = open("/sys/class/gpio/unexport", O_WRONLY); // 打开 unexport 文件
if (fd < 0)
{
printf("open /sys/class/gpio/unexport error\n"); // 打开文件失败
return -1;
}
len = strlen(argv); // 获取参数字符串的长度
ret = write(fd, argv, len); // 将参数字符串写入文件,取消导出 GPIO 引脚
if (ret < 0)
{
printf("write /sys/class/gpio/unexport error\n"); // 写入文件失败
return -2;
}
close(fd); // 关闭文件
}
// 控制 GPIO 引脚的属性
int gpio_ctrl(char *arg, char *val)
{
sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径,格式为 "gpio_path/arg"
fd = open(file_path, O_WRONLY); // 打开文件
if (fd < 0)
{
printf("open file_path error\n"); // 打开文件失败
return -1;
}
len = strlen(val); // 获取参数字符串的长度
ret = write(fd, val, len); // 将参数字符串写入文件,控制 GPIO 引脚的属性
if (ret < 0)
{
printf("write file_path error\n"); // 写入文件失败
return -2;
}
close(fd); // 关闭文件
}
// 读取 GPIO 引脚的值
//读取 cat /sys/class/gpio/gpio8/value
int gpio_read_value(char *arg)
{
sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径,格式为 "gpio_path/arg"
fd = open(file_path, O_RDONLY); // 打开文件
if (fd < 0)
{
printf("open file_path error\n"); // 打开文件失败
return -1;
}
ret = read(fd, buf, 1); // 读取文件内容到缓冲区
if (!strcmp(buf, "1"))
{
printf("The value is high\n"); // GPIO 引脚值为高电平
return 1;
}
else if (!strcmp(buf,"0"))
{
printf("The value is low\n"); // GPIO 引脚值为低电平
return 0;
}
close(fd); // 关闭文件
return -1;
}
int main(int argc, char *argv[]) // 主函数
{
int value;
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 构建 GPIO 路径,格式为 "/sys/class/gpio/gpio引脚号"
if (access(gpio_path, F_OK)) // 检查 GPIO 路径是否存在
{
gpio_export(argv[1]); // 不存在则导出 GPIO 引脚
}
else
{
gpio_unexport(argv[1]); // 存在则取消导出 GPIO 引脚
}
gpio_ctrl("direction", "in"); // 配置 GPIO 为输入模式
value = gpio_read_value("value"); // 读取 GPIO 引脚的值
printf("The value is %d\n", value); // 打印读取的 GPIO 引脚的值
gpio_unexport(argv[1]); // 最后取消导出 GPIO 引脚
return 0; // 返回 0 表示程序正常退出
}
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
4.3、使用 GPIO 中断
通过 GPIO 的输入中断程序, 将中断触发方式设置为边沿触发, 每当触发中断会打印 value的值。
./gpioctrl 42&
- 非中断引脚: echo "none" > edge
- 上升沿触发: echo "rising" > edge
- 下降沿触发: echo "falling" > edge
- 边沿触发: echo "both" > edge
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
int fd; // 文件描述符
int ret; // 返回值
char gpio_path[100]; // GPIO路径
int len; // 字符串长度
char file_path[100]; // 文件路径
char buf[2]; // 缓冲区
struct pollfd fds[1]; // poll结构体数组
// 导出GPIO引脚
int gpio_export(char *argv)
{
fd = open("/sys/class/gpio/export", O_WRONLY); // 打开export文件
if (fd < 0)
{
printf("open /sys/class/gpio/export error \n"); // 打开文件失败
return -1;
}
len = strlen(argv); // 获取字符串长度
ret = write(fd, argv, len); // 写入引脚号到export文件
if (ret < 0)
{
printf("write /sys/class/gpio/export error \n"); // 写入失败
return -2;
}
close(fd); // 关闭文件
}
// 取消导出GPIO引脚
int gpio_unexport(char *argv)
{
fd = open("/sys/class/gpio/unexport", O_WRONLY); // 打开unexport文件
if (fd < 0)
{
printf("open /sys/class/gpio/unexport error \n"); // 打开文件失败
return -1;
}
len = strlen(argv); // 获取字符串长度
ret = write(fd, argv, len); // 写入引脚号到unexport文件
if (ret < 0)
{
printf("write /sys/class/gpio/unexport error \n"); // 写入失败
return -2;
}
close(fd); // 关闭文件
}
// 控制GPIO引脚的属性
int gpio_ctrl(char *arg, char *val)
{
sprintf(file_path, "%s/%s", gpio_path, arg); // 构建属性文件的路径
fd = open(file_path, O_WRONLY); // 打开属性文件
if (fd < 0)
{
printf("open file_path error \n"); // 打开文件失败
return -1;
}
len = strlen(val); // 获取字符串长度
ret = write(fd, val, len); // 写入属性值到属性文件
if (ret < 0)
{
printf("write file_path error\n"); // 写入失败
return -2;
}
close(fd); // 关闭文件
}
// 监听GPIO引脚的中断事件
int gpio_interrupt(char *arg)
{
sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径
fd = open(file_path, O_RDONLY); // 打开文件
if (fd < 0)
{
printf("open file_path error \n"); // 打开文件失败
return -1;
}
memset((void *)fds, 0, sizeof(fds)); // 清空poll结构体数组
fds[0].fd = fd; // 设置poll结构体的文件描述符
fds[0].events = POLLPRI; // 设置poll结构体的事件类型为POLLPRI,表示有紧急数据可读
read(fd, buf, 2); // 读取文件内容,清除中断事件
ret = poll(fds, 1, -1); // 调用poll函数等待中断事件发生,阻塞直到事件发生
if (ret <= 0)
{
printf("poll error \n"); // 调用poll失败或超时
return -1;
}
if(fds[0].revents & POLLPRI)
{
lseek(fd, 0, SEEK_SET); // 重新定位文件指针到文件开头
read(fd, buf, 2); // 读取文件内容,获取中断事件的值
buf[1] = '\0';
printf("value is %s\n", buf); // 输出中断事件的值
}
}
// 读取GPIO引脚的值
int gpio_read_value(char *arg)
{
sprintf(file_path, "%s/%s", gpio_path, arg); // 构建文件路径
fd = open(file_path, O_WRONLY); // 打开文件,以只写模式打开是一个错误,应该使用只读模式
if (fd < 0)
{
printf("open file_path error\n"); // 打开文件失败
return -1;
}
ret = read(fd, buf, 1); // 读取文件内容,获取引脚的值
if (!strcmp(buf, "1"))
{
printf("The value is high\n"); // 引脚值为高电平
return 1;
}
else if (!strcmp(buf, "0"))
{
printf("The value is low\n"); // 引脚值为低电平
return 0;
}
return -1; // 这里应该返回读取到的引脚值(0或1),而不是返回固定的-1
close(fd); // 关闭文件(这行代码无法执行到,应该放在read之前)
}
int main(int argc, char *argv[]) // 主函数
{
int value;
sprintf(gpio_path, "/sys/class/gpio/gpio%s", argv[1]); // 构建GPIO路径
if (access(gpio_path, F_OK)) // 检查GPIO路径是否存在
{
gpio_export(argv[1]); // 不存在则导出GPIO引脚
}
gpio_ctrl("direction", "in"); // 设置GPIO引脚为输入模式
gpio_ctrl("edge", "both"); // 设置GPIO引脚的中断触发方式为上升沿和下降沿
gpio_interrupt("value"); // 监听GPIO引脚的中断事件
gpio_unexport(argv[1]); // 最后取消导出GPIO引脚
return 0; // 返回0表示程序正常退出
}
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149