搭建一个和linux开发者知识共享和学习的平台,QQ群 925939366,欢迎加入
分类: 嵌入式
2025-11-19 16:58:54
GPIO是集成在微处理器或微控制器上的可编程引脚,其功能可以通过软件灵活配置。从硬件视角看,GPIO引脚通常有以下特性:
在Linux操作系统中,由于安全性和稳定性考虑,应用程序不能直接访问硬件寄存器,必须通过内核提供的GPIO子系统来访问这些引脚。这种设计既保护了硬件资源不被误操作,又为不同硬件平台提供了统一的编程接口。
Linux GPIO框架的设计遵循了内核驱动的通用设计原则,其核心设计目标可以概括为:
关键字段说明:
GPIO控制器驱动通过gpiochip_add_data()函数将gpio_chip注册到系统中,此后该控制器的GPIO引脚就可以被其他驱动或应用程序使用了。
在设备树中,消费者节点通过phandle引用GPIO控制器:
Pinctrl(引脚控制)子系统与GPIO子系统紧密协作,负责管理SoC引脚的多功能配置。 当一个引脚被配置为GPIO功能时,实际上经历了以下过程:
Sysfs GPIO接口是历史{BANNED}最佳悠久、使用{BANNED}最佳广泛的用户空间GPIO控制方法,它通过虚拟文件系统暴露GPIO操作接口。
在/sys/class/gpio/目录下,可以看到以下核心文件和目录:
字符设备接口的底层操作相对复杂,推荐使用libgpiod库来简化开发:
如果系统中还没有挂载debugfs,可以手动挂载:
输出显示了:
对于引脚复用问题,可以查看pinctrl子系统的状态:
许多嵌入式Linux发行版提供了gpio-utils工具包:
当遇到gpio_request失败时,可能的原因包括:
当GPIO电平与预期不符时:
GPIO中断无法触发时的诊断步骤:
消费者驱动通过gpiod_get()系列函数获取GPIO描述符,这个过程完成了GPIO资源的映射和预留,防止多个驱动同时使用同一个GPIO引脚。
GPIO中断的实现依赖于irq_domain机制,它在GPIO子系统与通用中断子系统之间建立了桥梁。 当调用gpiod_to_irq()时,GPIO控制器驱动通过irq_domain将GPIO偏移量转换为全局中断号。
通过pinctrl-0属性引用了引脚配置节点,确保在GPIO驱动使用引脚前,硬件状态已正确配置。
4.1.1 Sysfs GPIO目录结构
├── export # 写-only,用于导出GPIO到用户空间
├── unexport # 写-only,用于取消导出GPIO
├── gpiochip0/ # GPIO控制器0
├── gpiochip32/ # GPIO控制器32
└── gpioN/ # 已导出的GPIO引脚
├── direction # 读写,引脚方向:in/out
├── value # 读写,引脚电平:0/1
├── edge # 读写,中断触发:none/rising/falling/both
└── active_low # 读写,电平反转:0/1
对于输入和中断检测,操作也很直观:
# 导出并设置为输入
echo 12 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio12/direction
# 设置中断触发方式
echo rising > /sys/class/gpio/gpio12/edge
# 读取当前值
cat /sys/class/gpio/gpio12/value
使用libgpiod库
#include
#include
int main(void)
{
struct gpiod_chip *chip;
struct gpiod_line *led_line, *button_line;
int ret;
// 打开GPIO控制器
chip = gpiod_chip_open("/dev/gpiochip0");
if (!chip) {
perror("Open chip failed");
return -1;
}
// 获取GPIO线
led_line = gpiod_chip_get_line(chip, 5); // GPIO1_5
button_line = gpiod_chip_get_line(chip, 3); // GPIO1_3
if (!led_line || !button_line) {
perror("Get line failed");
gpiod_chip_close(chip);
return -1;
}
// 配置LED为输出,初始低电平
ret = gpiod_line_request_output(led_line, "led-example", 0);
if (ret < 0) {
perror("Request LED as output failed");
gpiod_chip_close(chip);
return -1;
}
// 配置按钮为输入
ret = gpiod_line_request_input(button_line, "button-example");
if (ret < 0) {
perror("Request button as input failed");
gpiod_line_release(led_line);
gpiod_chip_close(chip);
return -1;
}
// 主循环:按钮按下时点亮LED
while (1) {
int value = gpiod_line_get_value(button_line);
gpiod_line_set_value(led_line, value);
usleep(10000); // 10ms
}
// 清理资源
gpiod_line_release(led_line);
gpiod_line_release(button_line);
gpiod_chip_close(chip);
return 0;
}
DebugFS是内核提供的调试文件系统,可以展示GPIO子系统的内部状态信息。
mount -t debugfs none /sys/kernel/debug/
查看系统中所有GPIO控制器的状态:
cat /sys/kernel/debug/gpio
查看Pinctrl状态
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins
# 查看引脚配置
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinconf-pins
# 查看GPIO范围映射
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/gpio-ranges
gpio-utils包
gpiodetect
# 查看GPIO信息
gpioinfo
# 读取GPIO值
gpioget gpiochip0 1 2 3 # 读取gpiochip0的1,2,3引脚
# 设置GPIO值
gpioset gpiochip0 4=1 5=0 # 设置引脚4为高,引脚5为低
GPIO申请失败
电平异常问题
中断不触发
GPIO翻转速度测试
#include
#include
void gpio_speed_test(void)
{
struct gpiod_chip *chip;
struct gpiod_line *line;
struct timespec start, end;
int i;
chip = gpiod_chip_open("/dev/gpiochip0");
line = gpiod_chip_get_line(chip, 5);
gpiod_line_request_output(line, "speed-test", 0);
clock_gettime(CLOCK_MONOTONIC, &start);
for (i = 0; i < 1000; i++) {
gpiod_line_set_value(line, 1);
gpiod_line_set_value(line, 0);
}
clock_gettime(CLOCK_MONOTONIC, &end);
double duration = (end.tv_sec - start.tv_sec) * 1e9 +
(end.tv_nsec - start.tv_nsec);
printf("平均翻转周期: %.2f ns\n", duration / 2000.0);
gpiod_line_release(line);
gpiod_chip_close(chip);
}