4. GPIO与中断的硬件关联:GPIO引脚作为中断源的硬件原理,电平触发与边沿触发,以及设备树如何表达这种关联

在嵌入式系统中,GPIO(通用输入输出)引脚除了作为数字信号的输入输出端口外,最常见的扩展功能便是作为外部中断源。理解GPIO引脚如何产生中断、中断触发方式的硬件差异,以及设备树如何描述这种关联,是编写正确设备树绑定的核心基础。

4.1 GPIO引脚作为中断源的硬件原理

从硬件角度看,一个GPIO引脚要作为中断源,通常需要经过以下三个层次的信号路径:

  1. 物理引脚电平变化:外部设备(按键、传感器、外设)改变GPIO引脚上的电压电平(高→低 或 低→高)。
  2. GPIO控制器内部检测:GPIO控制器的输入缓冲器捕获电平变化,并与内部配置的触发条件(电平或边沿)进行比较。
  3. 中断控制器路由:匹配触发条件后,GPIO控制器向中断控制器(如GIC、NVIC)发送中断请求信号,CPU响应后执行中断服务程序(ISR)。
关键概念:GPIO中断并非由CPU直接轮询引脚电平,而是由硬件自动检测并触发。设备树的作用是告诉内核:哪个GPIO控制器、哪个引脚、以何种触发方式连接到哪个中断控制器。
GPIO引脚中断信号路径图 外部设备 按键 / 传感器 GPIO引脚 电平变化检测 GPIO控制器 触发条件匹配 中断控制器 GIC / NVIC 说明: 1. 外部设备改变GPIO引脚电平(上升沿/下降沿/高电平/低电平) 2. GPIO控制器内部比较器检测到电平变化,与配置的触发条件匹配 3. 匹配成功后,GPIO控制器向中断控制器发送中断请求信号

4.2 电平触发与边沿触发

中断触发方式决定了GPIO控制器在何种条件下产生中断请求。两种基本触发方式在硬件实现和应用场景上有本质区别:

触发方式 硬件检测原理 中断产生条件 典型应用 注意事项
电平触发 持续检测引脚电平状态 引脚电平保持为指定值(高或低)期间持续产生中断 唤醒信号、电源管理、紧急事件 必须在中断服务程序中清除中断源,否则会无限触发
边沿触发 检测电平跳变瞬间 引脚电平从低到高(上升沿)或从高到低(下降沿)时触发一次 按键输入、编码器、脉冲计数 不会重复触发,但可能丢失快速脉冲
双边沿触发 同时检测上升和下降跳变 任何电平变化都触发一次中断 旋转编码器、通信协议检测 中断频率是单边沿的两倍
避坑指南:电平触发中断在ISR中必须显式操作硬件寄存器清除中断源(如读取数据寄存器、写清除位),否则中断会立即再次触发,导致CPU陷入死循环。边沿触发则不会出现此问题,但需要确保脉冲宽度满足GPIO控制器的最小检测时间。
电平触发 vs 边沿触发波形对比 引脚电平 上升沿 下降沿 上升沿 下降沿 电平触发: 高电平期间持续产生中断 边沿触发: 仅在跳变瞬间产生一次中断 双边沿触发: 上升沿和下降沿都产生中断

4.3 设备树中GPIO中断关联的表达

设备树通过特定的属性和绑定规则,将GPIO引脚与中断控制器关联起来。核心是使用 interrupts 属性和 interrupt-parent 属性来描述中断资源。

4.3.1 基本属性

属性名 作用 格式 示例
interrupt-parent 指定该设备的中断控制器父节点 <phandle> interrupt-parent = &gpio1;
interrupts 描述中断号、触发类型等 <中断号 触发类型> interrupts = <5 IRQ_TYPE_EDGE_RISING>;
interrupt-cells 定义中断描述符的单元数(在中断控制器节点中声明) <数字> #interrupt-cells = <2>;
gpios 当GPIO同时作为中断源时,有时也通过此属性引用引脚 <phandle GPIO_SPEC> gpios = <&gpio2 3 GPIO_ACTIVE_LOW>;

4.3.2 触发类型宏定义

Linux内核在 include/dt-bindings/interrupt-controller/irq.h 中定义了标准的触发类型宏,设备树中应优先使用这些宏而非直接写数字:

/* 标准中断触发类型宏 */
#define IRQ_TYPE_NONE          0x00000000
#define IRQ_TYPE_EDGE_RISING   0x00000001  /* 上升沿触发 */
#define IRQ_TYPE_EDGE_FALLING  0x00000002  /* 下降沿触发 */
#define IRQ_TYPE_EDGE_BOTH     0x00000003  /* 双边沿触发 */
#define IRQ_TYPE_LEVEL_HIGH    0x00000004  /* 高电平触发 */
#define IRQ_TYPE_LEVEL_LOW     0x00000008  /* 低电平触发 */

4.3.3 完整设备树示例

以下是一个典型的GPIO按键中断设备树节点,展示了如何将GPIO引脚关联为中断源:

/* 在dts文件中包含头文件 */
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/* GPIO控制器节点(通常由SoC dtsi提供) */
gpio1: gpio@50002000 {
    compatible = "vendor,gpio-controller";
    reg = <0x50002000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;           /* 声明该节点也是中断控制器 */
    #interrupt-cells = <2>;        /* 中断描述符:<引脚号 触发类型> */
    interrupts = <0 31 IRQ_TYPE_LEVEL_HIGH>;  /* 连接到GIC */
    interrupt-parent = <&gic>;
};

/* 外部设备节点:按键使用GPIO中断 */
key_button {
    compatible = "gpio-keys";
    pinctrl-names = "default";
    pinctrl-0 = <&key_pins>;

    /* 方式一:通过interrupts直接关联GPIO中断控制器 */
    interrupt-parent = <&gpio1>;
    interrupts = <5 IRQ_TYPE_EDGE_FALLING>;  /* GPIO1_5,下降沿触发 */

    /* 方式二:同时指定gpios属性,便于驱动获取引脚状态 */
    gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
    linux,code = <KEY_ENTER>;
    label = "Enter Key";
};
最佳实践:当GPIO控制器同时支持 gpio-controller 和 interrupt-controller 时,建议在设备节点中同时提供 interrupts 和 gpios 属性。前者用于中断注册,后者用于驱动在ISR中读取或操作引脚状态(如去抖、确认电平)。

4.4 GPIO中断控制器的级联关系

在复杂SoC中,GPIO控制器通常作为中断控制器的下级节点,形成中断级联结构。设备树通过 interrupt-parent 链来描述这种层级:

GPIO中断控制器级联结构 GIC 中断控制器 #interrupt-cells = <3> GPIO1 控制器 #interrupt-cells = <2> GPIO2 控制器 #interrupt-cells = <2> GPIO3 控制器 #interrupt-cells = <2> 按键 (GPIO1_5) 传感器 (GPIO2_3) 编码器 (GPIO3_7) 设备树中:外设 → interrupt-parent = &gpioX → gpioX → interrupt-parent = &gic

4.5 常见问题与调试方法

  • 中断号不匹配:GPIO控制器内部的引脚号与设备树中 interrupts 的第一个单元必须一致。例如GPIO1_5对应中断号5,而非硬件绝对中断号。
  • 触发类型冲突:某些GPIO控制器硬件不支持所有触发类型,需查阅芯片手册确认支持的触发方式。
  • 中断共享问题:多个设备共享同一个GPIO中断线时,需在驱动中正确判断中断源。
  • 调试命令:使用 cat /proc/interrupts 查看中断注册情况;使用 devicetree/unittest 验证设备树解析是否正确。
核心总结:GPIO引脚作为中断源的本质是GPIO控制器内部集成了中断检测逻辑。设备树通过 interrupt-parent 和 interrupts 属性,将外设的中断请求路由到正确的GPIO控制器,再由GPIO控制器通过其自身的 interrupts 属性连接到上级中断控制器。理解这个级联关系是掌握设备树中断绑定的关键。