04-GPIO与中断系统.md 32 KB


title: GPIO与中断系统 tags: [GPIO, 中断, IMX6ULL, 裸机开发, Cortex-A7, GIC] created: 2026-09-16 updated: 2026-09-17

pdf_ref: "【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第十七章 GPIO中断实验"

GPIO与中断系统

💡 关联知识: [[STM32学习笔记/02-GPIO寄存器详解与三步进化法]] | [[STM32学习笔记/04-GPIO输入与NVIC中断系统]] | [[嵌入式Linux驱动开发实战/01-ARM架构与裸机编程/01-ARM架构概述]]


一、GPIO概述

1.1 GPIO基本概念

GPIO(General Purpose Input/Output)是通用输入输出接口,是处理器与外部设备交互的基础通道。IMX6ULL芯片提供5组GPIO(GPIO1~GPIO5),每组包含32个IO引脚,总共160个GPIO引脚。

flowchart LR
    accTitle: GPIO功能架构
    accDescr: GPIO引脚通过IOMUXC复用控制器连接到处理器内核,支持输入输出和中断功能

    subgraph 外设
        A[🔴 LED]
        B[🔘 按键]
        C[📟 LCD]
        D[📡 传感器]
    end

    subgraph GPIO控制器
        E[🔌 IOMUXC复用]
        F[⚙️ GPIO控制器]
    end

    subgraph 处理器内核
        G[🧠 Cortex-A7]
    end

    A & B & C & D --> E
    E --> F
    F --> G

1.2 GPIO功能特性

特性 描述
输入模式 读取外部电平状态(0/1)
输出模式 控制引脚输出高/低电平
中断功能 支持边沿触发和电平触发
引脚复用 通过IOMUXC配置引脚功能
电气特性 可配置驱动强度、上下拉电阻

1.3 GPIO寄存器基地址

/* GPIO基地址定义 - IMX6ULL参考手册 Section 28 */
#define GPIO1_BASE          0x0209C000
#define GPIO2_BASE          0x020A0000
#define GPIO3_BASE          0x020A4000
#define GPIO4_BASE          0x020A8000
#define GPIO5_BASE          0x020AC000

/* GPIO寄存器偏移 - IMX6ULL参考手册 Table 28-3 */
#define GPIO_DR             0x000   /* 数据寄存器 */
#define GPIO_GDIR           0x004   /* 方向寄存器 */
#define GPIO_PSR            0x008   /* 状态寄存器 */
#define GPIO_ICR1           0x00C   /* 中断配置寄存器1 (pin0~15) */
#define GPIO_ICR2           0x010   /* 中断配置寄存器2 (pin16~31) */
#define GPIO_IMR            0x014   /* 中断屏蔽寄存器 */
#define GPIO_ISR            0x018   /* 中断状态寄存器 */
#define GPIO_EDGE_SEL       0x01C   /* 边沿选择寄存器 */

1.4 IOMUXC引脚复用

/* IOMUXC基地址 - IMX6ULL参考手册 Section 8 */
#define IOMUXC_BASE         0x020E0000

/* 示例:GPIO1_IO03复用配置 */
/* MUX_MODE寄存器:选择引脚功能 */
#define SW_MUX_GPIO1_IO03   (*(volatile unsigned int *)(IOMUXC_BASE + 0x068))
/* PAD属性寄存器:配置电气特性 */
#define SW_PAD_GPIO1_IO03   (*(volatile unsigned int *)(IOMUXC_BASE + 0x2F4))

/* MUX_MODE值定义 - IMX6ULL参考手册 Table 8-23 */
#define MUX_MODE_GPIO       0x5     /* GPIO功能 */
#define MUX_MODE_ALT0       0x0     /* 复用功能0 */
#define MUX_MODE_ALT1       0x1     /* 复用功能1 */
/* ... 其他复用功能 */

/* PAD属性配置 */
#define PAD_CTL_SPEED_LOW   0x0     /* 低速 */
#define PAD_CTL_SPEED_MED   0x4     /* 中速 */
#define PAD_CTL_SPEED_HIGH  0x8     /* 高速 */
#define PAD_CTL_PULL_UP     0x20000 /* 上拉 */
#define PAD_CTL_PULL_DOWN   0x10000 /* 下拉 */

1.5 GPIO操作函数

/* GPIO初始化函数 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
    /* 1. 禁用中断 */
    base->IMR &= ~(1U << pin);

    /* 2. 设置方向 */
    if (config->direction == kGPIO_DigitalInput)
    {
        base->GDIR &= ~(1 << pin);  /* 输入 */
    }
    else
    {
        base->GDIR |= 1 << pin;     /* 输出 */
        gpio_pinwrite(base, pin, config->outputLogic);
    }

    /* 3. 配置中断触发方式 */
    gpio_intconfig(base, pin, config->interruptMode);
}

/* GPIO输出控制 */
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{
    if (value == 0U)
        base->DR &= ~(1U << pin);   /* 输出低电平 */
    else
        base->DR |= (1U << pin);    /* 输出高电平 */
}

/* GPIO输入读取 */
int gpio_pinread(GPIO_Type *base, int pin)
{
    return (((base->DR) >> pin) & 0x1);
}

二、GPIO寄存器详解

2.1 DR(数据寄存器)

DR寄存器是GPIO的数据寄存器,用于读取引脚状态(输入模式)或设置输出电平(输出模式)。

/* DR寄存器结构 - IMX6ULL参考手册 Figure 28-4 */
/*
 * 位[31:0]:每个位对应一个GPIO引脚
 * 输入模式:读取引脚实际电平
 * 输出模式:写入值控制引脚输出
 */
#define GPIO_DR_BASE        0x0209C000

/* 示例代码 */
volatile unsigned int *gpio_dr = (volatile unsigned int *)GPIO_DR_BASE;

/* 读取GPIO1_IO18的值 */
int value = (*gpio_dr >> 18) & 0x1;

/* 设置GPIO1_IO18输出高电平 */
*gpio_dr |= (1 << 18);

/* 设置GPIO1_IO18输出低电平 */
*gpio_dr &= ~(1 << 18);

2.2 GDIR(方向寄存器)

GDIR寄存器控制GPIO引脚的数据传输方向,0表示输入,1表示输出。

/* GDIR寄存器结构 - IMX6ULL参考手册 Figure 28-5 */
/*
 * 位[31:0]:每个位对应一个GPIO引脚
 * 0:输入模式
 * 1:输出模式
 */

/* 示例代码 */
volatile unsigned int *gpio_gdir = (volatile unsigned int *)(GPIO1_BASE + GPIO_GDIR);

/* 设置GPIO1_IO18为输入模式 */
*gpio_gdir &= ~(1 << 18);

/* 设置GPIO1_IO18为输出模式 */
*gpio_gdir |= (1 << 18);

2.3 PSR(状态寄存器)

PSR寄存器用于读取GPIO引脚的当前状态,无论引脚被配置为输入还是输出。

/* PSR寄存器结构 - IMX6ULL参考手册 Figure 28-6 */
/*
 * 位[31:0]:只读,每个位对应一个GPIO引脚的实际电平
 * 0:低电平
 * 1:高电平
 */

/* 示例代码 */
volatile unsigned int *gpio_psr = (volatile unsigned int *)(GPIO1_BASE + GPIO_PSR);

/* 读取GPIO1_IO18的实际电平 */
int pin_state = (*gpio_psr >> 18) & 0x1;

2.4 ICR(中断配置寄存器)

ICR寄存器用于配置GPIO中断的触发方式,分为ICR1(pin0~15)和ICR2(pin16~31)两个寄存器。

/* ICR寄存器结构 - IMX6ULL参考手册 Figure 28-7 */
/*
 * 每个引脚占用2位,配置4种触发方式:
 * 00:低电平触发
 * 01:高电平触发
 * 10:上升沿触发
 * 11:下降沿触发
 */

/* 中断触发类型枚举 */
typedef enum _gpio_interrupt_mode
{
    kGPIO_NoIntmode = 0U,           /* 无中断功能 */
    kGPIO_IntLowLevel = 1U,         /* 低电平触发 */
    kGPIO_IntHighLevel = 2U,        /* 高电平触发 */
    kGPIO_IntRisingEdge = 3U,       /* 上升沿触发 */
    kGPIO_IntFallingEdge = 4U,      /* 下降沿触发 */
    kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都触发 */
} gpio_interrupt_mode_t;

/* 配置GPIO中断触发方式 */
void gpio_intconfig(GPIO_Type *base, unsigned int pin,
                    gpio_interrupt_mode_t pin_int_mode)
{
    volatile uint32_t *icr;
    uint32_t icrShift;

    icrShift = pin;
    base->EDGE_SEL &= ~(1U << pin);  /* 清除边沿选择 */

    if (pin < 16)  /* 低16位使用ICR1 */
    {
        icr = &(base->ICR1);
    }
    else           /* 高16位使用ICR2 */
    {
        icr = &(base->ICR2);
        icrShift -= 16;
    }

    switch (pin_int_mode)
    {
        case (kGPIO_IntLowLevel):
            *icr &= ~(3U << (2 * icrShift));        /* 00:低电平 */
            break;
        case (kGPIO_IntHighLevel):
            *icr = (*icr & (~(3U << (2 * icrShift)))) |
                   (1U << (2 * icrShift));           /* 01:高电平 */
            break;
        case (kGPIO_IntRisingEdge):
            *icr = (*icr & (~(3U << (2 * icrShift)))) |
                   (2U << (2 * icrShift));           /* 10:上升沿 */
            break;
        case (kGPIO_IntFallingEdge):
            *icr |= (3U << (2 * icrShift));          /* 11:下降沿 */
            break;
        case (kGPIO_IntRisingOrFallingEdge):
            base->EDGE_SEL |= (1U << pin);           /* 边沿选择寄存器 */
            break;
        default:
            break;
    }
}

2.5 IMR(中断屏蔽寄存器)

IMR寄存器用于控制GPIO中断的使能和禁止,0表示禁止中断,1表示使能中断。

/* IMR寄存器结构 - IMX6ULL参考手册 Figure 28-8 */
/*
 * 位[31:0]:每个位对应一个GPIO引脚的中断屏蔽
 * 0:禁止中断
 * 1:使能中断
 */

/* 使能GPIO中断 */
void gpio_enableint(GPIO_Type *base, unsigned int pin)
{
    base->IMR |= (1 << pin);
}

/* 禁止GPIO中断 */
void gpio_disableint(GPIO_Type *base, unsigned int pin)
{
    base->IMR &= ~(1 << pin);
}

2.6 ISR(中断状态寄存器)

ISR寄存器用于指示中断是否发生,写1清除对应的中断状态标志。

/* ISR寄存器结构 - IMX6ULL参考手册 Figure 28-9 */
/*
 * 位[31:0]:只读,每个位对应一个GPIO引脚的中断状态
 * 0:中断未发生
 * 1:中断已发生
 * 写1清除对应中断状态
 */

/* 清除GPIO中断标志位 */
void gpio_clearintflags(GPIO_Type *base, unsigned int pin)
{
    base->ISR |= (1 << pin);  /* 写1清除 */
}

三、中断系统概述

3.1 Cortex-A7中断向量表

Cortex-A7内核有8个异常中断,中断向量表位于程序最前面:

flowchart LR
    accTitle: Cortex-A7中断向量表
    accDescr: 显示Cortex-A7处理器的8个异常中断及其向量地址

    subgraph 中断向量表
        A[0x00: 复位中断] --> B[0x04: 未定义指令中断]
        B --> C[0x08: 软中断SWI]
        C --> D[0x0C: 指令预取中止]
        D --> E[0x10: 数据访问中止]
        E --> F[0x14: 未使用]
        F --> G[0x18: IRQ中断]
        G --> H[0x1C: FIQ中断]
    end
向量地址 中断类型 中断模式 描述
0x00 复位中断(Reset) SVC CPU复位后进入,完成初始化
0x04 未定义指令中断 Undef 指令不能识别时触发
0x08 软中断(SWI) SVC Linux系统调用使用此中断
0x0C 指令预取中止 Abort 预取出错时触发
0x10 数据访问中止 Abort 数据访问出错时触发
0x14 未使用 保留
0x18 IRQ中断 IRQ 外部中断,最常用
0x1C FIQ中断 FIQ 快速中断,需要快速处理

3.2 GIC中断控制器

IMX6ULL使用GIC-400中断控制器(ARM Generic Interrupt Controller),支持多核中断管理。

flowchart LR
    accTitle: GIC中断控制器架构
    accDescr: GIC控制器分为分发器端和CPU接口端,接收外部中断并传递给ARM内核

    subgraph 中断源
        A[SPI共享中断]
        B[PPI私有中断]
        C[SGI软件中断]
    end

    subgraph GIC控制器
        D[分发器端Distributor]
        E[CPU接口端Interface]
    end

    subgraph ARM内核
        F[IRQ信号]
        G[FIQ信号]
    end

    A & B & C --> D
    D --> E
    E --> F & G
    F --> H[Cortex-A7]

3.3 GIC中断类型

中断类型 ID范围 数量 描述
SGI (Software-generated) ID0~ID15 16 软件触发中断,用于多核通信
PPI (Private Peripheral) ID16~ID31 16 私有中断,每个核独有
SPI (Shared Peripheral) ID32~ID1019 988 共享中断,所有核共享

3.4 IMX6ULL中断源

IMX6ULL总共使用160个中断ID(ID0~ID159),主要包括:

/* 中断ID定义 - 核心中断 */
typedef enum IRQn {
    /* SGI中断 (ID0~ID15) */
    Software0_IRQn                  = 0,
    Software1_IRQn                  = 1,
    // ... Software15_IRQn = 15

    /* PPI中断 (ID16~ID31) */
    VirtualMaintenance_IRQn         = 25,
    HypervisorTimer_IRQn            = 26,
    VirtualTimer_IRQn               = 27,
    LegacyFastInt_IRQn              = 28,
    SecurePhyTimer_IRQn             = 29,
    NonSecurePhyTimer_IRQn          = 30,
    LegacyIRQ_IRQn                  = 31,

    /* SPI中断 (ID32~ID159) */
    IOMUXC_IRQn                     = 32,  /* IOMUXC中断 */
    DAP_IRQn                        = 33,  /* 调试端口中断 */
    SDMA_IRQn                       = 34,  /* SDMA中断 */
    TSC_IRQn                        = 35,  /* 触摸屏中断 */
    SNVS_IRQn                       = 36,  /* SNVS中断 */
    // ... 其他中断
    GPIO1_Combined_16_31_IRQn       = 99,  /* GPIO1_IO16~IO31组合中断 */
    // ... 更多中断
    PMU_IRQ2_IRQn                   = 159, /* PMU中断 */
} IRQn_Type;

#define NUMBER_OF_INT_VECTORS 160

3.5 GIC分发器端寄存器

/* GIC分发器端寄存器 - IMX6ULL参考手册 Section 3.2 */
#define GICD_BASE           0x01880000

/* 分发器控制寄存器 */
#define GICD_CTLR           (*(volatile unsigned int *)(GICD_BASE + 0x000))
/* 中断使能寄存器 (16个,每个控制32个中断) */
#define GICD_ISENABLER(n)   (*(volatile unsigned int *)(GICD_BASE + 0x100 + (n) * 4))
/* 中断禁止寄存器 */
#define GICD_ICENABLER(n)   (*(volatile unsigned int *)(GICD_BASE + 0x180 + (n) * 4))
/* 中断优先级寄存器 */
#define GICD_IPRIORITYR(n)  (*(volatile unsigned int *)(GICD_BASE + 0x400 + (n) * 4))
/* 中断目标CPU寄存器 */
#define GICD_ITARGETSR(n)   (*(volatile unsigned int *)(GICD_BASE + 0x800 + (n) * 4))
/* 中断配置寄存器 */
#define GICD_ICFGR(n)       (*(volatile unsigned int *)(GICD_BASE + 0xC00 + (n) * 4))

3.6 GIC CPU接口端寄存器

/* GIC CPU接口端寄存器 - IMX6ULL参考手册 Section 3.2 */
#define GICC_BASE           0x01840000

/* CPU接口控制寄存器 */
#define GICC_CTLR           (*(volatile unsigned int *)(GICC_BASE + 0x000))
/* 优先级掩码寄存器 */
#define GICC_PMR            (*(volatile unsigned int *)(GICC_BASE + 0x004))
/* 二进制点寄存器 */
#define GICC_BPR            (*(volatile unsigned int *)(GICC_BASE + 0x008))
/* 中断应答寄存器 (读取中断号) */
#define GICC_IAR            (*(volatile unsigned int *)(GICC_BASE + 0x00C))
/* 中断结束寄存器 (写入中断号结束中断) */
#define GICC_EOIR           (*(volatile unsigned int *)(GICC_BASE + 0x010))
/* 运行优先级寄存器 */
#define GICC_RPR            (*(volatile unsigned int *)(GICC_BASE + 0x014))
/* 最高优先级中断状态寄存器 */
#define GICC_HPPIR          (*(volatile unsigned int *)(GICC_BASE + 0x018))

3.7 CP15协处理器

CP15协处理器用于系统管理,在中断系统中主要用于获取GIC基地址和设置中断向量表偏移。

/* CP15协处理器访问指令 */
/* MRC: 读取CP15寄存器到ARM寄存器 */
/* MCR: 写入ARM寄存器到CP15寄存器 */

/* 获取GIC基地址 */
/* GIC基地址保存在CP15的CBAR寄存器中 */
uint32_t gic_base;
__asm__ __volatile__(
    "mrc p15, 4, %0, c15, c0, 0\n"
    : "=r"(gic_base)
    :
    : "memory"
);
/* gic_base即为GIC基地址 */

/* 设置中断向量表偏移 */
/* 中断向量表基地址保存在CP15的VBAR寄存器中 */
void set_vbar(uint32_t vbar_addr)
{
    __asm__ __volatile__(
        "mcr p15, 0, %0, c12, c0, 0\n"
        :
        : "r"(vbar_addr)
        : "memory"
    );
}
/* 将中断向量表设置到0x87800000 */
set_vbar(0x87800000);

四、GPIO中断配置

4.1 中断触发方式

GPIO中断支持5种触发方式:

触发方式 ICR值 EDGE_SEL 描述
低电平触发 00 0 引脚为低电平时持续触发
高电平触发 01 0 引脚为高电平时持续触发
上升沿触发 10 0 引脚电平从低到高跳变时触发
下降沿触发 11 0 引脚电平从高到低跳变时触发
双边沿触发 1 任意跳变都触发

4.2 中断配置流程

flowchart LR
    accTitle: GPIO中断配置流程
    accDescr: 从引脚复用配置到中断服务函数注册的完整流程

    A[1️⃣ 配置引脚复用] --> B[2️⃣ 设置GPIO方向]
    B --> C[3️⃣ 配置中断触发方式]
    C --> D[4️⃣ 使能GPIO中断]
    D --> E[5️⃣ 使能GIC中断]
    E --> F[6️⃣ 注册中断服务函数]

4.3 GPIO中断配置示例

/* GPIO中断配置函数 */
void gpio_intconfig(GPIO_Type *base, unsigned int pin,
                    gpio_interrupt_mode_t pin_int_mode)
{
    volatile uint32_t *icr;
    uint32_t icrShift;

    icrShift = pin;
    base->EDGE_SEL &= ~(1U << pin);  /* 清除边沿选择 */

    if (pin < 16)  /* 低16位使用ICR1 */
    {
        icr = &(base->ICR1);
    }
    else           /* 高16位使用ICR2 */
    {
        icr = &(base->ICR2);
        icrShift -= 16;
    }

    switch (pin_int_mode)
    {
        case (kGPIO_IntLowLevel):
            *icr &= ~(3U << (2 * icrShift));        /* 00:低电平 */
            break;
        case (kGPIO_IntHighLevel):
            *icr = (*icr & (~(3U << (2 * icrShift)))) |
                   (1U << (2 * icrShift));           /* 01:高电平 */
            break;
        case (kGPIO_IntRisingEdge):
            *icr = (*icr & (~(3U << (2 * icrShift)))) |
                   (2U << (2 * icrShift));           /* 10:上升沿 */
            break;
        case (kGPIO_IntFallingEdge):
            *icr |= (3U << (2 * icrShift));          /* 11:下降沿 */
            break;
        case (kGPIO_IntRisingOrFallingEdge):
            base->EDGE_SEL |= (1U << pin);           /* 边沿选择寄存器 */
            break;
        default:
            break;
    }
}

/* 使能GPIO中断 */
void gpio_enableint(GPIO_Type *base, unsigned int pin)
{
    base->IMR |= (1 << pin);
}

/* 禁止GPIO中断 */
void gpio_disableint(GPIO_Type *base, unsigned int pin)
{
    base->IMR &= ~(1 << pin);
}

/* 清除GPIO中断标志位 */
void gpio_clearintflags(GPIO_Type *base, unsigned int pin)
{
    base->ISR |= (1 << pin);  /* 写1清除 */
}

4.4 中断使能配置

/* 中断使能函数 */
void int_init(void)
{
    /* 1. 初始化GIC */
    GIC_Init();

    /* 2. 初始化中断服务函数表 */
    system_irqtable_init();

    /* 3. 设置中断向量表偏移 */
    __set_VBAR((uint32_t)0x87800000);
}

/* 使能GIC中断 */
void irq_enable(int irq)
{
    /* 使能分发器端中断 */
    GICD_ISENABLER(irq / 32] = 1 << (irq % 32);
}

/* 禁止GIC中断 */
void irq_disable(int irq)
{
    /* 禁止分发器端中断 */
    GICD_ICENABLER(irq / 32] = 1 << (irq % 32);
}

五、完整源码分析

5.1 中断向量表定义(start.S)

/* 中断向量表 - start.S */
.global _start

_start:
    ldr pc, =Reset_Handler       /* 复位中断 */
    ldr pc, =Undefined_Handler   /* 未定义指令中断 */
    ldr pc, =SVC_Handler         /* SVC中断 */
    ldr pc, =PrefAbort_Handler   /* 预取终止中断 */
    ldr pc, =DataAbort_Handler   /* 数据终止中断 */
    ldr pc, =NotUsed_Handler     /* 未使用中断 */
    ldr pc, =IRQ_Handler         /* IRQ中断 */
    ldr pc, =FIQ_Handler         /* FIQ中断 */

5.2 复位中断服务函数

/* 复位中断服务函数 */
Reset_Handler:
    cpsid i                     /* 关闭全局中断 */

    /* 关闭I/D Cache和MMU */
    mrc p15, 0, r0, c1, c0, 0  /* 读取CP15的C1寄存器 */
    bic r0, r0, #(0x1 << 12)   /* 清除I位,关闭I Cache */
    bic r0, r0, #(0x1 << 2)    /* 清除C位,关闭D Cache */
    bic r0, r0, #0x2            /* 清除A位,关闭对齐检查 */
    bic r0, r0, #(0x1 << 11)   /* 清除Z位,关闭分支预测 */
    bic r0, r0, #0x1            /* 清除M位,关闭MMU */
    mcr p15, 0, r0, c1, c0, 0  /* 写入CP15的C1寄存器 */

    /* 设置各模式栈指针 */
    /* IRQ模式栈 */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x12          /* IRQ模式 */
    msr cpsr, r0
    ldr sp, =0x80600000         /* IRQ栈首地址 */

    /* SYS模式栈 */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x1f          /* SYS模式 */
    msr cpsr, r0
    ldr sp, =0x80400000         /* SYS栈首地址 */

    /* SVC模式栈 */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0x13          /* SVC模式 */
    msr cpsr, r0
    ldr sp, =0X80200000         /* SVC栈首地址 */

    cpsie i                     /* 打开全局中断 */
    b main                      /* 跳转到main函数 */

5.3 IRQ中断服务函数

/* IRQ中断服务函数 - 重点 */
IRQ_Handler:
    push {lr}                   /* 保存lr地址 */
    push {r0-r3, r12}           /* 保存r0-r3,r12寄存器 */

    mrs r0, spsr                /* 读取spsr寄存器 */
    push {r0}                   /* 保存spsr寄存器 */

    /* 获取当前中断号 */
    mrc p15, 4, r1, c15, c0, 0 /* 获取GIC基地址 */
    add r1, r1, #0X2000        /* GIC基地址+0x2000=CPU接口端基地址 */
    ldr r0, [r1, #0XC]         /* 读取GICC_IAR寄存器,获取中断号 */
    push {r0, r1}              /* 保存中断号和基地址 */

    cps #0x13                   /* 进入SVC模式,允许其他中断 */
    push {lr}                   /* 保存SVC模式lr寄存器 */
    ldr r2, =system_irqhandler  /* 加载C语言中断处理函数 */
    blx r2                      /* 调用C语言中断处理函数 */

    pop {lr}                    /* SVC模式lr出栈 */
    cps #0x12                   /* 进入IRQ模式 */
    pop {r0, r1}
    str r0, [r1, #0X10]        /* 写EOIR,中断处理完成 */

    pop {r0}
    msr spsr_cxsf, r0          /* 恢复spsr */

    pop {r0-r3, r12}           /* r0-r3,r12出栈 */
    pop {lr}                    /* lr出栈 */
    subs pc, lr, #4            /* 返回被中断点 */

5.4 通用中断处理函数

/* 中断处理函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

/* 默认中断处理函数 */
void default_irqhandler(unsigned int giccIar, void *userParam)
{
    while(1) {}
}

/* C语言中断处理函数 */
void system_irqhandler(unsigned int giccIar)
{
    uint32_t intNum = giccIar & 0x3FFUL;  /* 获取中断号 */

    /* 检查中断号是否有效 */
    if ((intNum == 1020) || (intNum >= NUMBER_OF_INT_VECTORS))
    {
        return;
    }

    irqNesting++;  /* 中断嵌套计数器加一 */

    /* 调用对应的中断处理函数 */
    irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);

    irqNesting--;  /* 中断嵌套计数器减一 */
}

/* 注册中断处理函数 */
void system_register_irqhandler(IRQn_Type irq,
                                system_irq_handler_t handler,
                                void *userParam)
{
    irqTable[irq].irqHandler = handler;
    irqTable[irq].userParam = userParam;
}

5.5 按键中断驱动

/* 按键中断初始化 */
void exit_init(void)
{
    gpio_pin_config_t key_config;

    /* 1. 配置引脚复用 */
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);

    /* 2. 初始化GPIO为中断模式 */
    key_config.direction = kGPIO_DigitalInput;
    key_config.interruptMode = kGPIO_IntFallingEdge;  /* 下降沿触发 */
    key_config.outputLogic = 1;
    gpio_init(GPIO1, 18, &key_config);

    /* 3. 使能GIC中断 */
    GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);

    /* 4. 注册中断服务函数 */
    system_register_irqhandler(GPIO1_Combined_16_31_IRQn,
                                (system_irq_handler_t)gpio1_io18_irqhandler,
                                NULL);

    /* 5. 使能GPIO中断 */
    gpio_enableint(GPIO1, 18);
}

/* 按键中断处理函数 */
void gpio1_io18_irqhandler(void)
{
    static unsigned char state = 0;

    /* 延时消抖(实际项目中应使用定时器) */
    delay(10);

    /* 检查按键是否按下 */
    if (gpio_pinread(GPIO1, 18) == 0)
    {
        state = !state;
        beep_switch(state);  /* 控制蜂鸣器 */
    }

    /* 清除中断标志位 */
    gpio_clearintflags(GPIO1, 18);
}

5.6 主函数

/* 主函数 */
int main(void)
{
    unsigned char state = OFF;

    int_init();           /* 初始化中断(必须最先调用!) */
    imx6u_clkinit();      /* 初始化系统时钟 */
    clk_enable();         /* 使能所有时钟 */
    led_init();           /* 初始化LED */
    beep_init();          /* 初始化蜂鸣器 */
    key_init();           /* 初始化按键 */
    exit_init();          /* 初始化按键中断 */

    while(1)
    {
        state = !state;
        led_switch(LED0, state);  /* LED闪烁 */
        delay(500);
    }

    return 0;
}

六、实验验证

6.1 编译方法

# 交叉编译工具链
CROSS_COMPILE ?= arm-linux-gnueabihf-

# 编译命令
make clean
make

# 生成文件
# int.elf  - 可执行文件
# int.bin  - 二进制文件
# int.dis  - 反汇编文件

6.2 烧写到SD卡

# 赋予imxdownload可执行权限
chmod 777 imxdownload

# 烧写到SD卡(不能烧写到/dev/sda或sda1)
./imxdownload int.bin /dev/sdd

6.3 测试步骤

  1. 将SD卡插入开发板卡槽
  2. 复位开发板
  3. 观察LED0以500ms周期闪烁
  4. 按下KEY0按键
  5. 蜂鸣器打开
  6. 再次按下KEY0按键
  7. 蜂鸣器关闭

6.4 中断响应验证

测试项 预期结果 实际结果 状态
LED闪烁 周期约500ms
按键中断触发 蜂鸣器开关
中断清除标志 无重复触发
系统稳定性 无死机

七、跨平台对比

7.1 IMX6ULL vs STM32 vs RK3568

特性 IMX6ULL (Cortex-A7) STM32F103 (Cortex-M3) RK3568 (Cortex-A55)
中断控制器 GIC-400 NVIC GIC-500
中断数量 1024 (SPI+PPI+SGI) 81/240 1024
优先级级数 256级 16级 256级
中断嵌套 支持 支持 支持
中断向量表 可重定位(VBAR) 固定在Flash 可重定位(VBAR)
GPIO中断分组 16个IO共用一个IRQ 每个IO独立IRQ 16个IO共用一个IRQ
中断触发配置 ICR寄存器(2bit) EXTI_IMR/RTSR/FTSR ICR寄存器

7.2 中断处理流程对比

flowchart LR
    accTitle: 中断处理流程对比
    accDescr: 对比Cortex-A和Cortex-M中断处理的主要差异

    subgraph Cortex-M NVIC
        A1[硬件自动保存上下文] --> B1[读取中断向量表]
        B1 --> C1[调用中断服务函数]
        C1 --> D1[写ICPR清除中断]
        D1 --> E1[硬件自动恢复上下文]
    end

    subgraph Cortex-A GIC
        A2[保存现场到栈] --> B2[读取GICC_IAR获取中断号]
        B2 --> C2[调用C函数system_irqhandler]
        C2 --> D2[写GICC_EOIR结束中断]
        D2 --> E2[恢复现场]
    end

7.3 关键差异详解

差异点 IMX6ULL (GIC) STM32 (NVIC)
中断向量表位置 可通过VBAR寄存器设置 固定在0x08000000
中断号获取 需读取GICC_IAR寄存器 硬件自动获取
中断结束处理 需写入GICC_EOIR寄存器 硬件自动完成
中断分组 16个GPIO共用一个IRQ 每个EXTI独立IRQ
中断服务函数 需软件分发到具体处理函数 硬件直接跳转
中断嵌套 支持,通过优先级控制 支持,通过优先级控制

八、面试精选

题目1:GIC和NVIC有什么区别?

考察点: 中断控制器架构理解

参考答案:

  • 架构层面: GIC是ARM官方中断控制器IP核,适用于Cortex-A/R系列;NVIC是Cortex-M内核集成的中断控制器
  • 中断数量: GIC支持最多1020个中断ID(SPI+PPI+SGI);NVIC最多支持240个中断(STM32F103为81个)
  • 优先级: GIC支持256级优先级(8位);NVIC支持16级优先级(4位可配置)
  • 使用场景: GIC用于应用处理器(如IMX6ULL);NVIC用于微控制器(如STM32)
  • 中断向量表: GIC支持通过VBAR重定位;NVIC向量表固定在Flash起始地址

题目2:如何配置GPIO外部中断?

考察点: GPIO中断配置流程

参考答案:

  1. 配置引脚复用: 通过IOMUXC将引脚配置为GPIO功能
  2. 设置GPIO方向: 配置为输入模式(GDIR对应位清零)
  3. 配置中断触发方式: 通过ICR寄存器配置低电平/高电平/上升沿/下降沿触发
  4. 使能GPIO中断: 设置IMR寄存器对应位
  5. 使能GIC中断: 调用GIC_EnableIRQ使能对应中断号
  6. 注册中断服务函数: 调用system_register_irqhandler注册处理函数

题目3:中断处理函数中应该做什么?不应该做什么?

考察点: 中断编程规范

参考答案: 应该做:

  • 保存现场(硬件或软件完成)
  • 读取中断状态寄存器判断中断源
  • 执行必要的中断处理
  • 清除中断标志位
  • 恢复现场

不应该做:

  • 做耗时操作(如延时、大量计算)
  • 使用printf等输出函数
  • 等待事件或信号量
  • 动态分配内存
  • 调用可能阻塞的函数

题目4:GPIO1_IO16~IO31为什么共用一个中断号?

考察点: 中断资源管理

参考答案:

  • 硬件设计: IMX6ULL将GPIO1的IO16~IO31组合为一个中断源(GPIO1_Combined_16_31_IRQn,ID99)
  • 中断状态: 每个GPIO有独立的ISR状态位,可以通过读取ISR判断具体是哪个IO触发的中断
  • 中断使能: 每个GPIO有独立的IMR使能位
  • 软件处理: 在中断服务函数中需要遍历ISR判断具体中断源
  • 设计权衡: 减少中断号数量,同时保持足够的灵活性

题目5:中断向量表为什么需要重定位?

考察点: 中断向量表机制

参考答案:

  • 默认地址: Cortex-A7默认从0x00000000开始执行,中断向量表位于地址0x00
  • 实际场景: IMX6ULL的代码通常从0x87800000(SD卡)或0x80000000(NAND)启动
  • 问题: 如果中断向量表仍在0x00,CPU执行到0x00时会出错
  • 解决方案: 通过CP15协处理器的VBAR寄存器(c12)设置中断向量表基地址
  • 示例代码: set_vbar(0x87800000); 将中断向量表重定位到0x87800000

九、常见问题排查

问题现象 可能原因 排查方法 解决方案
中断不触发 GIC未使能 检查GICD_CTLR寄存器 确保GICD_CTLR[0]=1
中断无响应 GPIO中断未使能 检查IMR寄存器 确保IMR对应位=1
中断无响应 GIC中断未使能 检查GICD_ISENABLER 确保对应中断ID已使能
中断丢失 未清除中断标志 检查ISR寄存器 在ISR中写1清除
中断重复触发 中断源未处理 检查中断处理函数 确保处理所有中断源
系统死机 栈溢出 检查栈大小 增大栈空间
系统死机 中断嵌套过深 检查irqNesting 优化中断处理逻辑

十、参考资源

  • [[嵌入式Linux驱动开发实战/01-ARM架构与裸机编程/01-ARM架构概述]]
  • [[STM32学习笔记/02-GPIO寄存器详解与三步进化法]]
  • [[STM32学习笔记/04-GPIO输入与NVIC中断系统]]
  • 《I.MX6ULL参考手册》Section 28 (GPIO)
  • 《I.MX6ULL参考手册》Section 3.2 (Interrupts)
  • 《ARM Generic Interrupt Controller(ARM GIC) V2.0.pdf》
  • 《Cortex-A7 Technical Reference Manual》

代码来源: 裸机例程 9_int, bsp/int/, bsp/exit/ 最后更新: 2026-09-17