4. GPIO与中断的硬件关联:GPIO引脚作为中断源的硬件原理,电平触发与边沿触发,以及设备树如何表达这种关联
在嵌入式系统中,GPIO(通用输入输出)引脚除了作为数字信号的输入输出端口外,最常见的扩展功能便是作为外部中断源。理解GPIO引脚如何产生中断、中断触发方式的硬件差异,以及设备树如何描述这种关联,是编写正确设备树绑定的核心基础。
4.1 GPIO引脚作为中断源的硬件原理
从硬件角度看,一个GPIO引脚要作为中断源,通常需要经过以下三个层次的信号路径:
- 物理引脚电平变化:外部设备(按键、传感器、外设)改变GPIO引脚上的电压电平(高→低 或 低→高)。
- GPIO控制器内部检测:GPIO控制器的输入缓冲器捕获电平变化,并与内部配置的触发条件(电平或边沿)进行比较。
- 中断控制器路由:匹配触发条件后,GPIO控制器向中断控制器(如GIC、NVIC)发送中断请求信号,CPU响应后执行中断服务程序(ISR)。
关键概念:GPIO中断并非由CPU直接轮询引脚电平,而是由硬件自动检测并触发。设备树的作用是告诉内核:哪个GPIO控制器、哪个引脚、以何种触发方式连接到哪个中断控制器。
4.2 电平触发与边沿触发
中断触发方式决定了GPIO控制器在何种条件下产生中断请求。两种基本触发方式在硬件实现和应用场景上有本质区别:
| 触发方式 | 硬件检测原理 | 中断产生条件 | 典型应用 | 注意事项 |
|---|---|---|---|---|
| 电平触发 | 持续检测引脚电平状态 | 引脚电平保持为指定值(高或低)期间持续产生中断 | 唤醒信号、电源管理、紧急事件 | 必须在中断服务程序中清除中断源,否则会无限触发 |
| 边沿触发 | 检测电平跳变瞬间 | 引脚电平从低到高(上升沿)或从高到低(下降沿)时触发一次 | 按键输入、编码器、脉冲计数 | 不会重复触发,但可能丢失快速脉冲 |
| 双边沿触发 | 同时检测上升和下降跳变 | 任何电平变化都触发一次中断 | 旋转编码器、通信协议检测 | 中断频率是单边沿的两倍 |
避坑指南:电平触发中断在ISR中必须显式操作硬件寄存器清除中断源(如读取数据寄存器、写清除位),否则中断会立即再次触发,导致CPU陷入死循环。边沿触发则不会出现此问题,但需要确保脉冲宽度满足GPIO控制器的最小检测时间。
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 链来描述这种层级:
4.5 常见问题与调试方法
- 中断号不匹配:GPIO控制器内部的引脚号与设备树中
interrupts的第一个单元必须一致。例如GPIO1_5对应中断号5,而非硬件绝对中断号。 - 触发类型冲突:某些GPIO控制器硬件不支持所有触发类型,需查阅芯片手册确认支持的触发方式。
- 中断共享问题:多个设备共享同一个GPIO中断线时,需在驱动中正确判断中断源。
- 调试命令:使用
cat /proc/interrupts查看中断注册情况;使用devicetree/unittest验证设备树解析是否正确。
核心总结:GPIO引脚作为中断源的本质是GPIO控制器内部集成了中断检测逻辑。设备树通过
interrupt-parent 和 interrupts 属性,将外设的中断请求路由到正确的GPIO控制器,再由GPIO控制器通过其自身的 interrupts 属性连接到上级中断控制器。理解这个级联关系是掌握设备树中断绑定的关键。