ARM——中断(按键)
一、整体流程概览
- 调整返回地址并保存关键寄存器(stmfd sp!, {r0-r12, lr}入栈)
- 读取中断控制器信息(识别中断源)
- 保护临时数据并切换到 System 模式
- 调用 C 语言中断处理函数
- 切换回 IRQ 模式并恢复临时数据
- 清除中断标志(通知中断控制器处理完成)
- 恢复寄存器并从中断返回
第一部分:按键中断(中断源)配置:
- 配置触发方式:中断配置寄存器
ICR
(Interrupt Configuration Register)
GPIO1_ICR2:
GPIO1->ICR2 |= (3 << 4); - 解除屏蔽配置:中断屏蔽寄存器
IMR
(Interrupt Mask Register)
GPIO1_IMR:
GPIO1->IMR |= (1 << 18); - 中断判断:中断状态寄存器
ISR
(Interrupt Status Register)
GPIO1_ISR:
if((GPIO1->ISR & (1<<18)) != 0)
{led_nor();GPIO1->ISR |= (1 << 18);
}
第二部分:GIC设置core_ca7.h
(函数调用)
- GIC初始化
GIC_init(); - 使能IRQ中断
GIC_EnableIRQ(); - 中断优先级设置
GIC_SetPriority(99, 0); //99:中断号 - 获取GIC寄存器组基地址
mrc p15, 4, r0, c15, c0, 0 - 中断通知寄存器IAR
**C_IAR:base +offect(0x200C)** //基于基地址偏移0x200Cadd r0, r0, #0x2000ldr r1, [r0, #0x0C] //把r0地址的值(中断号)读到r1
- 中断结束寄存器
C_EOIR:base + offect(0x2010)
str, r1, [r0, #0x10]
第三部分:Kernal处理流程
- 异常向量表基地址重映射(顺带打开了 i cache)
V
位:bit13,Vector bit;
I
位:bit12,Istruction cache enable bit;
mrc p15,0,r0,c1,c0,0 //读取SCLTR的 V 位和 I 位到r0寄存器
bicr0,r0,#(1<<13) //V位清零
orr r0,r0,#(1<<12) // I 位置1
mcr p15,0,r0,c1, c0,0 //写回cp15协处理器__set_VBAR(0x87800000)
- 异常向量的处理函数
__IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register /
IAR:中断通知寄存器
__OM uint32_t C_EOIR; /!< Offset: 0x2010 ( /W) End Of Interrupt Register */
EOIR:中断结束寄存器
_irq_handler_:sub lr, lr, #4stmfd sp!, {r0-r12, lr}mrc p15, 4, r1, c15, c0, 0 //读取CP15协处理器的c15寄存器到r1,此处:读取中断控制器的基地址偏移值到 r1add r1, r1, #0x2000 //计算**中断通知寄存器**的实际基地址(r1 原价值 + 0x2000 偏移)ldr r0, [r1, #0x0C] //将r1地址的0x0C偏移量的中断ID读到r0寄存器,(r0用于后续中断处理函数传入的参数)stmfd sp!, {r0, r1} //保护存储中断号的r0,控制器基地址r1cps #0x1F //切换到system模式(0x1F是system模式的模式码)stmfd sp!, {lr} //保护sysytem模式的lr,因为后续调用C语言函数修改lr,确保模式切换回来时能恢复bl system_interrupt_handler //r0中的中断号传回中断处理函数,执行C语言中的中断处理函数ldmfd sp!, {lr}cps #0x12 //切换到irq模式ldmfd sp!, {r0,r1}str r0, [r1, #0x10] //r0中存储的中断号写入r1地址的0x10偏移位,清除中断标志,避免中断被重复触发ldmfd sp!, {r0-r12, pc}^ //从栈中恢复被中断程序的 r0-r12 寄存器,并通过 pc 跳回中断前的执行位置,实现中断返回
特殊修饰符
^
:含义是 “恢复用户模式的寄存器”(而非当前 IRQ 模式的专用寄存器),同时自动将
SPSR(保存的程序状态寄存器)的值恢复到 CPSR(当前程序状态寄存器),完成状态恢复
- 中断处理函数的封装
typedef void (*irq_handler_t)(void);
irq_handler_t iVector_table[160] = {0};
int system_interrupt_register(IRQn_Type irq, irq_handler_t handler)
{if (irq < IOMUXC_IRQn || irq > PMU_IRQ2_IRQn){return -1;}if(handler == NULL){return -1;}iVector_table[irq] = handler;return 0;
}void system_interrupt_handler(IRQn_Type irq)
{if(iVector_table[irq] != NULL){iVector_table[irq]();}
}void key_irq_handler(void)
{if((GPIO1->ISR & (1<<18)) != 0){led_nor();GPIO1->ISR |= (1 << 18); }
}