7. GPIO操作指南¶
7.1. 操作准备¶
使用SDK发布的kernel内核
7.2. 操作流程¶
GPIO相关模块默认已编入内核,无需执行加载命令
通过控制台执行GPIO读写命令,或在内核态/用户态编写GPIO操作程序即可
7.3. 操作示例¶
7.3.1. GPIO命令行操作示例¶
- 步骤1:导出GPIO控制权
echo N > /sys/class/gpio/exportN = GPIO组号值 + 偏移值
示例:GPIO1_2引脚(GPIO1组号448 + 偏移2 → N=450)
GPIO组号对应表:
GPIO组
Linux组号值
GPIO0 (GPIOA)
480
GPIO1 (GPIOB)
448
GPIO2 (GPIOC)
416
GPIO3 (GPIOD)
384
PWR_GPIO (GPIOE)
352
- 步骤2:配置GPIO方向
# 设置为输入模式 echo in > /sys/class/gpio/gpioN/direction # 设置为输出模式 echo out > /sys/class/gpio/gpioN/direction
- 步骤3:GPIO读写操作
# 读取输入值 cat /sys/class/gpio/gpioN/value # 输出低电平 echo 0 > /sys/class/gpio/gpioN/value # 输出高电平 echo 1 > /sys/class/gpio/gpioN/value
- 步骤4:释放GPIO资源
echo N > /sys/class/gpio/unexport
注解
开启CONFIG_DEBUG_FS选项后,可通过以下命令查看GPIO映射关系:
cat /sys/kernel/debug/gpio
7.3.2. 内核态GPIO操作示例¶
以下为完整的内核态 GPIO 操作示例,以可加载内核模块形式实现。示例以
GPIO1_2(编号 448 + 2 = 450)为例,实际使用时请根据 GPIO 组号对应表修改
GPIO_TEST_NUM 宏定义。模块加载后依次执行 GPIO 读写测试和中断注册,
卸载时自动释放资源。
7.3.2.1. 头文件与宏定义¶
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
/* 示例 GPIO:GPIO1_2 = 448 + 2 */
#define GPIO_TEST_NUM 450
/* 用作 request_irq / free_irq 的 dev_id 标识 */
#define GPIO_IRQ_DEV_ID ((void *)GPIO_TEST_NUM)
7.3.2.2. 中断回调函数¶
GPIO 电平发生变化时由内核调用,打印当前电平用于调试。
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
int val = gpio_get_value(GPIO_TEST_NUM);
printk(KERN_INFO "GPIO%d IRQ triggered, current value: %d\n",
GPIO_TEST_NUM, val);
return IRQ_HANDLED;
}
7.3.2.3. 模块初始化(GPIO 读写 + 中断注册)¶
insmod 加载模块时执行,分两个阶段:先做基础 GPIO 输出/输入读写测试,
再配置为输入模式并注册双边沿中断。
static int __init gpio_drv_init(void)
{
int ret, irq_num, val;
/*
* 阶段一:GPIO 读写测试
*/
/* 申请 GPIO 资源 */
ret = gpio_request(GPIO_TEST_NUM, "gpio_test");
if (ret < 0) {
printk(KERN_ERR "gpio_request(%d) failed: %d\n",
GPIO_TEST_NUM, ret);
return ret;
}
/* 配置为输出模式,初始低电平 */
gpio_direction_output(GPIO_TEST_NUM, 0);
printk(KERN_INFO "GPIO%d set to output, initial value: 0\n",
GPIO_TEST_NUM);
/* 输出高电平并保持 1 秒 */
gpio_set_value(GPIO_TEST_NUM, 1);
mdelay(1000);
printk(KERN_INFO "GPIO%d set to HIGH\n", GPIO_TEST_NUM);
/* 切换为输入模式并读取当前电平 */
gpio_direction_input(GPIO_TEST_NUM);
val = gpio_get_value(GPIO_TEST_NUM);
printk(KERN_INFO "GPIO%d switched to input, current value: %d\n",
GPIO_TEST_NUM, val);
/*
* 阶段二:中断配置
*/
/* 将 GPIO 编号转换为 IRQ 中断号 */
irq_num = gpio_to_irq(GPIO_TEST_NUM);
if (irq_num < 0) {
printk(KERN_ERR "gpio_to_irq(%d) failed: %d\n",
GPIO_TEST_NUM, irq_num);
gpio_free(GPIO_TEST_NUM);
return irq_num;
}
printk(KERN_INFO "GPIO%d mapped to IRQ %d\n",
GPIO_TEST_NUM, irq_num);
/* 注册中断:上升沿 + 下降沿均触发 */
ret = request_irq(irq_num, gpio_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"gpio_test_irq", GPIO_IRQ_DEV_ID);
if (ret < 0) {
printk(KERN_ERR "request_irq(%d) failed: %d\n", irq_num, ret);
gpio_free(GPIO_TEST_NUM);
return ret;
}
printk(KERN_INFO "IRQ %d registered, waiting for trigger...\n",
irq_num);
return 0;
}
中断类型标志参考:
标志 |
说明 |
|---|---|
|
上升沿触发 |
|
下降沿触发 |
|
高电平触发 |
|
低电平触发 |
|
共享中断 |
7.3.2.4. 模块卸载(资源释放)¶
rmmod 卸载模块时执行,依次释放中断和 GPIO 资源。
static void __exit gpio_drv_exit(void)
{
int irq_num = gpio_to_irq(GPIO_TEST_NUM);
/* 释放中断 */
free_irq(irq_num, GPIO_IRQ_DEV_ID);
/* 释放 GPIO */
gpio_free(GPIO_TEST_NUM);
printk(KERN_INFO "GPIO test driver unloaded\n");
}
7.3.2.5. 模块声明¶
module_init(gpio_drv_init);
module_exit(gpio_drv_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Kernel GPIO read/write and interrupt example");
MODULE_AUTHOR("Test");
注解
使用
insmod gpio_test.ko加载模块后,通过dmesg查看 GPIO 读写及中断注册日志。外部对该 GPIO 引脚施加电平变化即可在
dmesg中看到中断触发日志。使用
rmmod gpio_test卸载模块,资源自动释放。
7.3.3. 用户态GPIO操作示例¶
以下为完整的用户态 GPIO 操作示例程序,通过 sysfs 接口实现 GPIO 的导出、方向配置、
电平读写及释放。示例以 GPIO1_2(编号 448 + 2 = 450)为例,实际使用时请根据
GPIO 组号对应表修改 GPIO_NUM 宏定义。
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5
6#define GPIO_NUM 450 /* 示例 GPIO:GPIO1_2 = 448 + 2 */
7
8/* 导出 GPIO,在 /sys/class/gpio/ 下创建 gpioN 节点 */
9int gpio_export(int gpio)
10{
11 FILE *fp = fopen("/sys/class/gpio/export", "w");
12 if (fp == NULL) {
13 perror("open export failed");
14 return -1;
15 }
16 fprintf(fp, "%d", gpio);
17 fclose(fp);
18 /* 等待 sysfs 节点创建完成,避免后续访问 direction/value 失败 */
19 usleep(100000);
20 return 0;
21}
22
23/* 释放 GPIO,删除 /sys/class/gpio/gpioN 节点 */
24int gpio_unexport(int gpio)
25{
26 FILE *fp = fopen("/sys/class/gpio/unexport", "w");
27 if (fp == NULL) {
28 perror("open unexport failed");
29 return -1;
30 }
31 fprintf(fp, "%d", gpio);
32 fclose(fp);
33 return 0;
34}
35
36/* 设置 GPIO 方向:dir 取值为 "in" 或 "out" */
37int gpio_set_direction(int gpio, const char *dir)
38{
39 char path[64];
40 snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/direction", gpio);
41
42 FILE *fp = fopen(path, "w");
43 if (fp == NULL) {
44 perror("open direction failed");
45 return -1;
46 }
47 fprintf(fp, "%s", dir);
48 fclose(fp);
49 return 0;
50}
51
52/* 设置 GPIO 输出电平:value 为 0(低) 或 1(高) */
53int gpio_set_value(int gpio, int value)
54{
55 char path[64];
56 snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", gpio);
57
58 FILE *fp = fopen(path, "w");
59 if (fp == NULL) {
60 perror("open value for write failed");
61 return -1;
62 }
63 fprintf(fp, "%d", value);
64 fclose(fp);
65 return 0;
66}
67
68/* 读取 GPIO 输入电平,读取结果通过 value 返回 */
69int gpio_get_value(int gpio, int *value)
70{
71 char path[64];
72 snprintf(path, sizeof(path), "/sys/class/gpio/gpio%d/value", gpio);
73
74 FILE *fp = fopen(path, "r");
75 if (fp == NULL) {
76 perror("open value for read failed");
77 return -1;
78 }
79 /* sysfs value 文件通常返回字符 '0' 或 '1' */
80 char buf[2];
81 fread(buf, 1, 1, fp);
82 *value = atoi(buf);
83 fclose(fp);
84 return 0;
85}
86
87int main(void)
88{
89 int ret, value;
90
91 /* 步骤1:导出 GPIO */
92 ret = gpio_export(GPIO_NUM);
93 if (ret < 0)
94 exit(1);
95 printf("GPIO%d exported\n", GPIO_NUM);
96
97 /* 步骤2:设置为输出模式 */
98 ret = gpio_set_direction(GPIO_NUM, "out");
99 if (ret < 0) {
100 gpio_unexport(GPIO_NUM);
101 exit(1);
102 }
103 printf("GPIO%d set to output\n", GPIO_NUM);
104
105 /* 步骤3:输出高电平并保持 1 秒 */
106 ret = gpio_set_value(GPIO_NUM, 1);
107 if (ret < 0) {
108 gpio_unexport(GPIO_NUM);
109 exit(1);
110 }
111 printf("GPIO%d output HIGH\n", GPIO_NUM);
112 usleep(1000000);
113
114 /* 步骤4:切换为输入模式 */
115 ret = gpio_set_direction(GPIO_NUM, "in");
116 if (ret < 0) {
117 gpio_unexport(GPIO_NUM);
118 exit(1);
119 }
120 printf("GPIO%d set to input\n", GPIO_NUM);
121
122 /* 步骤5:读取输入电平 */
123 ret = gpio_get_value(GPIO_NUM, &value);
124 if (ret < 0) {
125 gpio_unexport(GPIO_NUM);
126 exit(1);
127 }
128 printf("GPIO%d input value: %d\n", GPIO_NUM, value);
129
130 /* 步骤6:释放 GPIO 资源 */
131 ret = gpio_unexport(GPIO_NUM);
132 if (ret < 0)
133 exit(1);
134 printf("GPIO%d unexported\n", GPIO_NUM);
135
136 return 0;
137}