以按键为例:
按键按下,中断发生,经过异常向量表跳转到IRQ_handle函数,IRQ_handle 在汇编启动代码start.s中定义。
IRQ_handle函数:
IRQ_handle:
// 设置IRQ模式下的栈
ldr sp, =IRQ_STACK
// 保存LR
// 因为ARM有流水线,所以PC的值会比真正执行的代码+8,
sub lr, lr, #4
// 保存r0-r12和lr到irq模式下的栈上面
stmfd sp!, {r0-r12, lr}
// 调用irq_handler函数(这是真正处理中断程序的地方)
bl irq_handler
// 现场恢复
ldmfd sp!, {r0-r12, pc}
IRQ_handle做了四件事:
1.设置IRQ模式下的栈
2.现场保护:保存下一个要执行的地址到LR以便返回,保存r0-r12到栈,保存CPSR到SPSR(自动)
3.转到irq_handler 执行ISR(中断服务程序)
4.现场恢复:LR中保存的地址给PC,栈中保存的r0–r12覆盖当r0–r12,SPSR保存的值恢复到CPSR(自动)
irq_handler函数:Soc支持很多中断,所以中断irq都会到这个函数,要在该函数里判断哪个中断发生了,然后调用ISR。
VICnADDR寄存器:发生中断后,硬件自动将该中断的IRQ地址放到这个寄存器。
VICVECTADDR寄存器:存放ISR地址,一共4*32个,每个中断源都有一个VICVECTADDR寄存器。
void irq_handler(void)
{
unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
void (*isr)(void) = NULL;
for(i=0; i<4; i++)
{
// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0。通过判断VICnIRQSTATUS的值确定那个VIC发生了中断
if(intc_getvicirqstatus(i) != 0) //
{
isr = (void (*)(void)) vicaddr[i];//发生中断时硬件自动将ISR首地址放入VICnADDR寄存器,此时isr函数指针指向了ISR
break;
}
}
(*isr)(); // 通过函数指针来调用函数
}
intc_getvicirqstatus函数:辅助型函数,返回VICnIRQSTATUS寄存器的值供irq_handler函数使用
//仅给出VIC0IRQSTATUS参考
#define VIC0_BASE (0xF2000000)
#define VIC0IRQSTATUS ( *((volatile unsigned long *)(VIC0_BASE + 0x00)) )
unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{
if(ucontroller == 0)
return VIC0IRQSTATUS;
else if(ucontroller == 1)
return VIC1IRQSTATUS;
else if(ucontroller == 2)
return VIC2IRQSTATUS;
else if(ucontroller == 3)
return VIC3IRQSTATUS;
else
{}
return 0;
}
绑定函数:绑定自己写的ISR到VICnVECTADDR寄存器,绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理,等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可(上面 irq_handler函数已经实现)。
// 中断源编号 自己写的IRQ函数
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
//先确定该中断在哪个分区
//VIC0
if(intnum<32)
{
//然后把IRQ函数首地址 赋给 VICnVECTADDR寄存器
*( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;
}
//VIC1
else if(intnum<64)
{
*( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
}
//VIC2
else if(intnum<96)
{
*( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
}
//VIC3
else
{
*( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
}
return;
}
使用:
#define NUM_EINT2 (2) //硬件设定的中断源编号
#define KEY_DOWN NUM_EINT2 //以按键为例,该按键对应的外部中断查原理图即可
intc_setvectaddr(KEY_DOWN, IRQ); //绑定IRQ到VICnVECTADDR寄存器
其他重要的 中断相关寄存器:
VICnINTENCLEAR 禁止中断源
VIC0INTENABLE 使能中断源
VICnINTSELECT 选择中断类型,比如IRQ中断
VICnADDR 清理这个寄存器,以便硬件将ISR地址放进去(不清貌似也可)
使用按键中断还要初始化 外部中断 的相关寄存器,步骤如下:
1.查找原理图,确定按键的GPIO引脚。
2.GPxxCON 设置GPxxCON寄存器相应位为外部中断模式
3.EXT_INT_n_CON 设置触发方式(电平,边沿触发)
4.EXT_INT_n_PEND 清除中断挂起寄存器,(来中断时硬件会自动将相应位置1,执行完IRQ需要手动置0)
5.EXT_INT_n_MASK 使能相应的外部中断
6.写IRQ函数
以上为学习朱有鹏老师的课程所作笔记