原文出处:http://www.51hei.com/bbs/dpj-28561-1.html
下面以串口UART0接收中断为例:
串口接收中断初始化时有这么一句:pISR_UART0=(unsigned)__irq UART0 _GetInt /把 UART0 _GetInt这个中断服务子程序的入口地址放到pISR_TICK,
S3C2440addr.h中#define pISR_UART0 (*(unsigned *)(_ISR_STARTADDRESS+0x90))
option.inc中_ISR_STARTADDRESS EQU 0x33ffff00 //也就是中断服务子程序的入口地址放到0x33ffff00+0x90这个地址单元,即放入相应的中断向量表中,当中断发生时可通过查向量表(S3C2440addr.h最后有向量表,有这么一句HandleUART0 # 4,此处即放的UART0的中断入口地址)找到入口地址,执行中断服务子程序
S3C2440addr.h中#define pISR_UART0 (*(unsigned *)(_ISR_STARTADDRESS+0x90))
option.inc中_ISR_STARTADDRESS EQU 0x33ffff00 //也就是中断服务子程序的入口地址放到0x33ffff00+0x90这个地址单元,即放入相应的中断向量表中,当中断发生时可通过查向量表(S3C2440addr.h最后有向量表,有这么一句HandleUART0 # 4,此处即放的UART0的中断入口地址)找到入口地址,执行中断服务子程序
再看S3C2440addr.h中下面几句指令,所在地址已标出,这些地址上的指令称为“异常向量”,当复位时,CPU进入系统模式,并跳到0x00地址执行, 再跳到ResetHandler 处执行相应的复位程序,其他异常也是一样的执行过程。
b ResetHandler ; @0x00
b HandlerUndef ; @0x04
b HandlerSWI ; @0x08
b HandlerPabort ; @0x0c
b HandlerDabort ; @0x10
b . ;reserved ,保留@0x14
b HandlerIRQ ;@0x18
b HandlerUndef ; @0x04
b HandlerSWI ; @0x08
b HandlerPabort ; @0x0c
b HandlerDabort ; @0x10
b . ;reserved ,保留@0x14
b HandlerIRQ ;@0x18
b HandlerFIQ ;@0x1c
言归正传,当中断(IRQ)发生时,CPU进入中断模式,并跳到0x18处执行b HandlerIRQ (在S3C2440addr.h中)这条指令,程序跳到HandlerIRQ HANDLER HandleIRQ(还在S3C2440addr.h中)处,此处将根据下面的宏定义展开:
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel ;相当于$HandleIRQ
sub sp,sp,#4 ;sp=sp-4
sub sp,sp,#4 ;sp=sp-4
stmfd sp!,{r0} ; r0内容保存到sp对应的栈中,应为入在下边用到所以先保存起来
ldr r0,=$HandleLabel; 把HandleLabel这个地址放r0
ldr r0,[r0] ; 把HandleLabel这个地址的内容放r0
str r0,[sp,#4] ;把r0保存到sp+4的栈中,中断函数首地址HandleLabel入栈
ldmfd sp!,{r0,pc} ; 把sp对应的内容出栈放r0,sp+4放PC中,即原来保存的r0回复到r0,程序跳到HandleLabel,即HandleIRQ
ldr r0,[r0] ; 把HandleLabel这个地址的内容放r0
str r0,[sp,#4] ;把r0保存到sp+4的栈中,中断函数首地址HandleLabel入栈
ldmfd sp!,{r0,pc} ; 把sp对应的内容出栈放r0,sp+4放PC中,即原来保存的r0回复到r0,程序跳到HandleLabel,即HandleIRQ
MEND
上边这段程序执行完了之后,程序跳到HandleIRQ处执行,再看下面一段程序(S3C2440addr.h中),
ldr r0,=HandleIRQ ;This routine is needed
ldr r1,=IsrIRQ ;if there is not 'subs pc,lr,#4' at 0x18, 0x1c
str r1,[r0]
ldr r1,=IsrIRQ ;if there is not 'subs pc,lr,#4' at 0x18, 0x1c
str r1,[r0]
2440启动时执行这段程序,此段程序即把IsrIRQ和HandleIRQ标号等价起来,接着上面,程序跳到HandleIRQ处执行即跳到IsrIRQ处,在看下面程序IsrIRQ(S3C2440addr.h中)查向量表找入口地址:
IsrIRQ
sub sp,sp,#4 ;给PC寄存器保留;reserved for PC
stmfd sp!,{r8-r9}; 把r8-r9压入栈
sub sp,sp,#4 ;给PC寄存器保留;reserved for PC
stmfd sp!,{r8-r9}; 把r8-r9压入栈
ldr r9,=INTOFFSET ;INTOFFSET的值在2440addr.inc中定义为0x4a000014,
; 把中断偏移INTOFFSET寄存器的地址装入r9
; 把中断偏移INTOFFSET寄存器的地址装入r9
ldr r9,[r9] ;把中断偏移INTOFFSET寄存器内容装入r9,
ldr r8,=HandleEINT0; 这就是向量表的入口HandleEINT0装入r8,中断向量表的基址
add r8,r8,r9,lsl #2 ;R8=R8+(R9<<2) ,基址加变址得到中断向量表地址
ldr r8,=HandleEINT0; 这就是向量表的入口HandleEINT0装入r8,中断向量表的基址
add r8,r8,r9,lsl #2 ;R8=R8+(R9<<2) ,基址加变址得到中断向量表地址
ldr r8,[r8] ; 装入中断服务程序的入口
str r8,[sp,#8] ;把入口压入堆,
str r8,[sp,#8] ;把入口压入堆,
ldmfd sp!,{r8-r9,pc} ;出栈,入口地址给PC即跳到中断服务程序执行,
专门解释 $HandlerLabel HANDLER $HandleLabel
出处: http://blog.chinaunix.net/uid-26435987-id-3073962.html
接下来的代码如下(注黑色为源程序,蓝色为解说部分代码):
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4 ;减少sp(用于存放转跳地址)实质上是在计算返回地址,用来存储PC地址
stmfd sp!,{r0} ;把将要使用的r0寄存器入栈
ldr r0,=$HandleLabel ;将HandleXXX的址址放入r0
ldr r0,[r0] ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0
str r0,[sp,#4] ;把中断服务程序(ISR)压入栈.
ldmfd sp!,{r0,pc} ;用出栈的方式恢复r0的原值和为pc设定新值(完成了到ISR的转跳)
MEND
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4 ;减少sp(用于存放转跳地址)实质上是在计算返回地址,用来存储PC地址
stmfd sp!,{r0} ;把将要使用的r0寄存器入栈
ldr r0,=$HandleLabel ;将HandleXXX的址址放入r0
ldr r0,[r0] ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0
str r0,[sp,#4] ;把中断服务程序(ISR)压入栈.
ldmfd sp!,{r0,pc} ;用出栈的方式恢复r0的原值和为pc设定新值(完成了到ISR的转跳)
MEND
;首先这段程序是个宏定义,HANDLER是宏名,不要想歪了
;其次后面程序遇到的HandlerXXX HANDLER HandleXXX这些语句将都被下面这段程序展开
例如:HandlerFIQ HANDLER HandleFIQ 被上面那段程序展开后为:
HandlerFIQ
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=HandleFIQ
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
HandlerFIQ
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=HandleFIQ
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
;再次这段程序目的在于把中断服务程序的首地址装载到PC中,可以称之为“加载程序”
;本初始化程序定义了一个数据区(在文件最后),34个字空间,存放相应中断服务程序的首地址。每个字空间都有一个标号,以Handle***命名。
;本初始化程序定义了一个数据区(在文件最后),34个字空间,存放相应中断服务程序的首地址。每个字空间都有一个标号,以Handle***命名。
例如:HandlerFIQ HANDLER HandleFIQ
HandlerIRQ HANDLER HandleIRQ
HandlerUndef HANDLER HandleUndef
HandlerSWI HANDLER HandleSWI
HandlerDabort HANDLER HandleDabort
HandlerPabort HANDLER HandlePabort
HandlerIRQ HANDLER HandleIRQ
HandlerUndef HANDLER HandleUndef
HandlerSWI HANDLER HandleSWI
HandlerDabort HANDLER HandleDabort
HandlerPabort HANDLER HandlePabort
;在向量中断模式下使用“加载程序”来执行中断服务程序。
注释:向量中断模式是当CPU读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的指令取代0x18处的指令,通过跳转指令,系统就直接跳转到对应地址函数中节省了中断处理时间提高了中断处理速度。例如:ADC中断的向量地址为0xC0,则在0xC0处放如下代码:ldr PC,=HandlerADC 当ADC 中断产生的时候系统会自动跳转到HandlerADC函数中处理中断。
非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将中断状态寄存器中对应标志位置位然后跳转到位于0x18处的统一中断函数中该函数通过读取中断状态寄存器中对应标志位来判断中断源,并根据中断优先级寄存器中优先级关系再跳到对应中断源的处理代码中处理中断。
补充说明:
;细心的人会发现在_ISR_STARTADDRESS=0x33FF_FF00里定义的第一级中断向量表是采用型如Handle***的方式的,而在程序的ENTRY处(程序开始处)采用的是b Handler***的方式.在这里Handler***就是通过HANDLER这个宏和Handle***进立联系的.所以认为HANDLER这个宏是用于第一次查表过程的实现中断向量的重定向。那么这样做有什么优点呢?
这种方式的优点就是真正定义的向量数据放到内存空间RAM里,而不是在ENTRY(地址0x0)处的ROM(FLASH)空间里, 这样,我们就可以在程序里灵活的改动中断向量的数据了(因为ROM是只读的,而RAM为可读可写的),在这里HANDLER是一个宏,就负责查找中断处理程序的入口地址。这些中断入口地址存放在由HandleXXX指向的表项中,该表定位在RAM高端,它的基地址为
_ISR_STARTADDRESS。
例如:_ISR_STARTADDRESS为0x800000000,当IRQ中断时,根据b HandlerFIQ,先跳转再根据_ISR_STARTADDRESS基地址+HandleIRQ的偏移地址(4*6)得到的中断地址0x80000000+0x00000024=0x80000024