ARM中断学习,讲的十分到位值得一看. 下载本文

内容发布更新时间 : 2024/5/9 6:56:54星期一 下面是文章的全部内容请认真阅读。

ARM 中断学习,讲的十分到位。。值得一看。

中断向量

b HandlerIRQ ;handler for IRQ interrupt

很自然,因为所有的单片机都是那样,中断向量一般放在开头,用过单片机的人都会很熟悉,那就不多说了。

异常服务程序

这里不用中断(interrupt)而用异常(exception),毕竟中断只是异常的一种情况,

下面主要分析的是“中断异常”说白了,就是我们平时单片机里面用的中断!!!所有由器件引起的中断,例如TIMER中断,UART中断,外部中断等等,都有一个统一的入口,那就是中断异常 IRQ ! 然后从IRQ的服务函数里面分辨出,当前究竟是什么中断,再跳转到相应的中断服务程序。这样看来,ARM比单片机要复杂一些了,不过原理是不变的。

上面说的就是思路,跟着这个思路来接着分析。

HandlerIRQ 很明显是一个标号,我们找到了 HandlerIRQ HANDLER HandleIRQ

这里是一个宏定义,我们再找到这个宏,看他是怎么定义的:

MACRO

$HandlerLabel HANDLER $HandleLabel

$HandlerLabel

sub sp,sp,#4 ;decrement sp(to store jump address)

stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original

address)

ldr r0,=$HandleLabel ;load the address of HandleXXX to r0

ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR) MEND

用 HandlerIRQ 将这个宏展开之后得到的结果实际是这样的

HandlerIRQ

sub sp,sp,#4 ;decrement sp(to store jump address)

stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original

address)

ldr r0,=HandleIRQ ;load the address of HandleXXX to r0

ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)

至于具体的跳转原理下面再说,

好了,这样的话就容易看的多了,很明显, HandlerIRQ 还是一个标号,IRQ异常向量就是跳转到这里执行的,这里粗略看一下,应该是保存现场,然后跳转到真正的处理函数,那么很容易发现了这么一句 ldr r0,=HandleIRQ ,没错,我们又找到了一个标号 HandleIRQ ,看来真正的处理函数应该是这个 HandleIRQ ,继续寻找

AREA RamData, DATA, READWRITE

^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00 HandleReset # 4 HandleUndef # 4 HandleSWI # 4 HandlePabort # 4 HandleDabort # 4 HandleReserved # 4

HandleIRQ # 4 对应上面第二个标号

最后我们发现在这里找到了 HandleIRQ ,^ 其实就是 MAP ,这段程序的意思是,从 _ISR_STARTADDRESS 开始,预留一个变量,每个变量一个标号,预留的空间为 4个字节,也就是 32BIT,其实这里放的是真正的C写的处理函数的地址,说白了,就是函数指针 - - 这样做的话就很灵活了

接着,我们需要安装IRQ处理句柄,说白了,就是设置处理函数的地址,让PC指针可以正确的跳转。 于是我们在接着的找到安装句柄的语句

; Setup IRQ handler

ldr r0,=HandleIRQ ;This routine is needed

ldr r1,=IsrIRQ ;if there is not ''subs pc,lr,#4'' at 0x18, 0x1c str r1,[r0]

说白了就是将 IsrIRQ 的地址填到 HandleIRQ对应的地址里面,前面说了 HandleIRQ 放的是中断处理的函数的入口地址,我们继续找 IsrIRQ

IsrIRQ

sub sp,sp,#4 ;reserved for PC stmfd sp!,{r8-r9} ldr r9,=INTOFFSET ldr r9,[r9]

ldr r8,=HandleEINT0 add r8,r8,r9,lsl #2 ldr r8,[r8] str r8,[sp,#8] ldmfd sp!,{r8-r9,pc}

要理解这个代码,得先学学2440的中断系统了,INTOFFSET存放的是当前中断的偏移号,根据偏移就知道当前是哪个中断源发生的中断。

注意了,我们说的是中断,而不是异常,看看原来的表是啥样子的

^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00

HandleReset # 4 HandleUndef # 4 HandleSWI # 4 HandlePabort # 4 HandleDabort # 4 HandleReserved # 4 HandleIRQ # 4 HandleFIQ # 4

HandleEINT0 # 4 HandleEINT1 # 4 HandleEINT2 # 4 HandleEINT3 # 4 .......

可以看到,前面几个是异常,从 HandleEINT0 就是 IRQ异常的向量存放的地方了,这样就可以理解为什么上面 IsrIRQ 里面里面要执行那条指令 ldr r8,=HandleEINT0 add r8,r8,r9,lsl #2

道理很简单, HandleEINT0 就是所有IRQ中断向量表的入口,在这个地址上面,加上一个适当的偏移量,INTOFFSET ,那么我们知道现在,到底是哪个IRQ在申请中断了。

至于具体怎么跳转的?

首先,我们说了,HandleEINT0 开始的一段内存里面,存放的就是中断服务函数的函数指针,ARM的体系的话,每个指针变量就是占4个字节,这里就解释了,为什么这里为每个标号分配了4个字节的空间,里面放的就是函数指针!!!下面再看看怎么跳转,继续看 IsrIRQ 里面就实现了跳转了 str r8,[sp,#8] ldmfd sp!,{r8-r9,pc}

其实最核心就是这两句了,先查找到当前中断服务程序的地址,将他放到 R8 里面,然后出栈,弹出给PC那么PC很自然就跳到中断服务程序了。至于这里的堆栈问题又是一个非常棘手的,需要好好的参透ARM的中断架构,需要了解的可以自己仔细的阅读 《ARM体系结构与编程》里面说的很详细。我们这里的重点是研究怎么跳转。

最后,我们看看在C代码中是怎么安装终端向量的,例如看 按键的外部中断,是怎么具体设置的,参看/src/keyscan.c 里面的代码很简单,里面只有3个函数

KeyScan_Test 是按键测试的主函数 Key_ISR 是按键中断服务函数

在 KeyScan_Test里面,我们发现了有这么一句

pISR_EINT0 = pISR_EINT2 = pISR_EINT8_23 = (U32)Key_ISR;