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) 等待下一次中断

相关推荐
森G2 分钟前
STM32F103C8T6工程---标准库版usart2写回显
stm32·单片机
EVERSPIN17 分钟前
基于MCU CH32X035 Type-C PD显示器方案
单片机·mcu·计算机外设
Z文的博客1 小时前
FLASHDB实战详解 - 嵌入式KV/TSD数据库开发全攻略
stm32·单片机·嵌入式·flash·flashdb·w25q256
SUNNYSPY0012 小时前
120R016-ASEMI解锁电力电子的效率革命
单片机
芯希望2 小时前
芯伯乐XOPA340/XOPA2340/XOPA4340系列11MHz低噪声CMOS运放,高性能与低功耗的理想平衡
单片机·嵌入式硬件·dc-dc·工业控制·国产替代·电源管理·xblw芯伯乐
LCMICRO-133108477462 小时前
长芯微LCMDC8588完全P2P替代ADS8588,是一款16位、8通道同步采样的逐次逼近型(SAR)模数转换器
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·模数转换器
誰能久伴不乏2 小时前
SPI总线通信协议基础与ICM20607传感器驱动开发指南
arm开发·c++·驱动开发·嵌入式硬件·arm
VBsemi-专注于MOSFET研发定制3 小时前
面向车载冰箱高效可靠需求的功率器件选型策略与器件适配手册
单片机
进击的小头3 小时前
第17篇:嵌入式通用串行外设:UART_SPI_I2C接口原理与外设扩展应用
单片机·嵌入式硬件
LCG元3 小时前
STM32实战:基于FreeRTOS的LVGL嵌入式GUI移植(智能手表界面)
stm32·嵌入式硬件·智能手表