GPIO 基础复习

一、GPIO 基础详解

GPIO(General Purpose Input/Output,通用输入输出)是嵌入式系统最基础的接口,引脚可通过软件灵活配置为输入输出模式,用于控制LED、读取按钮、驱动继电器、检测传感器状态等简单场景。


1. 核心功能
模式 功能 典型应用
输出模式 软件设置引脚电平(高/低) 控制LED、继电器、蜂鸣器
输入模式 软件读取引脚电平(高/低) 读取按钮、开关、传感器状态
中断模式 引脚电平变化时触发中断(驱动层常用) 检测按键按下、传感器报警

2. 电平逻辑
  • 高电平 :通常为 3.3V(部分硬件为 1.8V 或 5V,需查硬件手册),逻辑值为 1
  • 低电平 :0V(GND),逻辑值为 0
  • 注意 :GPIO 引脚通常有固定的电压域,不要接错电压(如 3.3V 引脚接 5V 会烧坏芯片)。

3. GPIO 编号规则

不同硬件的 GPIO 编号方式不同,需查对应硬件手册:

  • RK3588 :按 Bank 分组,每个 Bank 32 个引脚,分为 A/B/C/D 四组(每组 8 个)。
    例如:GPIO1_C6 → 编号 = 1*32 + 2*8 + 6 = 54(C 对应第 2 组,索引从 0 开始)。
  • 树莓派:直接使用物理引脚对应的 GPIO 编号(如物理引脚 12 对应 GPIO18)。

二、Linux GPIO 控制方式对比

Linux 下控制 GPIO 主要有两种方式,推荐使用 libgpiod(sysfs 已被内核标记为过时):

方式 原理 优点 缺点
sysfs 通过 /sys/class/gpio 文件系统操作 简单易懂,无需额外库 过时,不支持新特性,并发控制差
libgpiod 通过 /dev/gpiochipX 字符设备操作 现代、高效,支持所有新特性,并发控制好 需安装额外库

三、环境准备

1. 安装 libgpiod(推荐方式)

Ubuntu/Debian 系统直接安装:

bash 复制代码
sudo apt update
sudo apt install libgpiod-dev gpiod -y

验证安装:

bash 复制代码
gpiodetect  # 列出所有 GPIO 控制器(gpiochip0、gpiochip1 等)
gpioinfo    # 查看所有 GPIO 引脚状态
2. 硬件准备(可选,用于验证)
  • LED:正极串联 220Ω 电阻后接 GPIO 引脚,负极接 GND。
  • 按钮:一端接 GPIO 引脚,另一端接 GND(输入模式需配置上拉)。

四、应用层代码实现(一步一步)

我们以 控制 LED 闪烁 + 读取按钮状态 为例,分别实现 sysfs 方式 (简单入门)和 libgpiod 方式(推荐生产用)。


方式 1:sysfs 方式(简单入门)

通过操作 /sys/class/gpio 下的文件控制 GPIO,步骤清晰但已过时。

代码实现

保存为 gpio_sysfs.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

// 配置参数(需根据硬件修改)
#define LED_GPIO 54    // LED 连接的 GPIO 编号(RK3588 GPIO1_C6)
#define BUTTON_GPIO 55 // 按钮连接的 GPIO 编号(RK3588 GPIO1_C7)
#define BUFFER_SIZE 64

// 步骤 1:导出 GPIO(让内核暴露 GPIO 到 sysfs)
int gpio_export(int gpio) {
    int fd = open("/sys/class/gpio/export", O_WRONLY);
    if (fd < 0) {
        perror("Failed to open export");
        return -1;
    }
    char buf[BUFFER_SIZE];
    snprintf(buf, BUFFER_SIZE, "%d", gpio);
    if (write(fd, buf, strlen(buf)) < 0) {
        perror("Failed to export GPIO");
        close(fd);
        return -1;
    }
    close(fd);
    usleep(100000); // 等待 100ms,让内核完成导出
    return 0;
}

// 步骤 2:取消导出 GPIO(释放资源)
int gpio_unexport(int gpio) {
    int fd = open("/sys/class/gpio/unexport", O_WRONLY);
    if (fd < 0) {
        perror("Failed to open unexport");
        return -1;
    }
    char buf[BUFFER_SIZE];
    snprintf(buf, BUFFER_SIZE, "%d", gpio);
    if (write(fd, buf, strlen(buf)) < 0) {
        perror("Failed to unexport GPIO");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

// 步骤 3:配置 GPIO 方向(in=输入,out=输出)
int gpio_set_direction(int gpio, const char *direction) {
    char path[BUFFER_SIZE];
    snprintf(path, BUFFER_SIZE, "/sys/class/gpio/gpio%d/direction", gpio);
    int fd = open(path, O_WRONLY);
    if (fd < 0) {
        perror("Failed to open direction");
        return -1;
    }
    if (write(fd, direction, strlen(direction)) < 0) {
        perror("Failed to set direction");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

// 步骤 4:设置 GPIO 输出电平(0=低,1=高)
int gpio_set_value(int gpio, int value) {
    char path[BUFFER_SIZE];
    snprintf(path, BUFFER_SIZE, "/sys/class/gpio/gpio%d/value", gpio);
    int fd = open(path, O_WRONLY);
    if (fd < 0) {
        perror("Failed to open value");
        return -1;
    }
    char buf[2] = {value ? '1' : '0', '\0'};
    if (write(fd, buf, 1) < 0) {
        perror("Failed to set value");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

// 步骤 5:读取 GPIO 输入电平(返回 0=低,1=高)
int gpio_get_value(int gpio) {
    char path[BUFFER_SIZE];
    snprintf(path, BUFFER_SIZE, "/sys/class/gpio/gpio%d/value", gpio);
    int fd = open(path, O_RDONLY);
    if (fd < 0) {
        perror("Failed to open value");
        return -1;
    }
    char buf[2];
    if (read(fd, buf, 1) < 0) {
        perror("Failed to get value");
        close(fd);
        return -1;
    }
    close(fd);
    return (buf[0] == '1') ? 1 : 0;
}

int main() {
    // 1. 导出 LED 和按钮 GPIO
    printf("Exporting GPIOs...\n");
    if (gpio_export(LED_GPIO) < 0 || gpio_export(BUTTON_GPIO) < 0) {
        goto cleanup;
    }

    // 2. 配置 LED 为输出,按钮为输入(上拉需硬件或额外配置)
    printf("Configuring GPIOs...\n");
    if (gpio_set_direction(LED_GPIO, "out") < 0 || 
        gpio_set_direction(BUTTON_GPIO, "in") < 0) {
        goto cleanup;
    }

    // 3. 循环:LED 闪烁,读取按钮状态
    printf("Starting loop (Ctrl+C to exit)...\n");
    int led_state = 0;
    while (1) {
        // 切换 LED 状态
        led_state = !led_state;
        gpio_set_value(LED_GPIO, led_state);
        printf("LED: %s\n", led_state ? "ON" : "OFF");

        // 读取按钮状态
        int button_state = gpio_get_value(BUTTON_GPIO);
        printf("Button: %s\n", button_state ? "RELEASED" : "PRESSED"); // 按钮接 GND,按下为低

        // 等待 500ms
        usleep(500000);
    }

cleanup:
    // 4. 取消导出 GPIO(释放资源)
    printf("Unexporting GPIOs...\n");
    gpio_unexport(LED_GPIO);
    gpio_unexport(BUTTON_GPIO);
    return EXIT_FAILURE;
}

方式 2:libgpiod 方式(推荐生产用)

通过 /dev/gpiochipX 字符设备操作,API 简洁、高效,支持并发控制。

代码实现

保存为 gpio_libgpiod.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <gpiod.h>

// 配置参数(需根据硬件修改)
#define GPIO_CHIP "gpiochip0" // GPIO 控制器名称(用 gpiodetect 查看)
#define LED_GPIO 54            // LED 连接的 GPIO 编号
#define BUTTON_GPIO 55         // 按钮连接的 GPIO 编号
#define CONSUMER "gpio_test"   // 消费者名称(用于标识)

int main() {
    struct gpiod_chip *chip;
    struct gpiod_line *led_line, *button_line;
    int ret;

    // 步骤 1:打开 GPIO 控制器
    printf("Opening GPIO chip %s...\n", GPIO_CHIP);
    chip = gpiod_chip_open_by_name(GPIO_CHIP);
    if (!chip) {
        perror("Failed to open GPIO chip");
        return EXIT_FAILURE;
    }

    // 步骤 2:获取 LED 和按钮的 GPIO line
    printf("Getting GPIO lines...\n");
    led_line = gpiod_chip_get_line(chip, LED_GPIO);
    button_line = gpiod_chip_get_line(chip, BUTTON_GPIO);
    if (!led_line || !button_line) {
        perror("Failed to get GPIO line");
        ret = EXIT_FAILURE;
        goto cleanup_chip;
    }

    // 步骤 3:配置 LED 为输出(初始低电平),按钮为输入(上拉)
    printf("Configuring GPIO lines...\n");
    // 配置 LED:输出,初始低电平
    ret = gpiod_line_request_output(led_line, CONSUMER, 0);
    if (ret < 0) {
        perror("Failed to request LED line as output");
        goto cleanup_lines;
    }
    // 配置按钮:输入,上拉(GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
    ret = gpiod_line_request_input_flags(button_line, CONSUMER, 
                                          GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
    if (ret < 0) {
        perror("Failed to request button line as input");
        goto cleanup_lines;
    }

    // 步骤 4:循环:LED 闪烁,读取按钮状态
    printf("Starting loop (Ctrl+C to exit)...\n");
    int led_state = 0;
    while (1) {
        // 切换 LED 状态
        led_state = !led_state;
        ret = gpiod_line_set_value(led_line, led_state);
        if (ret < 0) {
            perror("Failed to set LED value");
            break;
        }
        printf("LED: %s\n", led_state ? "ON" : "OFF");

        // 读取按钮状态
        int button_state = gpiod_line_get_value(button_line);
        if (button_state < 0) {
            perror("Failed to get button value");
            break;
        }
        printf("Button: %s\n", button_state ? "RELEASED" : "PRESSED"); // 上拉时,按下为低

        // 等待 500ms
        usleep(500000);
    }

cleanup_lines:
    // 步骤 5:释放 GPIO line
    gpiod_line_release(led_line);
    gpiod_line_release(button_line);
cleanup_chip:
    // 步骤 6:关闭 GPIO 控制器
    gpiod_chip_close(chip);
    return ret < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

五、编译与运行

1. 编译 sysfs 方式
bash 复制代码
gcc gpio_sysfs.c -o gpio_sysfs
2. 编译 libgpiod 方式

需链接 libgpiod 库:

bash 复制代码
gcc gpio_libgpiod.c -o gpio_libgpiod -lgpiod
3. 运行(需 root 权限)
bash 复制代码
# sysfs 方式
sudo ./gpio_sysfs

# libgpiod 方式
sudo ./gpio_libgpiod

六、调试方法

  1. 查看 GPIO 控制器

    bash 复制代码
    gpiodetect

    输出示例:

    复制代码
    gpiochip0 [30030000.gpio] (32 lines)
    gpiochip1 [30040000.gpio] (32 lines)
  2. 查看 GPIO 引脚状态

    bash 复制代码
    gpioinfo gpiochip1  # 查看 gpiochip1 的所有引脚
  3. 手动测试 GPIO

    bash 复制代码
    # 设置 LED 为高电平(libgpiod 工具)
    sudo gpioset gpiochip1 22=1  # 假设 LED 在 gpiochip1 的 22 号引脚
    
    # 读取按钮状态
    sudo gpioget gpiochip1 23  # 假设按钮在 gpiochip1 的 23 号引脚

七、注意事项

  1. 权限 :操作 GPIO 需要 root 权限(使用 sudo)。
  2. 引脚编号:务必查硬件手册,不要用错引脚(否则可能烧坏芯片)。
  3. 电压域:GPIO 通常为 3.3V,不要接 5V 或更高电压。
  4. 上拉/下拉 :输入模式时,建议配置上拉或下拉(libgpiod 支持通过 flags 配置),避免引脚悬空导致电平不稳定。
  5. 并发控制:多个程序不要同时操作同一个 GPIO,libgpiod 会自动处理并发冲突(请求失败时返回错误)。

针对 RK3588 的 GPIO 驱动层实现,我们将从 硬件架构Linux GPIO 驱动框架RK3588 GPIO 控制器驱动源码分析设备树配置实际调试 进行深度讲解,结构与此前 SPI/I2C/CAN 驱动讲解保持一致,便于对比理解。

八、RK3588 GPIO 硬件基础

RK3588 集成了 5 个独立的 GPIO 控制器 (GPIO0 ~ GPIO4),每个控制器对应一个 Bank,支持以下核心特性:

特性 说明
引脚数量 每个 Bank 最多 32 个引脚,分为 A/B/C/D 四组(每组 8 个),总引脚数约 150+
电压域 支持 3.3V、1.8V 等多种电压(需查硬件手册确认具体引脚的电压域)
功能复用 每个引脚可配置为 GPIO 或其他功能(如 SPI、I2C、UART、GMAC 等),通过 pinctrl 管理
输入/输出模式 支持推挽输出、开漏输出、上拉/下拉输入
中断支持 每个 GPIO 引脚可配置为中断源(上升沿、下降沿、双边沿触发)
唤醒支持 部分 GPIO 引脚可配置为唤醒源,用于唤醒休眠的系统

GPIO 编号规则(与应用层对应)

RK3588 的 GPIO 编号按 Bank + 组 + 引脚 计算,公式为:

复制代码
GPIO编号 = Bank号 * 32 + 组号 * 8 + 引脚号

其中:

  • Bank 号:0 ~ 4(对应 GPIO0 ~ GPIO4)
  • 组号:A=0, B=1, C=2, D=3
  • 引脚号:0 ~ 7

示例

  • GPIO1_C6 → 编号 = 1*32 + 2*8 + 6 = 54
  • GPIO3_A0 → 编号 = 3*32 + 0*8 + 0 = 96

九、Linux GPIO 驱动整体架构

Linux GPIO 驱动分为 两层,从上到下依次为:

  1. GPIO 核心层(gpiolib)
    • 内核提供的通用框架,负责管理所有 GPIO 控制器,提供统一的 API 给驱动层和应用层(如 libgpiod)。
    • 源码路径:drivers/gpio/gpiolib.c
  2. GPIO 控制器驱动
    • 厂商针对具体硬件(如 RK3588)写的驱动,负责直接操作 GPIO 硬件寄存器,实现 gpiolib 要求的接口。
    • RK3588 对应源码:drivers/pinctrl/pinctrl-rockchip.c(GPIO 和 pinctrl 复用驱动,因为 GPIO 引脚通常需要配置复用功能)。

十、RK3588 GPIO 控制器驱动深度分析

我们以 Linux 5.10 内核源码为例,分析核心实现(GPIO 功能集成在 pinctrl-rockchip.c 中)。


1. 关键数据结构

c 复制代码
// drivers/pinctrl/pinctrl-rockchip.c
struct rockchip_gpio_chip {
    struct gpio_chip gpio_chip;  // gpiolib 核心结构体(必须放在最前面)
    struct rockchip_pin_bank *bank;  // 指向对应的 GPIO Bank
    void __iomem *reg_base;       // GPIO 寄存器基地址
    struct clk *clk;               // GPIO 时钟
    int irq;                       // GPIO 控制器的父中断号
    unsigned int irq_base;         // GPIO 引脚的中断基号
    // ... 其他成员
};

struct rockchip_pin_bank {
    int bank_num;                  // Bank 号(0 ~ 4)
    const char *name;              // Bank 名称(如 "gpio0")
    unsigned int nr_pins;          // 该 Bank 的引脚数量(通常 32)
    unsigned int pin_base;         // 该 Bank 的起始引脚编号
    // ... 其他成员
};

其中 struct gpio_chip 是 gpiolib 定义的核心结构体,包含 GPIO 控制器的所有信息和回调函数。


2. 驱动初始化与 probe 函数

GPIO 驱动集成在 pinctrl 驱动中,通过 platform_driver 框架注册,与设备树匹配后执行 probe

c 复制代码
static const struct of_device_id rockchip_pinctrl_of_match[] = {
    { .compatible = "rockchip,rk3588-pinctrl", .data = &rk3588_pinctrl_data },
    // ... 其他芯片兼容项
};
MODULE_DEVICE_TABLE(of, rockchip_pinctrl_of_match);

static struct platform_driver rockchip_pinctrl_driver = {
    .probe = rockchip_pinctrl_probe,
    .remove = rockchip_pinctrl_remove,
    .driver = {
        .name = "rockchip-pinctrl",
        .of_match_table = rockchip_pinctrl_of_match,
    },
};
module_platform_driver(rockchip_pinctrl_driver);
rockchip_pinctrl_probe 中 GPIO 相关的核心流程:
  1. 遍历所有 GPIO Bank

    c 复制代码
    for (i = 0; i < info->nr_banks; i++) {
        struct rockchip_pin_bank *bank = &info->banks[i];
        struct rockchip_gpio_chip *gpio_chip;
    
        // 2. 分配 rockchip_gpio_chip 结构体
        gpio_chip = devm_kzalloc(dev, sizeof(*gpio_chip), GFP_KERNEL);
        gpio_chip->bank = bank;
    
        // 3. 获取硬件资源
        // 寄存器地址映射
        gpio_chip->reg_base = devm_ioremap_resource(dev, &res);
        // 时钟获取与使能
        gpio_chip->clk = devm_clk_get(dev, bank->name);
        clk_prepare_enable(gpio_chip->clk);
    
        // 4. 初始化 gpio_chip 结构体(关键!)
        gpio_chip->gpio_chip.label = bank->name;
        gpio_chip->gpio_chip.parent = dev;
        gpio_chip->gpio_chip.owner = THIS_MODULE;
        gpio_chip->gpio_chip.base = bank->pin_base;  // 起始引脚编号
        gpio_chip->gpio_chip.ngpio = bank->nr_pins;  // 引脚数量
        gpio_chip->gpio_chip.direction_input = rockchip_gpio_direction_input;  // 输入方向回调
        gpio_chip->gpio_chip.direction_output = rockchip_gpio_direction_output;  // 输出方向回调
        gpio_chip->gpio_chip.get = rockchip_gpio_get;  // 读取电平回调
        gpio_chip->gpio_chip.set = rockchip_gpio_set;  // 设置电平回调
        gpio_chip->gpio_chip.set_multiple = rockchip_gpio_set_multiple;  // 批量设置电平回调
        gpio_chip->gpio_chip.of_node = bank->of_node;  // 设备树节点
    
        // 5. 初始化 GPIO 硬件
        rockchip_gpio_hw_init(gpio_chip);
    
        // 6. 注册 GPIO 控制器到 gpiolib
        devm_gpiochip_add_data(dev, &gpio_chip->gpio_chip, gpio_chip);
    
        // 7. 初始化 GPIO 中断(可选,用于 GPIO 中断功能)
        rockchip_gpio_irq_init(gpio_chip);
    }

3. 核心回调函数实现

这些函数是 gpiolib 要求的,驱动层必须实现,直接操作 RK3588 的 GPIO 硬件寄存器。

(1)配置为输入模式:rockchip_gpio_direction_input
c 复制代码
static int rockchip_gpio_direction_input(struct gpio_chip *chip, unsigned offset) {
    struct rockchip_gpio_chip *gpio_chip = gpiochip_get_data(chip);
    unsigned long flags;
    u32 data;

    spin_lock_irqsave(&gpio_chip->lock, flags);

    // 读取 GPIO 方向寄存器
    data = readl(gpio_chip->reg_base + GPIO_SWPORT_DDR);
    // 配置对应引脚为输入(0=输入,1=输出)
    data &= ~BIT(offset);
    // 写回方向寄存器
    writel(data, gpio_chip->reg_base + GPIO_SWPORT_DDR);

    spin_unlock_irqrestore(&gpio_chip->lock, flags);
    return 0;
}
(2)配置为输出模式并设置初始电平:rockchip_gpio_direction_output
c 复制代码
static int rockchip_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) {
    struct rockchip_gpio_chip *gpio_chip = gpiochip_get_data(chip);
    unsigned long flags;
    u32 data;

    spin_lock_irqsave(&gpio_chip->lock, flags);

    // 1. 先设置初始电平
    data = readl(gpio_chip->reg_base + GPIO_SWPORT_DR);
    if (value)
        data |= BIT(offset);  // 高电平
    else
        data &= ~BIT(offset); // 低电平
    writel(data, gpio_chip->reg_base + GPIO_SWPORT_DR);

    // 2. 再配置为输出模式
    data = readl(gpio_chip->reg_base + GPIO_SWPORT_DDR);
    data |= BIT(offset);  // 1=输出
    writel(data, gpio_chip->reg_base + GPIO_SWPORT_DDR);

    spin_unlock_irqrestore(&gpio_chip->lock, flags);
    return 0;
}
(3)读取引脚电平:rockchip_gpio_get
c 复制代码
static int rockchip_gpio_get(struct gpio_chip *chip, unsigned offset) {
    struct rockchip_gpio_chip *gpio_chip = gpiochip_get_data(chip);
    u32 data;

    // 读取 GPIO 输入电平寄存器
    data = readl(gpio_chip->reg_base + GPIO_EXT_PORT);
    // 返回对应引脚的电平(0=低,1=高)
    return !!(data & BIT(offset));
}
(4)设置引脚电平:rockchip_gpio_set
c 复制代码
static void rockchip_gpio_set(struct gpio_chip *chip, unsigned offset, int value) {
    struct rockchip_gpio_chip *gpio_chip = gpiochip_get_data(chip);
    unsigned long flags;
    u32 data;

    spin_lock_irqsave(&gpio_chip->lock, flags);

    // 读取 GPIO 输出电平寄存器
    data = readl(gpio_chip->reg_base + GPIO_SWPORT_DR);
    if (value)
        data |= BIT(offset);  // 高电平
    else
        data &= ~BIT(offset); // 低电平
    // 写回输出电平寄存器
    writel(data, gpio_chip->reg_base + GPIO_SWPORT_DR);

    spin_unlock_irqrestore(&gpio_chip->lock, flags);
}

4. GPIO 与 pinctrl 的结合(引脚复用)

RK3588 的 GPIO 引脚通常有多种功能(如 GPIO、SPI、I2C 等),需要通过 pinctrl 配置为 GPIO 模式才能使用。pinctrl 驱动也集成在 pinctrl-rockchip.c 中,核心流程:

  1. 设备树中定义引脚复用组(如 gpio1_c6 配置为 GPIO 模式)。
  2. 驱动层通过 devm_pinctrl_get_select_default() 自动应用引脚复用配置。

十一、RK3588 GPIO 设备树配置

GPIO 控制器节点定义在 arch/arm64/boot/dts/rockchip/rk3588.dtsi 中,用户需在板级 DTS(如 rk3588-evb.dts)中配置具体引脚。


1. GPIO 控制器节点示例(rk3588.dtsi 片段)

dts 复制代码
pinctrl: pinctrl {
    compatible = "rockchip,rk3588-pinctrl";
    reg = <0x0 0xfd400000 0x0 0x100000>;  // pinctrl 寄存器基地址
    rockchip,grf = <&grf>;  // 通用寄存器文件(GRF)
    // ... 其他 pinctrl 配置

    // GPIO1 控制器节点
    gpio1: gpio@fd410000 {
        compatible = "rockchip,gpio-bank";
        reg = <0x0 0xfd410000 0x0 0x100>;  // GPIO1 寄存器基地址
        interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;  // GPIO1 父中断号
        clocks = <&cru PCLK_GPIO1>;  // GPIO1 时钟
        gpio-controller;  // 标识为 GPIO 控制器
        #gpio-cells = <2>;  // GPIO 引用格式:<&gpio1 引脚号 标志>
        interrupt-controller;  // 标识为中断控制器
        #interrupt-cells = <2>;  // 中断引用格式:<&gpio1 引脚号 触发方式>
    };
    // ... 其他 GPIO 控制器节点(gpio0、gpio2、gpio3、gpio4)
};

2. 板级 DTS 配置(LED + 按钮示例)

dts 复制代码
&pinctrl {
    // 定义 LED 引脚复用组(GPIO1_C6 配置为 GPIO 模式)
    led_pins: led-pins {
        m0 {
            rockchip,pins = <1 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
            // 格式:<Bank号 引脚 功能 配置>
            // RK_FUNC_GPIO=GPIO 模式,pcfg_pull_none=无上下拉
        };
    };

    // 定义按钮引脚复用组(GPIO1_C7 配置为 GPIO 模式,上拉)
    button_pins: button-pins {
        m0 {
            rockchip,pins = <1 RK_PC7 RK_FUNC_GPIO &pcfg_pull_up>;
            // pcfg_pull_up=上拉
        };
    };
};

// LED 设备节点
led {
    compatible = "gpio-leds";
    pinctrl-names = "default";
    pinctrl-0 = <&led_pins>;  // 应用 LED 引脚复用配置

    user-led {
        label = "user:led";
        gpios = <&gpio1 RK_PC6 GPIO_ACTIVE_HIGH>;  // 引用 GPIO1_C6,高电平有效
        default-state = "off";  // 默认关闭
    };
};

// 按钮设备节点
button {
    compatible = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&button_pins>;  // 应用按钮引脚复用配置

    user-button {
        label = "user:button";
        gpios = <&gpio1 RK_PC7 GPIO_ACTIVE_LOW>;  // 引用 GPIO1_C7,低电平有效(上拉时按下为低)
        linux,code = <KEY_ENTER>;  // 按键码
        debounce-interval = <10>;  // 消抖时间(ms)
    };
};

十二、关键调试方法

1. 查看 GPIO 控制器和引脚状态

bash 复制代码
# 查看所有 GPIO 控制器(libgpiod 工具)
gpiodetect

# 查看所有 GPIO 引脚状态
gpioinfo

# 查看内核 GPIO 状态(debugfs)
sudo cat /sys/kernel/debug/gpio

/sys/kernel/debug/gpio 输出示例:

复制代码
gpiochip0: GPIOs 0-31, parent: platform/fd400000.pinctrl, gpio0:
 gpio-0   (                    |user:led           ) out hi
 gpio-1   (                    |user:button        ) in  hi

2. 手动测试 GPIO(libgpiod 工具)

bash 复制代码
# 设置 GPIO1_C6(编号 54)为高电平
sudo gpioset gpiochip1 22=1  # 假设 gpiochip1 的起始编号是 32,54-32=22

# 读取 GPIO1_C7(编号 55)的电平
sudo gpioget gpiochip1 23

3. 查看 GPIO 寄存器(devmem)

bash 复制代码
# 读取 GPIO1 的方向寄存器(假设基地址是 0xfd410000)
sudo devmem 0xfd410004  # GPIO_SWPORT_DDR 偏移 0x04

# 读取 GPIO1 的输入电平寄存器
sudo devmem 0xfd410050  # GPIO_EXT_PORT 偏移 0x50

十三、总结

RK3588 的 GPIO 驱动层核心在于:

  1. 集成在 pinctrl 驱动中:因为 GPIO 引脚通常需要配置复用功能,pinctrl 和 GPIO 驱动复用代码更高效。
  2. 实现 gpiolib 要求的回调函数direction_inputdirection_outputgetset 等,直接操作硬件寄存器。
  3. 设备树配置:定义引脚复用组,配置具体 GPIO 设备(如 LED、按钮)。
相关推荐
小年糕是糕手6 小时前
【35天从0开始备战蓝桥杯 -- Day6】
开发语言·前端·网络·数据库·c++·蓝桥杯
星轨初途6 小时前
【C/C++底层修炼】拆解动态内存管理:四大动态内存函数、六大错误与柔性数组
c语言·开发语言·c++·经验分享·笔记·柔性数组
闻缺陷则喜何志丹6 小时前
【计算几何】和差化积及积化和差
c++·数学·计算几何
Trouvaille ~6 小时前
【项目篇】从零手写高并发服务器(九):HTTP协议支持——从TCP到应用层
linux·服务器·c++·tcp/ip·http·高并发·应用层
小此方6 小时前
Re:从零开始的 C++ STL篇(八)深度解构AVL树自平衡机制:平衡维护与旋转调整背后的严密逻辑
开发语言·数据结构·c++·算法·stl
2301_789015626 小时前
封装哈希表实现unordered_set/undered_map
c语言·数据结构·c++·算法·哈希算法
落羽的落羽6 小时前
【Linux系统】中断机制、用户态与内核态、虚拟地址与页表的本质
java·linux·服务器·c++·人工智能·算法·机器学习
bksczm6 小时前
C++ iostream , sstream的基本理解
开发语言·c++