一、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
六、调试方法
-
查看 GPIO 控制器:
bashgpiodetect输出示例:
gpiochip0 [30030000.gpio] (32 lines) gpiochip1 [30040000.gpio] (32 lines) -
查看 GPIO 引脚状态:
bashgpioinfo gpiochip1 # 查看 gpiochip1 的所有引脚 -
手动测试 GPIO:
bash# 设置 LED 为高电平(libgpiod 工具) sudo gpioset gpiochip1 22=1 # 假设 LED 在 gpiochip1 的 22 号引脚 # 读取按钮状态 sudo gpioget gpiochip1 23 # 假设按钮在 gpiochip1 的 23 号引脚
七、注意事项
- 权限 :操作 GPIO 需要 root 权限(使用
sudo)。 - 引脚编号:务必查硬件手册,不要用错引脚(否则可能烧坏芯片)。
- 电压域:GPIO 通常为 3.3V,不要接 5V 或更高电压。
- 上拉/下拉 :输入模式时,建议配置上拉或下拉(libgpiod 支持通过
flags配置),避免引脚悬空导致电平不稳定。 - 并发控制:多个程序不要同时操作同一个 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 = 54GPIO3_A0→ 编号 =3*32 + 0*8 + 0 = 96
九、Linux GPIO 驱动整体架构
Linux GPIO 驱动分为 两层,从上到下依次为:
- GPIO 核心层(gpiolib) :
- 内核提供的通用框架,负责管理所有 GPIO 控制器,提供统一的 API 给驱动层和应用层(如 libgpiod)。
- 源码路径:
drivers/gpio/gpiolib.c。
- 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 相关的核心流程:
-
遍历所有 GPIO Bank :
cfor (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 中,核心流程:
- 设备树中定义引脚复用组(如
gpio1_c6配置为 GPIO 模式)。 - 驱动层通过
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 驱动层核心在于:
- 集成在 pinctrl 驱动中:因为 GPIO 引脚通常需要配置复用功能,pinctrl 和 GPIO 驱动复用代码更高效。
- 实现 gpiolib 要求的回调函数 :
direction_input、direction_output、get、set等,直接操作硬件寄存器。 - 设备树配置:定义引脚复用组,配置具体 GPIO 设备(如 LED、按钮)。