)
1. 中断的基本概念中断是嵌入式系统中一种重要的机制它允许外设或软件在需要时暂停 CPU 当前正在执行的任务转而处理一个更紧急的事件。处理完该事件后CPU 再返回到被暂停的任务继续执行。可以把中断想象成一个电话铃声你正在写代码执行主程序突然电话响了中断请求你停下手中的工作去接电话执行中断服务函数接完电话后继续写代码恢复现场。1.1 中断处理流程一个完整的中断处理流程通常包含以下步骤中断源发出中断请求外设如按键、定时器、串口检测到特定事件向 CPU 发送一个中断信号。CPU 检查中断响应条件CPU 在当前指令执行完毕后检查是否有中断请求以及该中断是否被屏蔽全局中断使能位或中断屏蔽寄存器。中断优先级比较如果同时有多个中断请求中断控制器如 GIC会比较它们的优先级选择优先级最高的中断进行处理。保护现场CPU 自动或通过软件将当前正在执行的程序的上下文如 PC、LR、通用寄存器等保存到栈中以便中断返回后能恢复执行。执行中断服务函数CPU 跳转到中断向量表中对应的入口地址执行中断服务函数ISR。恢复现场中断服务函数执行完毕后从栈中恢复之前保存的上下文并执行中断返回指令使 CPU 回到被中断的程序继续执行。2. GIC通用中断控制器介绍在 ARM Cortex-A7 架构的 i.MX6ULL 处理器中中断管理由GICGeneric Interrupt Controller通用中断控制器负责。GIC 是连接外设中断源和 CPU 核心的桥梁负责中断的使能、屏蔽、优先级排序和分发。2.1 GIC 版本与 i.MX6ULLGIC V1用于 Cortex-A5 等早期处理器。GIC V2用于 32 位处理器如 Cortex-A7、Cortex-A9。i.MX6ULL 使用的就是 GIC V2。GIC V3 - V4用于 64 位处理器如 Cortex-A53手机芯片。GIC V5重新架构用于数百核处理器服务器、高算力芯片。2.2 GIC 支持的中断类型GIC 支持多种中断类型i.MX6ULL 的 GIC 最多可支持 1020 个中断但实际只使用了 0 - 159 号共 160 个。主要分为三类SGISoftware Generated Interrupt软件中断编号 0 - 15由软件通过写寄存器触发常用于核间通信。PPIPrivate Peripheral Interrupt私有外设中断编号 16 - 31每个 CPU 核心私有的中断如定时器、看门狗等。SPIShared Peripheral Interrupt共享外设中断编号 32 - 1019由外部设备如 GPIO、UART、I2C产生的中断所有 CPU 核心都可以处理。2.3 GIC 关键寄存器在汇编代码中我们通过协处理器CP15来获取 GIC 的基地址并操作其关键寄存器GIC_IARInterrupt Acknowledge Register读取该寄存器可以获取当前发生的中断号。GIC_EOIREnd Of Interrupt Register中断服务函数执行完毕后向该寄存器写入中断号通知 GIC 中断处理结束。3. i.MX6ULL 配置按键中断的完整流程下面我们以 i.MX6ULL 开发板上的按键Key0连接到 GPIO1_IO18为例详细讲解如何配置一个下降沿触发的中断。3.1 硬件连接按键 Key0 连接到GPIO1_IO18引脚。按键按下时引脚电平从高电平变为低电平下降沿。按键松开时引脚电平从低电平变为高电平上升沿。目标当按键按下时触发中断在中断服务函数中点亮 LED。3.2 软件配置步骤配置一个 GPIO 中断需要完成以下步骤配置引脚复用功能将UART1_CTS_B引脚复用为GPIO1_IO18。配置引脚电气属性设置上下拉、驱动能力等。配置 GPIO 方向为输入GPIO1-GDIR寄存器对应位清零。配置中断触发方式设置GPIO1-ICR2寄存器选择下降沿触发。使能 GPIO 引脚中断设置GPIO1-IMR寄存器对应位为 1。初始化 GIC调用GIC_Init()函数。在 GIC 中使能该中断调用GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn)。编写中断服务函数在system_interrupt_handler中处理中断。编写汇编启动代码设置异常向量表、切换模式、设置栈指针等。4. 代码实现4.1 按键初始化函数 (key.c)#includekey.h#includeMCIMX6Y2.h#includefsl_iomuxc.h#includecore_ca7.hvoidkey_init(void){// 1. 引脚复用: 将 UART1_CTS_B 复用为 GPIO1_IO18IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);// 2. 电气属性: 设置上下拉、驱动能力等 (0xF0B0 为具体配置值)IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF0B0);// 3. GPIO方向: 设置为输入模式 (GDIR 对应位清零)GPIO1-GDIR~(118);// 4. 配置中断触发方式: 下降沿触发 (ICR2 对应位设置为 11)GPIO1-ICR2|(34);// 5. 使能GPIO1_18引脚中断 (IMR 对应位置1)GPIO1-IMR|(118);// 6. GIC使能中断GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);}4.2 主函数 (main.c)#includeMCIMX6Y2.h#includefsl_iomuxc.h#includecore_ca7.h#includeled.h#includebeep.h#includekey.hvoidenable_clock(void){// 使能所有外设时钟CCM-CCGR00xFFFFFFFF;CCM-CCGR10xFFFFFFFF;CCM-CCGR20xFFFFFFFF;CCM-CCGR30xFFFFFFFF;CCM-CCGR40xFFFFFFFF;CCM-CCGR50xFFFFFFFF;CCM-CCGR60xFFFFFFFF;}voidsystem_interrupt_init(void){// 初始化 GICGIC_Init();}// 系统中断服务函数voidsystem_interrupt_handler(intnum){// 判断是否为 GPIO1_Combined_16_31 中断if(numGPIO1_Combined_16_31_IRQn){// 判断是否为 GPIO1_IO18 引脚产生的中断if((GPIO1-ISR(118))!0){// 点亮 LEDled_on();// 清除中断标志位 (向 ISR 对应位写1)GPIO1-ISR|(118);}}}voiddelay(unsignedintn){while(n--);}intmain(void){// 1. 使能外设时钟enable_clock();// 2. GIC初始化system_interrupt_init();// 3. LED初始化led_init();// 4. 蜂鸣器初始化beep_init();// 5. 按键初始化 (配置中断)key_init();// 初始状态: 关闭LED和蜂鸣器led_off();beep_off();while(1){// 主循环: 等待中断发生delay(0xFFFFFFFF);}return0;}4.3 汇编启动文件 (start.S)汇编代码负责最底层的初始化工作包括设置异常向量表、切换 CPU 工作模式、设置栈指针、使能中断等。.global _start _start: ldr pc, _reset_handler ldr pc, _undefine_handler ldr pc, _svc_handler ldr pc, _prefetch_aboart_handler ldr pc, _data_abort_handler ldr pc, _reserved_handler ldr pc, _irq_handler ldr pc, _fiq_handler _fiq_handler: ldr pc, _fiq_handler _irq_handler: // 1. 修正返回地址: IRQ模式下LR保存的是当前指令4需要减4 subs lr, lr, #4 // 2. 获取GIC基地址 (通过CP15协处理器读取CBAR寄存器) mrc p15, 4, r1, c15, c0, 0 add r1, r1, #0x2000 // 3. 读取GIC_IAR寄存器获取中断号 ldr r0, [r1, #0x0C] // 4. 保护现场: 保存寄存器到栈中 stmfd sp!, {r0-r12, lr} stmfd sp!, {r0, r1} // 5. 切换到System模式以便使用System模式的栈 cps #0x1F stmfd sp!, {lr} // 6. 调用C语言中断服务函数参数为中断号 bl system_interrupt_handler // 7. 恢复现场 ldmfd sp!, {lr} cps #0x12 // 切换回IRQ模式 ldmfd sp!, {r0, r1} // 8. 写GIC_EOIR寄存器通知GIC中断处理结束 str r0, [r1, #0x10] ldmfd sp!, {r0-r12, pc}^ _reserved_handler: ldr pc, _reserved_handler _data_abort_handler: ldr pc, _data_abort_handler _prefetch_aboart_handler: ldr pc, _prefetch_aboart_handler _svc_handler: ldr pc, _svc_handler _undefine_handler: ldr pc, _undefine_handler _reset_handler: // 1. 切换工作模式到IRQ模式 cps #0x12 // 2. 使能IRQ中断 (CPSR的I位清零) cpsie i // 3. 设置IRQ模式下的栈指针 ldr sp, 0x86000000 // 4. 切换工作模式到System模式 cps #0x1F // 5. 设置System模式下的栈指针 ldr sp, 0x84000000 // 6. 清空BSS段 bl _bss_clear // 7. 打开I-Cache (指令缓存) bl _enable_icache // 8. 设置异常向量表重映射地址 bl _set_vbar // 9. 跳转到main函数 b main b finished _enable_icache: mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #(1 13) // 清除V位 orr r0, r0, #(1 12) // 设置I位 mcr p15, 0, r0, c1, c0, 0 bx lr _set_vbar: ldr r0, 0x87800000 mcr p15, 0, r0, c12, c0, 0 bx lr _bss_clear: ldr r0, __bss_start ldr r1, __bss_end loop: mov r2, #0 str r2, [r0] add r0, r0, #4 cmp r0, r1 blt loop bx lr finished: b finished5. 代码详解5.1 中断服务函数执行流程当按键按下时整个中断处理流程如下外设GPIO1_IO18检测到下降沿向 GIC 发送中断信号。GIC接收中断经过优先级判断后向 CPU 核心发送 IRQ 信号。CPU 核心响应 IRQ自动切换到 IRQ 模式并跳转到异常向量表中的_irq_handler。_irq_handler汇编修正返回地址subs lr, lr, #4。通过 CP15 协处理器获取 GIC 基地址。读取GIC_IAR寄存器获取中断号。保护现场保存寄存器到栈。切换到 System 模式调用 C 函数system_interrupt_handler。system_interrupt_handlerC 语言判断中断号是否为GPIO1_Combined_16_31_IRQn。判断具体中断源是否为GPIO1_IO18通过GPIO1-ISR寄存器。执行中断处理点亮 LED。清除中断标志位向GPIO1-ISR写 1。返回汇编恢复现场。向GIC_EOIR寄存器写入中断号通知 GIC 处理完毕。执行ldmfd sp!, {r0-r12, pc}^指令从 IRQ 模式返回恢复被中断的程序继续执行。5.2 关键点说明GIC_IAR和GIC_EOIR这两个寄存器是 GIC 编程的核心。IAR用于获取中断号EOIR用于告知 GIC 中断处理结束。在汇编代码中GIC_IAR的偏移地址是0x0CGIC_EOIR的偏移地址是0x10。GPIO1-ISR寄存器当 GPIO 引脚检测到配置的中断事件如下降沿时ISR寄存器的对应位会自动置 1。在中断服务函数中必须通过向该位写 1 来清除中断标志否则会一直触发中断。cpsie i指令用于使能 CPU 的 IRQ 中断。如果不执行这条指令即使外设和 GIC 都配置好了CPU 也不会响应中断。栈指针设置IRQ 模式和 System 模式需要分别设置栈指针因为它们在发生中断时会使用不同的栈空间。6. 总结本文详细介绍了 i.MX6ULL 处理器上按键中断的完整配置流程从中断的基本概念、GIC 中断控制器的工作原理到具体的代码实现和汇编启动代码。通过这个例子可以清晰地理解 ARM Cortex-A7 架构下中断处理的完整链路外设 - GIC - CPU - 异常向量表 - 汇编处理 - C 语言服务函数。