在嵌入式系统开发中,中断处理是连接硬件与软件的核心机制之一。本文将以NXP i.MX6UL处理器为例,结合具体代码,深入分析ARM Cortex-A7架构下的中断系统实现,涵盖启动代码、中断向量表、GIC(通用中断控制器)配置以及GPIO中断的完整流程。
一、启动代码中的中断向量表
任何ARM系统的中断处理都始于中断向量表。在start.S文件中,我们可以看到标准的Cortex-A7向量表定义:
assembly
.global _start
_start:
ldr pc, =_reset_handle /* 复位 */
ldr pc, =_undefine_handle /* 未定义指令 */
ldr pc, =_svc_handle /* 系统调用 */
ldr pc, =_prefetch_abort_handle /* 预取指中止 */
ldr pc, =_data_abort_handle /* 数据中止 */
ldr pc, =_reserved_handle /* 保留 */
ldr pc, =_irq_handle /* IRQ中断 */
ldr pc, =_fiq_handle /* FIQ快速中断 */
向量表占据32字节,每个表项是一条跳转指令。这里使用ldr pc, =label形式,能够实现32位地址范围的跳转,相比b指令更灵活。
二、复位处理与系统初始化
复位处理程序是整个系统的起点,代码在_reset_handle中完成了关键的初始化工作:
1. MMU和Cache控制
assembly
mrc p15,0,r0,c1,c0,0
bic r0, r0, #(1 << 0) /* 关闭MMU */
bic r0, r0, #(1 << 2) /* 关闭DCache */
bic r0, r0 , #(1 << 13) /* 关闭异常向量表重定位 */
orr r0, r0, #(1 << 12) /* 开启ICache */
mcr p15,0,r0,c1,c0,0
这里通过CP15协处理器操作:
-
位0(MMU):复位阶段关闭MMU,使用物理地址访问
-
位2(DCache):关闭数据缓存,避免初始化阶段的数据一致性问题
-
位13(V):关闭向量表重定位,确保向量表位于低地址0x0
-
位12(I):开启指令缓存,提升代码执行效率
2. 各模式栈指针设置
ARM Cortex-A7支持多种处理器模式,每种模式需要独立的栈空间:
assembly
/* 切换到IRQ模式,设置IRQ栈 */
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x12
msr cpsr, r0
ldr sp, =0x86000000
/* 切换到系统模式,设置系统栈 */
mrs r0, cpsr
bic r0, r0, #0x1F
orr r0, r0, #0x1F
msr cpsr, r0
ldr sp, =0x84000000
栈地址的选取需要考虑内存布局:
-
IRQ栈 :位于
0x86000000,中断处理需要独立的栈空间 -
系统栈 :位于
0x84000000,普通代码运行的栈空间
最后通过cpsie i开启IRQ中断,并调用_bss_clear清除未初始化数据段,最后跳转到main函数。
三、IRQ中断处理的实现
IRQ中断处理程序_irq_handle是中断系统的核心,完整的处理流程如下:
assembly
_irq_handle:
sub lr, lr, #4 /* 调整返回地址 */
stmfd sp!, {r0-r12, lr} /* 保存现场 */
/* 获取GIC基地址 */
mrc p15, 4, r1, c15, c0, 0
add r1, r1, #0x2000
ldr r0, [r1, #0x0c] /* 读取中断号 */
stmfd sp!, {r0, r1} /* 保存中断号、GIC地址 */
cps #0x1F /* 切换到系统模式 */
stmfd sp!, {lr} /* 保存系统模式LR */
bl system_interrupt_handler /* 调用中断处理函数 */
ldmfd sp!, {lr} /* 恢复系统模式LR */
cps #0x12 /* 切回IRQ模式 */
ldmfd sp!, {r0, r1} /* 恢复中断号、GIC地址 */
str r0, [r1, #0x10] /* 写EOIR,结束中断 */
ldmfd sp!, {r0-r12, pc}^ /* 恢复现场并返回 */
关键步骤解析:
-
sub lr, lr, #4:ARM流水线特性导致中断时的LR指向下一条指令的下一条,需要减4得到正确返回地址 -
现场保存 :使用
stmfd压栈保存通用寄存器和LR -
获取中断号 :通过CP15读取GIC基地址,加
0x2000得到GIC CPU接口基址,偏移0x0c读取当前中断号 -
模式切换:切换到系统模式调用中断处理函数,避免IRQ模式栈空间受限
-
中断结束 :将中断号写入
GICC_EOIR(偏移0x10)通知GIC中断处理完成 -
返回 :
ldmfd sp!, {r0-r12, pc}^中的^表示恢复时同时恢复CPSR
四、GIC与系统中断管理
现代ARM处理器采用GIC(Generic Interrupt Controller)管理中断,system_interrupt_init函数完成了关键配置:
c
void system_interrupt_init(void)
{
/* 设置向量异常表的起始地址 */
__set_VBAR(0x87800000);
/* GIC初始化 */
GIC_Init();
}
1. 向量表重定位
通过__set_VBAR将向量表从默认的0x0重定位到0x87800000。这是为什么?在实际系统中,RAM通常从0x80000000开始,0x87800000是链接脚本中指定的向量表存放地址。重定位后,CPU中断时会从这个地址获取跳转指令。
2. 中断向量表
系统定义了一个160项的向量表:
c
irq_handle_t vector_table[160];
IRQn_Type枚举定义了各个外设的中断编号,160项覆盖了i.MX6UL的所有中断源。中断发生时,system_interrupt_handler根据中断号查找并执行注册的处理函数:
c
void system_interrupt_handler(IRQn_Type irq)
{
if(irq < IOMUXC_IRQn || irq > PMU_IRQ2_IRQn)
return;
if(vector_table[irq] != NULL)
vector_table[irq]();
}
五、GPIO中断配置实例
以GPIO中断为例,展示完整的设备中断配置流程:
c
void gpio_init(..., GPIO_Type *gpio, int pin, gpio_config_t *config)
{
/* 1. 配置引脚复用功能 */
IOMUXC_SetPinMux(muxRegister, muxMode, inputRegister,
inputDaisy, configRegister, config->PinMux);
/* 2. 配置引脚电气特性 */
IOMUXC_SetPinConfig(muxRegister, muxMode, inputRegister,
inputDaisy, configRegister, config->PinConfig);
/* 3. 配置GPIO方向 */
if(config->mode_io == MODE_INPUT)
gpio->GDIR &= ~(1 << pin);
if(config->mode_io == MODE_OUTPUT)
gpio->GDIR |= (1 << pin);
/* 4. 配置中断 */
if(config->enable_interrupt == 1)
{
/* 注册中断处理函数 */
system_interrupt_register(config->IRQn, config->handler);
/* 配置触发方式 */
if(pin > 15)
gpio->ICR2 |= (config->mode_trigger << ((pin - 16) * 2));
else
gpio->ICR1 |= (config->mode_trigger << (pin * 2));
/* 使能GPIO中断 */
gpio->IMR |= (1 << pin);
/* 设置GIC中断优先级 */
GIC_SetPriority(config->IRQn, config->priority);
/* 使能GIC中断 */
GIC_EnableIRQ(config->IRQn);
}
}
配置流程体现了中断系统的分层结构:
-
引脚层:通过IOMUXC配置引脚功能
-
GPIO层:配置方向、中断触发方式、使能GPIO模块中断
-
GIC层:设置中断优先级、使能外设中断
GPIO读写操作
c
int gpio_read(GPIO_Type *gpio, int pin)
{
return (gpio->PSR && (1 << pin)) != 0;
}
void gpio_write(GPIO_Type *gpio, int pin, int value)
{
if(value == 0)
gpio->DR &= ~(1 << pin);
if(value == 1)
gpio->DR |= (1 << pin);
}
六、总结
通过以上代码分析,我们可以看到i.MX6UL中断系统的完整层次:
| 层次 | 组件 | 作用 |
|---|---|---|
| 硬件层 | GIC、GPIO | 中断信号的产生和分发 |
| 汇编层 | 向量表、现场保存/恢复 | 中断入口和退出 |
| 管理层 | system_interrupt_* | 中断注册和分发 |
| 设备层 | gpio_init | 具体外设中断配置 |
理解这一层次结构,对于嵌入式系统开发者来说至关重要。在调试中断相关问题时,可以按照这个层次逐一排查:先检查硬件引脚配置,再确认GPIO中断使能,然后验证GIC配置,最后检查向量表是否正确定位。这种结构化思维能显著提高调试效率。