7. GPIO操作指南

7.1. 操作准备

  • 使用SDK发布的kernel内核

7.2. 操作流程

  • GPIO相关模块默认已编入内核,无需执行加载命令

  • 通过控制台执行GPIO读写命令,或在内核态/用户态编写GPIO操作程序即可

7.3. 操作示例

7.3.1. GPIO命令行操作示例

步骤1:导出GPIO控制权
echo N > /sys/class/gpio/export
  • N = 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;
}

中断类型标志参考:

标志

说明

IRQF_TRIGGER_RISING

上升沿触发

IRQF_TRIGGER_FALLING

下降沿触发

IRQF_TRIGGER_HIGH

高电平触发

IRQF_TRIGGER_LOW

低电平触发

IRQF_SHARED

共享中断

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}