中断体系结构
ARM体系CPU有以下7种工作模式。
.用户模式(usr):ARM处理器正常的程序执行状态。
.快速中断模式(fiq):用于高速数据传输或通道处理。
.中断模式(irq):用于通用的中断处理。
.管理模式(svc):操作系统使用的保护模式。
.数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
.系统模式(sys):运行具有特权的操作系统任务。
.未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。
可以通过软件来进行模式切换,或者发生各类中断、异常时CPU自动进入相应的模式。除用户模式外,其它6种工作模式都属于特权模式。大多数程序运行于用户模式,进入特权模式是为了处理中断、异常,或者访问被保护的系统资源。
ARM920T有31个通用的32位寄存器和6个程序状态寄存器。这37个寄存器分为7组,进入某个工作模式时就使用它那组的寄存器。有些寄存器,不同的工作模式下有自己的副本,当切换到另一个工作模式时,那个工作模式的寄存器副本将被使用:这些寄存器被称为备份寄存器。
在ARM状态下,每种工作模式都有16个通用寄存器和1个(或2个,这取决于工作模式)程序状态寄存器。
图中R0~R15可以直接访问,这些寄存器中除R15外都是通用寄存器,即它们既可以用于保存数据也可以用于保存地址。另外,R13~R15稍有些特殊。R13又被称为栈指针寄存器,通常被用于保存栈指针。R14又被称为程序连接寄存器或连接寄存器,当执行BL子程序调用指令时,R14得到R15(程序计数器PC)的备份。而当发生中断或异常时,对应的R14_svc、R14_irq、R14_fiq、R14_abt或R14_und中保存R15返回值。
快速中断模式有7个备份寄存器R8_fiq~R14_fiq,这使得进入快速中断模式执行很大部分程序时,甚至不需要保存任何寄存器(只要它们不改变R0~R7)。
每种工作模式除R0~R15共16个寄存器外,还有第17个寄存器CPSR,即“当前程序状态寄存器”。CPSR中一些位被用于标识各种状态,一些位被用于标识当前处于什么工作模式。
除CPSR外,还有快速中断模式、中断模式、管理模式、数据访问终止模式和未定义指令中止模式等5种工作模式和一个寄存器——SPSR,即“程序状态保存寄存器”。当切换进入这些工作模式时,在SPSR中保存前一个工作模式的CPSR值,这样,当返回前一个工作模式时,可以将SPSR的值恢复到CPSR中。
综上所述,当一个异常发生时,将切换进入相应的工作模式(为表述方便,下文中将它称为异常模式),这时ARM920T CPU核将自动完成如下事情。
a.在异常工作模式的连接寄存器R14中保存前一个工作模式的下一条,即将执行的指令的地址。对于ARM状态,这个值是当前PC值加4或加8.
b.将CPSR的值复制到异常模式的SPSR。
c.将CPSR的工作模式位设为这个异常对应的工作模式。
d.令PC值等于这个异常模式在异常向量表中的地址,即跳转去执行异常向量表中的相应指令。
相反地,从异常工作模式退回到之前的工作模式时,需要通过软件完成如下事情。
a.前面进入异常工作模式时,连接寄存器中保存了前一个工作模式的一个指令地址,将它减去一个适当的值后赋值给PC寄存器。
b.将SPSR的值复制回CPSR。
不论何种CPU,中断的处理过程是相似的。
(1)中断控制器汇集各类外设发出的中断信号,然后告诉CPU。
(2)CPU保存当前程序的运行环境(各个寄存器等),调用中断服务程序(ISR)来处理这些中断。
(3)在ISR中通过读取中断控制器、外设的相关寄存器来识别这是哪个中断,并进行相应的处理。
(4)清除中断:通过读写中断控制器和外设的相关寄存器来实现。
(5)最后恢复被中断程序的运行环境(即上面保存的各个寄存器等),继续执行。
SUBSRCPND和SRCPND寄存器表明有哪些中断被触发了,正在等待处理;SUBMASK和MASK用于屏蔽某些中断。
“Request source(withoutsub-register)”中的中断源被触发之后,SRCPND寄存器中相应位被置1,如果此中断没有被INTMASK寄存器屏蔽或者快速中断的话,它将被进一步处理。
“Request source(withsub-register)”中的中断源被触发之后,SUBSRCPND寄存器中的相应位被置1,如果此中断没有被INTSUBMASK寄存器屏蔽的话,它在SRCPND寄存器中的相应位也被置1,之后的处理过程就和“Request source(without sub-register)”一样了。
在SRCPND寄存器中,被触发的中断的相应位被置1,等待处理。
如果被触发的中断有快速中断(FIQ)——MODE(INTMODE寄存器,FIQ只能分配一个,即INTMOD中只能有一位设为1)中为1的位对应的中断是FIQ,则CPU进入快速中断模式(FIQMode)进行处理。
对于一般中断IRQ,可能同时有几个中断被触发,未被INTMASK寄存器屏蔽的中断经过比较后,选出优先级最高的中断,此中断在INTPND寄存器中的相应位被置1,然后CPU进入中断模式(IRQMode)进行处理。中断服务程序可以通过读取INTPND寄存器或者INTOFFSET寄存器来确定中断源。
使用中断的步骤如下:
(1)设置好中断模式和快速中断模式下的栈:当发生中断IRQ时,CPU进入中断模式,这时使用中断模式下的栈;当发生快速中断FIQ时,CPU进入快速中断模式,这时使用快速中断模式下的栈。
InitStacks:
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_c,r1 @UndefMode
ldrsp,=UndefStack @UndefStack=0x33FF_5C00
orr r1,r0,#ABORTMODE|NOINT
msrcpsr_c,r1 @AbortMode
ldrsp,=AbortStack @AbortStack=0x33FF_6000
orr r1,r0,#IRQMODE|NOINT
msrcpsr_c,r1 @IRQMode
ldrsp,=IRQStack @IRQStack=0x33FF_7000
orr r1,r0,#FIQMODE|NOINT
msrcpsr_c,r1 @FIQMode
ldrsp,=FIQStack @FIQStack=0x33FF_8000
bic r0,r0,#MODEMASK|NOINT
orr r1,r0,#SVCMODE
msrcpsr_c,r1 @SVCMode
ldrsp,=SVCStack @SVCStack=0x33FF_5800
(2)准备好中断处理函数。
a.异常向量表中设置好当进入中断模式或快速中断模式时的跳转函数,它们的异常向量地址分别为0x00000018、0x0000001c。
b Reset
@ 0x04: 未定义指令中止模式的向量地址
b HandlerUndef
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
b HandlerSWI
@ 0x0c: 指令预取终止导致的异常的向量地址
b HandlerPrefetchAbort
@ 0x10: 数据访问终止导致的异常的向量地址
b HandlerDataAbort
@ 0x14: 保留
b HandlerNotUsed
@ 0x18: 中断模式的向量地址
b HandlerIRQ
@ 0x1c: 快中断模式的向量地址
b HandlerFIQ
跳转到具体的异常处理函数:
HandlerFIQ:
HANDLER HandleFIQ
HandlerIRQ:
HANDLER HandleIRQ
HandlerUndef:
HANDLER HandleUndef
HandlerSWI:
HANDLER HandleSWI
HandlerDataAbort:
HANDLER HandleDabort
HandlerPrefetchAbort:
HANDLER HandlePabort
HandlerNotUsed:
b .
HANDLER是一个宏定义,它将跳转到具体的函数进行执行。
.macro HANDLER HandleLabel
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=\HandleLabel
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
.endm
b.中断服务程序(ISR)。IRQ、FIQ的跳转函数,最终将调用具体中断的服务函数。
对于IRQ,读取INTPND寄存器或INTOFFSET寄存器的值来确定中断源,然后分别处理。
IsrIRQ:
sub lr,lr, #4 @ 计算返回地址
stmfd sp!,{r0-r12,lr } @ 保存使用到的寄存器
subsp,sp,#4 @保留pc寄存器的值
stmfd sp!,{r8-r9} @把r8 r9按入堆栈
ldr lr,=int_return @ 设置调用ISR即EINT_Handle函数后的返回地址
ldr r9,=INTOFFSET @把中断偏移INTOFFSET的地址装入r9里面
ldr r9,[r9] @取出INTOFFSET单元里面的值给r9
ldr r8,=HandleEINT0 @向量表的入口地址赋给r8
add r8,r8,r9,lsl #2 @求出具体中断向量的地址
ldr r8,[r8] @中断向量里面存储的中断服务程序的入口地址赋给r8
strr8,[sp,#8] @按入堆栈
ldmfd sp!,{r8-r9,pc} @堆栈弹出,跳转到相应的中断服务程序
int_return:
ldmfd sp!,{r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
对于FIQ,因为只有一个中断可以设为FIQ,无须判断中断源。
c.清除中断:如果不清除中断,则CPU会误以为中断又一次发生了。
可以在调用ISR之前清除中断,也可以在调用ISR之后清除中断,这取决于在ISR执行过程中,这个中断是否可能继续发生、是否能够丢弃。如果在ISR执行过程中,这个中断可能发生并不能丢弃,则在调用ISR之前清除中断,这样在ISR执行过程中发生的中断能够被各寄存器再次记录并通知CPU;如果在ISR执行过程中,这个中断不会发生或者可以丢弃,则在调用ISR之后清除中断。
清除中断时,从源头开始:首先,需要的话,操作具体外设清除中断信号;其次,清除SUBSRCPND(用到的话),SRCPND寄存器中相应位(往相应位写1即可);最后,清除INTPND寄存器中的相应位(往相应位写1即可),最简单的方法就是“INTPND=INTPND”。
(3)进入、退出中断模式或快速中断模式时,需要保存、恢复被中断程序的运行环境。
a.对于IRQ,进入和退出的代码如下:
sub lr,lr,#4 //计算返回地址
stmdbsp!,{r0-r12,lr} //保存使用到的寄存器
…… //中断处理
ldmia sp!,{r0-r12,pc}^ //中断返回,^表示将spsr的值赋给cpsr
b.对于FIQ,进入和退出的代码如下:
sub lr,lr,#4 //计算返回地址
stmdbsp!,{r0-r7,lr} //保存使用到的寄存器
…… //中断处理
ldmia sp!,{r0-r7,pc}^ //中断返回,^表示将spsr的值赋给cpsr
(4)根据具体中断,设置相关外设。
(5)对于“Requestsources(with sub-register)”中的中断,将INTSUBMASK寄存器中相应位设为0。
(6)确定使用此中断的方式:FIQ或IRQ。如果是FIQ,则在INTMODE寄存器中设置相应位为1.如果是IRQ,则在PRIORITY寄存器中设置优先级。
(7)如果是IRQ,将INTMASK寄存器中相应位设为0(FIQ不受INTMASK寄存器控制)。
(8)设置CPSR寄存器中的I-bit(对于IRQ)或F-bit(对于FIQ)为0,使能IRQ或FIQ。
以下是一个按键的中断测试程序。主要做以下几件事:初始化中断,中断函数注册,开中断,如果中断测试完毕则关掉相应的中断。基于bootloader的设计,在写中断的时候,只需要将中断函数的地址写到我们规定的地址即可。关于bootloader的设计,后续再作介绍。
#defineGPB5_out (1<<(5*2)) //LED1
#defineGPB6_out (1<<(6*2)) //LED2
#defineGPB7_out (1<<(7*2)) //LED3
#defineGPB8_out (1<<(8*2)) //LED4
#define GPF4_eint (2<<(4*2)) //K2,EINT4
#define GPF2_eint (2<<(2*2)) //K3,EINT2
#define GPF1_eint (2<<(1*2)) //K1,EINT1
#define GPF0_eint (2<<(0*2)) //K4,EINT0
void init_led(void)
{
GPBCON = GPB5_out | GPB6_out |GPB7_out | GPB8_out ;
GPBDAT = 0xffff;
}
/*
* 初始化GPIO引脚为外部中断
* GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void init_irq(void)
{
GPFCON = GPF0_eint |GPF1_eint | GPF2_eint | GPF4_eint;
EINTMASK &= (~(1<<4));
PRIORITY = (PRIORITY &((~0x01) | (0x3<<7))) | (0x0 << 7) ;
INTMSK &=(~(1<<0)) & (~(1<<1)) & (~(1<<2))&(~(1<<4));
}
void EINT_Handle()
{
unsigned long oft = INTOFFSET;
switch( oft )
{
// K4被按下
case 0:
{
GPBDAT|= (0x0f<<5); // 所有LED熄灭
GPBDAT&= ~(1<<8); // LED4点亮
break;
}
case 1:
{
GPBDAT|= (0x0f<<5); // 所有LED熄灭
GPBDAT&= ~(1<<5); // LED3点亮
break;
}
// K3被按下
case 2:
{
GPBDAT|= (0x0f<<5); // 所有LED熄灭
GPBDAT&= ~(1<<7); // LED3点亮
break;
}
// K1或K2被按下
case 4:
{
GPBDAT|= (0x0f<<5); // 所有LED熄灭
GPBDAT&= ~(1<<6); // K2被按下,LED2点亮
break;
}
default:
break;
}
//清中断
if( oft == 4 )
EINTPEND= (1<<4);
SRCPND = 1<<oft;
INTPND = 1<<oft;
}
void KeyScan_Test(void)
{
char c;
init_led(); //led 初始化
init_irq();
printf("\nKey Scan Test,press ESC key to exit !\n");
ClearPending(BIT_EINT0|BIT_EINT1|BIT_EINT2|BIT_EINT4_7);
pISR_EINT0 = pISR_EINT1 =pISR_EINT2 = pISR_EINT4_7 = (int)EINT_Handle; //注册中断函数
EnableIrq(BIT_EINT0|BIT_EINT1|BIT_EINT2|BIT_EINT4_7);
//while(1);
while( (c=get_c())!=0x1b);
DisableIrq(BIT_EINT0|BIT_EINT1|BIT_EINT2|BIT_EINT4_7);
}
对于pISR_EINT0 、 pISR_EINT1 、 pISR_EINT2 、 pISR_EINT4_7等相关的二级中断地址我们已经有了定义。
#define _ISR_STARTADDRESS 0x31ffff00
// Exception vector
#define pISR_RESET (*(unsigned*)(_ISR_STARTADDRESS+0x0))
#define pISR_UNDEF (*(unsigned*)(_ISR_STARTADDRESS+0x4))
#define pISR_SWI (*(unsigned*)(_ISR_STARTADDRESS+0x8))
#define pISR_PABORT (*(unsigned*)(_ISR_STARTADDRESS+0xc))
#define pISR_DABORT (*(unsigned*)(_ISR_STARTADDRESS+0x10))
#define pISR_RESERVED (*(unsigned*)(_ISR_STARTADDRESS+0x14))
#define pISR_IRQ (*(unsigned*)(_ISR_STARTADDRESS+0x18))
#define pISR_FIQ (*(unsigned*)(_ISR_STARTADDRESS+0x1c))
// Interrupt vector
#define pISR_EINT0 (*(unsigned*)(_ISR_STARTADDRESS+0x20))
#define pISR_EINT1 (*(unsigned*)(_ISR_STARTADDRESS+0x24))
#define pISR_EINT2 (*(unsigned*)(_ISR_STARTADDRESS+0x28))
#define pISR_EINT3 (*(unsigned*)(_ISR_STARTADDRESS+0x2c))
#define pISR_EINT4_7 (*(unsigned*)(_ISR_STARTADDRESS+0x30))
#define pISR_EINT8_23 (*(unsigned*)(_ISR_STARTADDRESS+0x34))
#define pISR_CAM (*(unsigned*)(_ISR_STARTADDRESS+0x38))
#define pISR_BAT_FLT (*(unsigned*)(_ISR_STARTADDRESS+0x3c))
#define pISR_TICK (*(unsigned*)(_ISR_STARTADDRESS+0x40))
#define pISR_WDT_AC97 (*(unsigned*)(_ISR_STARTADDRESS+0x44))
#define pISR_TIMER0 (*(unsigned*)(_ISR_STARTADDRESS+0x48))
#define pISR_TIMER1 (*(unsigned*)(_ISR_STARTADDRESS+0x4c))
#define pISR_TIMER2 (*(unsigned*)(_ISR_STARTADDRESS+0x50))
#define pISR_TIMER3 (*(unsigned*)(_ISR_STARTADDRESS+0x54))
#define pISR_TIMER4 (*(unsigned*)(_ISR_STARTADDRESS+0x58))
#define pISR_UART2 (*(unsigned*)(_ISR_STARTADDRESS+0x5c))
#define pISR_LCD (*(unsigned*)(_ISR_STARTADDRESS+0x60))
#define pISR_DMA0 (*(unsigned*)(_ISR_STARTADDRESS+0x64))
#define pISR_DMA1 (*(unsigned *)(_ISR_STARTADDRESS+0x68))
#define pISR_DMA2 (*(unsigned*)(_ISR_STARTADDRESS+0x6c))
#define pISR_DMA3 (*(unsigned*)(_ISR_STARTADDRESS+0x70))
#define pISR_SDI (*(unsigned*)(_ISR_STARTADDRESS+0x74))
#define pISR_SPI0 (*(unsigned*)(_ISR_STARTADDRESS+0x78))
#define pISR_UART1 (*(unsigned*)(_ISR_STARTADDRESS+0x7c))
#define pISR_NFCON (*(unsigned*)(_ISR_STARTADDRESS+0x80))
#define pISR_USBD (*(unsigned*)(_ISR_STARTADDRESS+0x84))
#define pISR_USBH (*(unsigned*)(_ISR_STARTADDRESS+0x88))
#define pISR_IIC (*(unsigned*)(_ISR_STARTADDRESS+0x8c))
#define pISR_UART0 (*(unsigned*)(_ISR_STARTADDRESS+0x90))
#define pISR_SPI1 (*(unsigned*)(_ISR_STARTADDRESS+0x94))
#define pISR_RTC (*(unsigned*)(_ISR_STARTADDRESS+0x98))
#define pISR_ADC (*(unsigned*)(_ISR_STARTADDRESS+0x9c))
http://www.360doc.com/content/12/0605/20/7775902_216231961.shtml