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 配置和硬件接线

相关推荐
骄傲的心别枯萎3 小时前
RV1126 NO.16:通过多线程同时获取H264和H265码流
linux·c++·音视频·rv1126
空灵之海3 小时前
Ubuntu系统安全合规配置
linux·ubuntu·系统安全·1024程序员节
喜欢你,还有大家3 小时前
FTP文件传输服务
linux·运维·服务器·前端
czhc11400756634 小时前
LINUX99 centos8:网络 yum配置;shell:while [ $i -ne 5 ];do let i++ done
linux
会开花的二叉树6 小时前
彻底搞懂 Linux 基础 IO:从文件操作到缓冲区,打通底层逻辑
linux·服务器·c++·后端
呼啦啦5616 小时前
【Linux】权限
linux·权限
晨曦5432106 小时前
零基础12周精通Linux学习计划
linux
linux修理工7 小时前
n1 Armbian OS 24.11.0 noble 安装suricata
linux·运维·服务器
傅里叶7 小时前
sudo启动Flutter程序AMD初始化失败
linux·flutter