4. GPIO控制器与中断
在嵌入式Linux系统中,GPIO(通用输入输出)引脚除了作为数字输入/输出使用外,最常见的扩展功能就是作为外部中断源。本章将深入讲解GPIO控制器如何产生中断、设备树中如何描述这种能力,以及硬件触发方式的配置细节。
4.1 GPIO中断的硬件基础
GPIO控制器内部通常集成了一组中断检测逻辑,每个GPIO引脚都可以独立配置为中断源。当引脚上的电平状态满足预设条件时,控制器向中断控制器(如GIC)发送中断信号。
核心概念: GPIO中断的本质是“引脚状态变化事件”通过GPIO控制器转换为“中断请求信号”,再经中断控制器分发到CPU。
4.1.1 GPIO中断路径
从物理引脚到CPU处理函数的完整路径如下:
- 物理引脚:外部设备产生电平变化(上升/下降沿或高低电平)。
- GPIO控制器:检测到变化,匹配触发条件后产生内部中断信号。
- 中断控制器(GIC):接收GPIO控制器的中断请求,进行优先级仲裁和分发。
- CPU:响应中断,执行注册的中断处理函数(ISR)。
4.2 GPIO中断的硬件触发方式
GPIO中断的触发方式决定了中断产生的条件。Linux内核支持以下四种标准触发方式,在设备树中使用interrupts属性的第三个单元格(flags)进行配置。
| 触发方式 | 设备树标志值 | 宏定义 | 触发条件 | 典型应用场景 |
|---|---|---|---|---|
| 上升沿触发 | IRQ_TYPE_EDGE_RISING (1) |
#define IRQ_TYPE_EDGE_RISING 0x00000001 |
信号从低电平跳变到高电平时触发 | 按键按下释放、脉冲计数 |
| 下降沿触发 | IRQ_TYPE_EDGE_FALLING (2) |
#define IRQ_TYPE_EDGE_FALLING 0x00000002 |
信号从高电平跳变到低电平时触发 | 按键按下、边沿检测 |
| 双沿触发 | IRQ_TYPE_EDGE_BOTH (3) |
#define IRQ_TYPE_EDGE_BOTH 0x00000003 |
上升沿或下降沿均触发 | 编码器信号、电平变化检测 |
| 低电平触发 | IRQ_TYPE_LEVEL_LOW (8) |
#define IRQ_TYPE_LEVEL_LOW 0x00000008 |
信号保持低电平时持续触发 | 中断请求线(IRQ)共享、唤醒源 |
| 高电平触发 | IRQ_TYPE_LEVEL_HIGH (4) |
#define IRQ_TYPE_LEVEL_HIGH 0x00000004 |
信号保持高电平时持续触发 | 电源管理、状态指示 |
实战提示: 边沿触发(Edge)和电平触发(Level)的核心区别在于:边沿触发只在跳变瞬间产生一次中断,适合“事件”类场景;电平触发在条件满足期间持续产生中断,适合“状态”类场景,但需要配合硬件清除机制防止中断风暴。
4.2.1 边沿触发 vs 电平触发对比
| 特性 | 边沿触发 | 电平触发 |
|---|---|---|
| 中断产生时机 | 电平跳变瞬间(一次) | 电平保持有效期间(持续) |
| 中断丢失风险 | 高(脉冲过窄可能漏检) | 低(只要电平有效就会触发) |
| 中断风暴风险 | 低(不会重复触发) | 高(需在ISR中清除中断源) |
| 共享中断支持 | 不支持(无法区分设备) | 支持(通过状态判断) |
| 典型应用 | 按键、脉冲计数、编码器 | 设备IRQ线、唤醒信号、状态监测 |
4.3 设备树中GPIO中断的绑定描述
在设备树中,GPIO中断的绑定需要同时描述两个关键信息:GPIO引脚和中断触发方式。这通过interrupts属性和interrupt-parent属性实现。
4.3.1 标准绑定格式
对于挂载在GPIO控制器下的设备,设备树节点通常采用以下格式:
/* 设备节点绑定GPIO中断 */
device_node {
compatible = "vendor,device";
interrupt-parent = &gpio1; /* 指定中断父节点为GPIO控制器 */
interrupts = <PIN_NUMBER FLAGS>; /* 引脚号和触发标志 */
};
其中:
interrupt-parent:指向GPIO控制器节点,表明该设备的中断由该GPIO控制器产生。interrupts:第一个单元格是GPIO引脚号(相对于GPIO控制器的偏移),第二个单元格是触发方式标志。
4.3.2 完整设备树示例
/* GPIO控制器节点 */
gpio1: gpio@50002000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x50002000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
/* 使用GPIO中断的设备节点 */
key_device {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
button@0 {
label = "KEY0";
linux,code = <KEY_ENTER>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; /* GPIO1_IO18 */
interrupts = <18 IRQ_TYPE_EDGE_FALLING>; /* 下降沿触发 */
interrupt-parent = <&gpio1>;
};
};
常见错误: 在
gpio-keys驱动中,如果同时指定了gpios和interrupts属性,驱动会优先使用interrupts属性中的触发方式。如果两者不一致,可能导致中断行为不符合预期。建议保持两者一致,或只使用其中一种方式。
4.4 GPIO控制器作为中断控制器的设备树声明
要让GPIO控制器能够作为中断源,必须在设备树中显式声明其中断控制器能力:
| 属性 | 作用 | 值说明 |
|---|---|---|
interrupt-controller; |
声明该节点是一个中断控制器 | 空属性,无值 |
#interrupt-cells = <N>; |
指定中断描述符的单元格数量 | 通常为2:<引脚号 触发标志> |
interrupts |
GPIO控制器自身连接到上级中断控制器的中断线 | 通常每个GPIO bank对应一条中断线 |
4.5 触发方式选择实战建议
选型原则:
- 按键/开关:优先使用下降沿触发(按下动作),配合软件消抖。如果关心释放动作,使用双沿触发。
- 脉冲计数:使用上升沿或下降沿触发,避免电平触发导致重复计数。
- 中断请求线(IRQ):使用电平触发(低电平或高电平),支持多设备共享中断线。
- 唤醒源:使用电平触发,确保在睡眠状态下电平有效时能唤醒系统。
- 编码器信号:使用双沿触发(IRQ_TYPE_EDGE_BOTH),捕获每个状态变化。
4.5.1 触发方式与驱动代码的对应关系
在Linux内核驱动中,可以通过irq_set_irq_type()函数或设备树解析结果来配置触发方式:
/* 驱动中获取并配置中断触发方式 */
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int irq, ret;
/* 从设备树获取中断号 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 设备树中已指定触发方式,内核会自动配置 */
/* 也可以手动修改触发方式 */
ret = irq_set_irq_type(irq, IRQ_TYPE_EDGE_FALLING);
if (ret) {
dev_err(dev, "failed to set irq type\n");
return ret;
}
/* 注册中断处理函数 */
ret = request_irq(irq, my_isr, IRQF_TRIGGER_FALLING,
"my_device", dev);
if (ret)
return ret;
return 0;
}
内核行为说明: 当设备树中通过
interrupts属性指定了触发方式后,内核在解析设备树时会自动调用irq_set_irq_type()进行配置。驱动中request_irq()的irqflags参数应与设备树保持一致,否则可能导致配置冲突。
4.6 本章小结
GPIO中断的配置涉及硬件触发方式的理解和设备树绑定的正确描述。核心要点包括:
- GPIO控制器通过
interrupt-controller和#interrupt-cells声明其中断源能力。 - 设备通过
interrupt-parent和interrupts绑定到GPIO控制器的特定引脚。 - 触发方式分为边沿触发(上升沿、下降沿、双沿)和电平触发(高电平、低电平),各有适用场景。
- 设备树中的触发标志必须与硬件电路设计和驱动代码保持一致。