Linux GPIO 使用 Pinctrl 及 Gpiolib 通俗详解

内核 GPIO、Pinctrl、Gpiolib 通俗详解(实操向)

本文针对内核驱动初学者,用「生活化比喻 + 可直接复用的代码」拆解核心逻辑 ------ 不用死记术语,跟着流程就能上手控制硬件(如 LED、按键)。

一、先搞懂:三者是啥关系?

把硬件引脚比作 "员工":

  • Pinctrl:"人事部门"------ 给员工分配岗位(是当 GPIO,还是当 I2C/UART 接口),避免一个人干多份活;

  • Gpiolib:"业务工具包"------ 员工被分配成 "GPIO 岗" 后,用这个工具包干活(输出电平、读信号、触发中断);

  • GPIO:最终的 "工作内容"------ 控制硬件的具体动作(亮 LED、检测按键)。

二、Pinctrl:给引脚 "分配岗位"(避坑关键!)

核心目标

让引脚明确 "自己该干啥"------ 比如指定 "第 0 组第 5 号引脚" 只当 GPIO,不抢 I2C 的活,否则后续操作全失败。

两步配置法(DTS + 驱动)

1. 第一步:在设备树(DTS)写 "岗位说明书"

提前告诉内核 "哪个引脚做什么",直接复用改参数即可:

复制代码
// 1. 定义"人事部门"(引脚控制器):管理某一片区的引脚

pinmux: pinmux@13000000 {

   reg = <0x13000000 0x1000>;  // 控制器硬件地址(看芯片手册)

   pinctrl-controller;         // 标记为"人事部门"

   #pinctrl-cells = <2>;       // 配置引脚需2个参数(组号+功能)

   // 2. 给具体引脚定岗位:第0组第5号 → 当GPIO

   gpio_func: gpio-func {

       pins = <0 5>;          // 引脚:第0组第5号(改这里换引脚)

       function = "gpio";     // 岗位:GPIO(当I2C写"i2c")

   };

};

// 3. 绑定到设备(如控制LED的设备)

led_device: led-device {

   compatible = "demo,led-gpio";  // 驱动匹配标识(和代码对应)

   pinctrl-names = "default";     // 状态名:默认用这个配置

   pinctrl-0 = <&gpio_func>;      // 绑定GPIO配置

   led-gpio = <&gpio0 5 GPIO_ACTIVE_HIGH>;  // GPIO属性(高电平亮)

   status = "okay";               // 启用配置(必须写)

};
2. 第二步:在驱动里 "激活岗位"

代码里告诉内核:"按 DTS 的规则,让引脚变成 GPIO":

复制代码
#include <linux/pinctrl/pinctrl.h>  // 必须包含的头文件

static int led_probe(struct platform_device *pdev) {

   // 1. 拿到"人事部门"联系方式(引脚控制器)

   struct pinctrl *pctrl = pinctrl_get(&pdev->dev, NULL);

   if (IS_ERR(pctrl)) {  // 判断是否拿到,避免报错

       return PTR_ERR(pctrl);

   }

   // 2. 找到DTS里的"GPIO岗位"

   struct pinctrl_state *pstate = pinctrl_lookup_state(pctrl, "gpio-func");

   if (IS_ERR(pstate)) {

       pinctrl_put(pctrl);  // 没找到就归还控制器

       return PTR_ERR(pstate);

   }

   // 3. 激活岗位:让引脚正式变GPIO

   if (pinctrl_select_state(pctrl, pstate)) {

       pinctrl_put(pctrl);

       return -EINVAL;

   }

   printk("Pinctrl配置完成,引脚已设为GPIO!\n");

   return 0;

}

三、Gpiolib:用 GPIO "干活"(核心操作)

当引脚被 Pinctrl 设为 GPIO 后,用 Gpiolib 的工具函数实现 "读 / 写 / 中断",全是现成接口,直接抄!

1. 先 "占坑":申请 GPIO(避免被抢)

像 "登记工具归属",用gpio_request函数:

复制代码
#include <linux/gpio.h>       // Gpiolib核心头文件

#include <linux/of_gpio.h>    // 从DTS读GPIO的头文件

static int led_gpio;  // 存储要操作的GPIO编号

static int led_probe(struct platform_device *pdev) {

   struct device_node *np = pdev->dev.of_node;  // 拿到DTS节点

   // 1. 从DTS读GPIO编号(对应"led-gpio"属性)

   led_gpio = of_get_named_gpio_flags(np, "led-gpio", 0, NULL);

   if (led_gpio < 0) {

       printk("从DTS读GPIO失败!\n");

       return led_gpio;

   }

   // 2. 申请GPIO:参数2是"用途说明"(调试用)

   if (gpio_request(led_gpio, "control_led")) {

       printk("GPIO被占用了!\n");

       return -EBUSY;

   }

   printk("GPIO申请成功,编号:%d\n", led_gpio);

   return 0;

}

2. 基础活:读 / 写电平(控制 LED / 读按键)

需求 函数 通俗解释 代码示例
设为输入(读) gpio_direction_input(gpio) 让 GPIO "听命令"(检测外部信号) gpio_direction_input(led_gpio);
读当前电平 gpio_get_value(gpio) 得到 0(低)或 1(高) int level = gpio_get_value(led_gpio);
设为输出(写) gpio_direction_output(gpio, val) 让 GPIO "发命令"(输出电平) gpio_direction_output(led_gpio, 0);(初始低)
改输出电平 gpio_set_value(gpio, val) 0 灭 / 1 亮(控制 LED) gpio_set_value(led_gpio, 1);(亮)

实操示例:控制 LED 亮灭

复制代码
// 在probe里加:设为输出+初始灭

gpio_direction_output(led_gpio, 0);

// 亮LED(可在其他函数调用)

void led_on(void) {

   gpio_set_value(led_gpio, 1);

   printk("LED亮了!\n");

}

// 灭LED

void led_off(void) {

   gpio_set_value(led_gpio, 0);

   printk("LED灭了!\n");

}

3. 进阶活:GPIO 触发中断(按键检测)

"按键按下时内核立刻反应",核心是 "GPIO 转中断号 + 注册处理函数":

复制代码
#include <linux/interrupt.h>  // 中断相关头文件

static int irq_num;  // 存储GPIO对应的中断号

// 3.1 中断处理函数:按键按下时自动执行

static irqreturn_t key_irq_handler(int irq, void *dev_id) {

   // 读按键电平:0=按下,1=松开(按硬件调整)

   int level = gpio_get_value(led_gpio);

   if (level == 0) {

       printk("按键按下了!\n");

       led_on();  // 按下亮LED(自定义逻辑)

   } else {

       printk("按键松开了!\n");

       led_off(); // 松开灭LED

   }

   return IRQ_HANDLED;  // 告诉内核:中断已处理

}

// 3.2 在probe里注册中断

static int led_probe(struct platform_device *pdev) {

   // ... 前面的Pinctrl配置、GPIO申请 ...

   // 1. GPIO转中断号:每个GPIO对应一个中断号

   irq_num = gpio_to_irq(led_gpio);

   if (irq_num < 0) {

       gpio_free(led_gpio);  // 失败就释放GPIO

       return irq_num;

   }

   // 2. 注册中断处理函数

   if (request_irq(

       irq_num,                  // 中断号

       key_irq_handler,          // 处理函数

       IRQF_TRIGGER_FALLING | IRQF_ONESHOT,  // 下降沿+单次触发(避中断风暴)

       "key_gpio_irq",           // 中断名称(调试用)

       pdev                      // 传给处理函数的参数

   )) {

       gpio_free(led_gpio);

       printk("中断注册失败!\n");

       return -EBUSY;

   }

   return 0;

}

4. 收尾:释放资源(不用了要还)

驱动卸载时,归还资源(写在remove函数里):

复制代码
static int led_remove(struct platform_device *pdev) {

   struct pinctrl *pctrl = pinctrl_get(&pdev->dev, NULL);

   // 1. 释放中断

   free_irq(irq_num, pdev);

   // 2. 释放GPIO

   gpio_free(led_gpio);

   // 3. 释放Pinctrl控制器

   pinctrl_put(pctrl);

   printk("资源已释放!\n");

   return 0;

}

四、完整流程:控制 LED 的全步骤

  1. DTS 配置:定义引脚控制器→指定引脚为 GPIO→绑定到 LED 设备;

  2. 驱动初始化

  • 激活 Pinctrl(让引脚变 GPIO);

  • 申请 GPIO + 设为输出;

  • 注册中断(若需按键控制);

  1. 业务逻辑 :调用gpio_set_value亮灭 LED,中断触发时执行处理函数;

  2. 卸载驱动:释放中断→GPIO→Pinctrl 资源。

五、避坑指南(新手必看)

  1. Pinctrl 必须先配:没给引脚分配 "GPIO 岗" 就用 Gpiolib,100% 失败!先跑通 Pinctrl 再往下走;

  2. 中断触发方式别乱选

  • 按键用「边沿触发」(IRQF_TRIGGER_FALLING/RISING),避免一直触发;

  • 传感器(如温度检测)用「电平触发」(IRQF_TRIGGER_HIGH/LOW),加IRQF_ONESHOT避 "中断风暴";

  1. 调试小技巧(不用写驱动)

    内核支持用文件系统直接操作 GPIO,适合快速测硬件:

    1. 导出GPIO(操作5号GPIO)

    echo 5 > /sys/class/gpio/export

    2. 设为输出

    echo out > /sys/class/gpio/gpio5/direction

    3. 亮LED(输出高电平)

    echo 1 > /sys/class/gpio/gpio5/value

    4. 灭LED(输出低电平)

    echo 0 > /sys/class/gpio/gpio5/value

    5. 用完删除

    echo 5 > /sys/class/gpio/unexport

总结

GPIO 核心逻辑 8 个字:「定功能→做操作→还资源」

  • 定功能:Pinctrl 给引脚分配 GPIO 岗位;(部分不需要

  • 做操作:Gpiolib 读 / 写 / 中断;(实际上,很多默认为 GPIO ,直接进行此步就可使用

  • 还资源:卸载时释放,不占坑。

实操中若遇 "GPIO 申请失败""中断不触发",先查 Pinctrl 配置和硬件接线

相关推荐
tokepson1 天前
Mysql下载部署方法备份(Windows/Linux)
linux·服务器·windows·mysql
zz_nj1 天前
工作的环境
linux·运维·服务器
极客先躯1 天前
如何自动提取Git指定时间段的修改文件?Win/Linux双平台解决方案
linux·git·elasticsearch
suijishengchengde1 天前
****LINUX时间同步配置*****
linux·运维
qiuqyue1 天前
基于虹软Linux Pro SDK的多路RTSP流并发接入、解码与帧级处理实践
linux·运维·网络
切糕师学AI1 天前
Linux 操作系统简介
linux
南烟斋..1 天前
GDB调试核心指南
linux·服务器
爱跑马的程序员1 天前
Linux 如何查看文件夹的大小(du、df、ls、find)
linux·运维·ubuntu
oMcLin1 天前
如何在 Ubuntu 22.04 LTS 上部署并优化 Magento 电商平台,提升高并发请求的响应速度与稳定性?
linux·运维·ubuntu
Qinti_mm1 天前
Linux io_uring:高性能异步I/O革命
linux·i/o·io_uring