4. GPIO控制器与中断:GPIO作为中断源与硬件触发方式

在嵌入式Linux系统中,GPIO(通用输入输出)引脚除了作为数字输入/输出使用外,最常见的扩展功能就是作为外部中断源。本章将深入讲解GPIO控制器如何产生中断、设备树中如何描述这种能力,以及硬件触发方式的配置细节。

4.1 GPIO中断的硬件基础

GPIO控制器内部通常集成了一组中断检测逻辑,每个GPIO引脚都可以独立配置为中断源。当引脚上的电平状态满足预设条件时,控制器向中断控制器(如GIC)发送中断信号。

核心概念: GPIO中断的本质是“引脚状态变化事件”通过GPIO控制器转换为“中断请求信号”,再经中断控制器分发到CPU。

4.1.1 GPIO中断路径

从物理引脚到CPU处理函数的完整路径如下:

  1. 物理引脚:外部设备产生电平变化(上升/下降沿或高低电平)。
  2. GPIO控制器:检测到变化,匹配触发条件后产生内部中断信号。
  3. 中断控制器(GIC):接收GPIO控制器的中断请求,进行优先级仲裁和分发。
  4. CPU:响应中断,执行注册的中断处理函数(ISR)。
GPIO中断路径流程图 外部设备 电平变化 GPIO控制器 检测/触发匹配 中断控制器 GIC仲裁分发 CPU 执行ISR 关键点:GPIO控制器是中断源,中断控制器是中断路由器 设备树中需要同时描述GPIO控制器的中断产生能力和中断控制器的连接关系

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线、唤醒信号、状态监测
边沿触发 vs 电平触发波形对比 上升沿触发: 低 高 触发 触发 下降沿触发: 高 低 触发 触发 高电平触发: 低 高 持续触发 低电平触发: 高 低 持续触发 边沿触发:只在跳变瞬间产生中断;电平触发:在电平有效期间持续产生中断,需配合硬件清除机制

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对应一条中断线
GPIO控制器中断绑定结构 GIC中断控制器 interrupt-controller; GPIO控制器 interrupt-controller; #interrupt-cells = <2>; 外设设备 interrupt-parent = <&gpio1>; interrupts = <18 2>; GPIO bank中断线 设备中断请求 绑定层级关系 外设 → GPIO控制器(作为中断控制器) → GIC(顶层中断控制器) 设备树通过 interrupt-parent 和 interrupts 属性建立完整中断路径

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控制器的特定引脚。
  • 触发方式分为边沿触发(上升沿、下降沿、双沿)和电平触发(高电平、低电平),各有适用场景。
  • 设备树中的触发标志必须与硬件电路设计和驱动代码保持一致。