ARM嵌入式学习(五)---IMX6ULL外部中断

目录

一、中断源三大分类

二、相关专业名词解析

三、异常向量表

关键点(极其重要)

四、中断完整真实流程

[阶段 1:上电 → 复位处理(_reset_handler)](#阶段 1:上电 → 复位处理(_reset_handler))

[阶段 2:main 里做了什么](#阶段 2:main 里做了什么)

核心动作

[阶段 3:按键按下 → 真正中断触发(硬件自动做)](#阶段 3:按键按下 → 真正中断触发(硬件自动做))

[阶段 4:进入汇编 _irq_handler](#阶段 4:进入汇编 _irq_handler)

[阶段 5:C 语言中断分发函数(完整代码)](#阶段 5:C 语言中断分发函数(完整代码))

核心意义

[阶段 6:从中断 C 函数返回汇编](#阶段 6:从中断 C 函数返回汇编)

[告诉 GIC:中断处理完了](#告诉 GIC:中断处理完了)

最后一步:真正返回主程序

[五、中断服务函数表 vs 异常向量表(彻底区分)](#五、中断服务函数表 vs 异常向量表(彻底区分))

异常向量表(硬件层)

中断服务函数表(软件层)

两者关系

[六、最容易踩的 7 个坑(必须注意)](#六、最容易踩的 7 个坑(必须注意))

七、一句话总结整个中断流程

一、中断源三大分类

  1. SGI(Software Generated Interrupt)软件触发中断

    • 中断号:0~15
    • 来源:软件写寄存器触发,不是硬件
    • 用途:多核 CPU 间相互打断、任务调度
  2. PPI(Private Peripheral Interrupt)私有外设中断

    • 中断号:16~31
    • 来源:每个 CPU 自己独有的硬件
    • 例如:CPU 内部定时器、CPU 看门狗
  3. SPI(Shared Peripheral Interrupt)共享外设中断

    • 中断号:32~159
    • 来源:所有外部设备(GPIO、UART、LCD、定时器等)
    • 本次学习的中断 GPIO1_IO18 就属于这一类

二、相关专业名词解析

名词 全称 核心作用
IRQ 普通中断请求 所有外设共用唯一硬件中断入口
GIC 通用中断控制器 管理 160 个中断、提供中断号、接收结束信号
VBAR 向量基地址寄存器 告诉 CPU 向量表存放地址(0x87800000)
CPSR 当前程序状态寄存器 保存 CPU 模式、中断开关、状态标志
SPSR 备份程序状态寄存器 中断时自动保存 CPSR,返回时恢复
LR(R14) 链接寄存器 保存中断返回地址,需硬件修正
PC(R15) 程序计数器 指向当前取指指令,中断时强制跳转
CP15 系统协处理器 配置 VBAR、读取 GIC 基地址、控制 Cache

三、异常向量表

复制代码
.global _start
_start:
    ldr pc, =_reset_handler          // 0x00 复位
    ldr pc, =_undef_handler          // 0x04 未定义指令
    ldr pc, =_software_handler       // 0x08 软中断 SWI
    ldr pc, =_prefetch_abort_handler // 0x0C 取指令失败
    ldr pc, =_data_abort_handler     // 0x10 读/写数据失败
    nop                              // 0x14 保留
    ldr pc, =_irq_handler            // 0x18 所有外设中断统一入口
    ldr pc, =_fiq_handler            // 0x1C 快速中断

关键点(极其重要)

  • 这 8 个入口是 ARM 架构硬件规定死的,顺序不能变
  • 每个入口占 4 字节
  • 所有外部设备(GPIO、UART 等)全部共用同一个入口:0x18 的 irq_handler
  • 硬件只认这张表,不认你的 C 语言中断表

四、中断完整真实流程

阶段 1:上电 → 复位处理(_reset_handler)

复制代码
_reset_handler:
    cpsid i                // 关中断,初始化期间不允许被打断
    ldr sp, =0x81000000    // 设置 SVC 模式栈

    // 配置 CP15:打开 I-Cache、打开向量表重映射
    mrc p15, 0, r1, c1, c0, 0
    orr r1, r1, #< 12) // 打开 I-Cache
    bic r1, r1, #(< 13) // 清除 V 位,让 VBAR 生效
    mcr p15, 0, r1, c1, c0, 0

    cps #0x12              // 切换到 IRQ 模式
    ldr sp, =0x82000000    // 设置 IRQ 模式专用栈

    cps #0x1f              // 切换到 System 模式
    ldr sp, =0x83000000    // 设置 C 语言用的栈

    cpsie i                // 打开总中断!
    bl main                // 进入 C 语言 main
  • 关中断 :初始化不能被打断
  • 设置独立栈 :给每个模式设置独立栈
    • SVC 栈:0x81000000
    • IRQ 栈:0x82000000
    • System 栈:0x83000000
  • 配置 CP15:打开 VBAR 重映射,清除 SCTLR.V (bit13),否则 VBAR 设置无效
  • 开总中断:cpsie i,打开 CPSR.I 位,从此 CPU 可以响应外设中断

阶段 2:main 里做了什么

复制代码
int main(void)
{
    system_irq_init();     // GIC 初始化 + VBAR 重映射
    ccm_ccgr_enable();     // 打开所有时钟
    led_init();
    beep_init();
    key_irq_init();        // 配置 GPIO1_IO18 为中断输入

    // 重点:把"中断号"和"处理函数"绑定
    request_irq(GPIO1_IO18_IRQ, gpio1_io18_handler);

    while(1){}  // 等待中断触发
}
核心动作
  • VBAR=0x87800000,向量表放在这里
  • GIC 初始化,打开 GIC 控制器
  • request_irq 注册函数,把 GPIO1_IO18 对应的处理函数存入数组
  • while (1),等待硬件触发中断

阶段 3:按键按下 → 真正中断触发(硬件自动做)

当你按键 → GPIO1_IO18 产生中断信号 → 发给 GIC → GIC 发给 CPU硬件自动做 4 件事:

  1. 把 CPSR 复制到 SPSR_irq
  2. 把 当前 PC 下一条地址 存入 LR_irq
  3. 把 CPU 模式 强制切换到 IRQ 模式
  4. 强制 PC 跳转到 VBAR + 0x18,也就是跳转到 _irq_handler

阶段 4:进入汇编 _irq_handler

复制代码
_irq_handler:
  1. 修正返回地址

    sub lr, lr, #4

  • 为什么?ARM 三级流水线,中断到来时 PC 已预取。硬件保存的 LR 是 下下条指令地址,不减 4,返回会跳错地址、跑飞、死机
异常类型 进入时的 lr 值 需修正量 目的
IRQ(普通中断) PC + 4 减 4 返回被中断的指令,确保主程序正常续行
FIQ(快速中断) PC + 4 减 4 返回被中断的指令,快速中断专用修正
预取中止(Prefetch Abort) PC + 4 减 4 返回被中止的指令,重试取指操作,恢复程序执行
数据中止(Data Abort) PC + 8 减 8 返回被中止的指令,可重新访问内存,修复数据访问异常
SVC(软中断) PC + 4 不减 返回下一条指令,软中断执行完成后正常续行
未定义指令(Undefined) PC + 4 不减 返回下一条指令,跳过未定义指令,避免程序卡死
复位(Reset) 无定义 不复位返回,复位为系统启动入口,无返回逻辑
  1. 保存现场(压栈 R0~R12 + LR)

    stmfd sp!, {r0-r12, lr}

  • 中断里会用到这些寄存器,必须保存,否则主程序寄存器被破坏
  1. 读取 GIC 基地址(CP15)

    mrc p15, 4, r1, c15, c0, 0

  • 从 CP15 拿到 GIC CPU 接口基地址,这是 IMX6ULL 规定的读取方式
  1. 偏移到 GIC 寄存器区域

    add r1, r1, #0x2000

  2. 读取 IAR 寄存器,获取中断号

    ldr r0, [r1, #0xC]

  • IAR 偏移是 0xC,读取后 R0 = 真实中断号,只有 GIC 能告诉你:到底是谁触发的中断
  1. 把中断号和 GIC 地址压栈

    stmfd sp!, {r0,r1}

  2. 切换到 System 模式(0x1F)

    cps #0x1f

  • IRQ 模式栈小,不能直接调用 C 函数,C 语言函数必须在 System 或 SVC 模式
  1. 再次保存现场(进 C 函数前)

    stmfd sp!, {r0-r12, lr}

  2. 调用 C 语言统一中断入口

    bl system_irq_handler

  • 跳转到 C 函数,中断号 R0 会作为参数传进去

阶段 5:C 语言中断分发函数(完整代码)

复制代码
// 两张中断表(核心全局变量)
static irq_handler_t irq_handler_array[160];       // 普通外设中断服务函数表
static irq_handler_t irq_gpio_handler_array[160];  // GPIO 专用中断服务函数表

// 所有中断统一 C 入口(汇编跳转至此)
void system_irq_handler(IRQn_Type irq_num)
{
    switch(irq_num)
    {
        // GPIO1 组合中断(0~15、16~31 引脚共用两个中断号)
        case GPIO1_Combined_0_15_IRQn:
        case GPIO1_Combined_16_31_IRQn:
        {
            // 判断是否是 GPIO1_IO18 引脚触发中断
            if (GPIO1->ISR & (1< 18))
            {
                // 调用注册好的 GPIO1_IO18 处理函数
                irq_gpio_handler_array[GPIO1_HANDLER_BASE + 18]();
                // 清除 GPIO 中断标志位(必须!否则反复进中断)
                GPIO1->ISR |= (< 18);
            }
        }
        break;

        // 普通外设中断(UART、Timer、LCD 等)
        default:
            if(irq_handler_array[irq_num])  // 判断是否注册了处理函数
                irq_handler_array[irq_num](); // 调用对应的中断处理函数
        break;
    }
}

// 中断注册 API(绑定中断号与处理函数)
int request_irq(int irq_num, irq_handler_t handler)
{
    // 参数校验:中断号范围、处理函数不为空
   < IOMUXC_IRQn) || (irq_num > GPIO_IRQ_MAX))
        return -1;
    if(!handler)
        return -1;

    switch (irq_num)
    {
    // GPIO1 0~15 引脚(共用 GPIO1_Combined_0_15_IRQn 中断号)
    case GPIO1_IO0_IRQ ... GPIO1_IO15_IRQ:
        GIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);          // 使能 GIC 对应中断
        GIC_SetPriority(GPIO1_Combined_0_15_IRQn, 0);     // 设置中断优先级为 0
        irq_gpio_handler_array[irq_num - GPIO_IRQ_BASE - 1] = handler; // 绑定函数
        break;

    // GPIO1 16~31 引脚(共用 GPIO1_Combined_16_31_IRQn 中断号)
    case GPIO1_IO16_IRQ ... GPIO1_IO31_IRQ:
        GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);         // 使能 GIC 对应中断
        GIC_SetPriority(GPIO1_Combined_16_31_IRQn, 0);    // 设置中断优先级为 0
        irq_gpio_handler_array[irq_num - GPIO_IRQ_BASE - 1] = handler; // 绑定函数
        break;

    // 普通外设中断(直接用中断号绑定)
    default:
        GIC_EnableIRQ(irq_num);              // 使能 GIC 对应中断
        GIC_SetPriority(irq_num, 0);         // 设置中断优先级为 0
        irq_handler_array[irq_num] = handler; // 绑定函数
        break;
    }
    return 0;
}
核心意义
  • 根据中断号查找对应的处理函数,实现中断分发
  • GPIO 是 组合中断(多个引脚共用一个中断号),必须读 ISR 寄存器判断具体触发引脚
  • 必须清除 GPIO 的 ISR 中断标志位,否则会反复触发中断
  • 普通外设中断可直接通过中断号查表,调用对应处理函数

阶段 6:从中断 C 函数返回汇编

复制代码
ldmfd sp!, {r0-r12, lr}   // 恢复进入 C 函数前保存的现场
cps #0x12                 // 切换回 IRQ 模式(准备后续返回主程序)
ldmfd sp!, {r0, r1}       // 恢复中断号(r0)和 GIC 基地址(r1)
告诉 GIC:中断处理完了
复制代码
str r0, [r1, #0x10]
  • 偏移 0x10 是 EOIR(End of Interrupt,中断结束寄存器)
  • 不写这句:GIC 会认为中断还没处理完,后果是再也进不了下一次中断
最后一步:真正返回主程序
复制代码
ldmfd sp!, {r0-r12, pc}^
  • 恢复所有寄存器(r0~r12、lr)
  • ^ 符号作用:自动把 SPSR_irq 的值写回 CPSR
  • 同时完成:恢复原来的 CPU 模式、自动打开中断、PC 跳回被打断的位置(回到 main 函数的 while (1))

五、中断服务函数表 vs 异常向量表(彻底区分)

异常向量表(硬件层)

  • 共 8 个入口,ARM 架构硬件规定死,顺序不可变
  • 硬件自动跳转,所有外设中断共用同一个 IRQ 入口(0x18)
  • 作用:告诉 CPU 中断来了该跳转到哪里(只区分中断类型,不区分具体外设)

中断服务函数表(软件层)

  • 核心代码:

    复制代码
    irq_handler_array[160];       // 普通外设中断表(160个表项,对应0~159中断号)
    irq_gpio_handler_array[160];  // GPIO 专用中断表(区分组合中断的具体引脚)
  • 160 个函数指针,下标 = 中断号(或引脚对应偏移)

  • 作用:区分具体是哪个外设 / 引脚触发中断,调用对应的处理函数

两者关系

硬件向量表 → 统一 IRQ 入口(汇编 _irq_handler)→ 读 GIC 中断号 → 查软件中断服务函数表 → 执行对应 C 处理函数

六、最容易踩的 7 个坑(必须注意)

  1. LR 必须减 4sub lr, lr, #4 不可省略,否则返回地址错误,主程序跑飞、死机
  2. EOIR 必须写str r0, [r1, #0x10] 不可省略,否则 GIC 认为中断未结束,无法再次触发中断
  3. 必须清除外设中断标志:GPIO 的 ISR 寄存器必须写 1 清除,否则会无限触发中断
  4. 调用 C 函数前必须切 System 模式:IRQ 模式栈空间小,直接调用 C 函数会导致栈溢出
  5. 不同模式必须用不同栈:SVC、IRQ、System 模式需配置独立栈地址,否则会破坏寄存器数据
  6. CP15 的 V 位必须清 0bic r1, r1,< 13) 不可省略,否则 VBAR 重映射无效,CPU 找不到向量表
  7. 向量表必须 4 字节对齐、地址连续:ARM 硬件要求,否则向量表无法正常跳转

七、一句话总结整个中断流程

按键触发 → GIC 接收 → 硬件跳向量表 → 汇编保存现场 → 读中断号 → C 语言查表执行处理函数 → 清除外设中断标志 → 告诉 GIC 中断结束 → 恢复现场 → 返回 main 函数 while (1) 等待下一次中断

相关推荐
无人机9012 小时前
Delphi网络编程实战:UDP通信与多线程网络优化详解
单片机·嵌入式硬件
笨笨饿2 小时前
博客目录框架
c语言·开发语言·arm开发·git·嵌入式硬件·神经网络·编辑器
KOYUELEC光与电子努力加油3 小时前
BROADCOM博通集成 Matter 1.5平台认证就绪、BK7239N等芯片助力智能家居无缝融合
服务器·科技·单片机·智能家居
Lugas Luo3 小时前
SATA Port Multiplier (SATA 集线器) 原理与驱动架构深度剖析
linux·嵌入式硬件
zmj3203243 小时前
芯片的ISP在系统编程-模式
单片机·嵌入式开发
zmj3203243 小时前
KW45的ISP模式
stm32·单片机·嵌入式硬件·kw45
小美单片机4 小时前
十字路交通灯系统设计
c语言·单片机·51单片机·proteus·课设
AzusaFighting4 小时前
STM32F103R基于AI生成的HAL库DMA串口应用用例
stm32·单片机·嵌入式硬件
Lugas Luo4 小时前
Kernel 5.10 ATA 驱动分析与车载环境诊断
linux·嵌入式硬件