中断问题与完整工程测试
一. 中断问题
中断对编写程序非常的重要,所以程序对中断处理的好坏将直接影响程序的优劣,对实时性要求较高的系统更是如此。对于ADS2.0,在编写中断处理程序的时候,只需要在程序前面加上"_irq"这个关键字,ADS就会自动为我们保存中断现场,等程序返回的时候自动恢复现场,细节无须我们关心。当然,也可以不加这个关键字,如果这样就得自己保存与恢复中断现场,考虑的问题就多了。arm-linux-gcc开发环境下,目前我还没有发现类似“_irq”这样的关键字可以通知编译器自动处理中断过程。因此,如果使用arm-linux-gcc编译带有中断的程序,就必须自己处理中断现场。除了保护中断现场外,程序还需要做的就是设置正确的中断向量表,使得每个中断发生时都能找到合适的跳转地址。这里我主要参考了2440init.S中的方法。有如下几步:
(1)首先定义宏
这个宏的名称是Handler,作用是取地址Addr处存放的值,赋值给PC。
(2)宏展开
这样一来,发生中断后,PC就会跳转到HandleIRQ存放的地址处执行(假设是普通中断)
(3)在HandlerIRQ处有这样的代码
HandleIRQ处存放的地址是IsrIRQ,所以发生中断后,PC跳转到IsrIRQ处执行
(4) IsrIRQ 是interrupt.S中的标号
这段程序就是中断处理的核心代码:保存中断现场,然后跳转到相应的中断服务程序中,返回后恢复中断现场。这是一种最简单的中断处理方式。程序的开始修正了程序返回地址,我们知道由于流水线的问题,arm体系结构中pc不是指向正在执行的指令,而是指向正在取址的指令,因为中断而保存在lLR寄存器中的就是正在执行的指令的后两个指令,比如正在执行第n条指令,而cpu为我们保存的是第n+2条指令,中断返回后我们要执行第n+1条指令。一个指令四个字节,所以这里LR减4正好是程序应该返回的地址。把这个地址压入栈才是正确的。修正完返回地址,就可以保存寄存器了。这里保存的寄存器有r0-r3,r12,还有返回地址LR。这些寄存器的保存我是参考一些书上介绍的,这样保存事实上也可以正常工作。但是我一直有一个疑问:在arm不同的模式下共用的寄存器不止r0-r3,r0-r7都是各个模式共用的寄存器。那么为什么就保存r0-r3就可以了呢。保存完现场,然后通过rINTOFFSET寄存器找到究竟发生了哪个中断。rINTOFFSET是中断控制器的寄存器,发生中断后,这里记录了具体中断的偏移。所有的中断都是有号码的,我们可以利用这个号码来实现中断服务程序的跳转。这里读出中断号,左移2位也就是乘以4,加上HandleEINT0的值,最后将这个地址处存放的值赋值个pc,从而完成了中断的跳转。
rINTOFFSET 是start.S中的标号,在start.S的最后有这么一段代码:
HandleEINT0等于ISR_BADDR+4*8,也就是0x33ffff00+4×8,这个地址处就是0号中断(也就是外部中断0)服务程序入口地址。其他中断入口地址都是在此基础上加上相应的偏移值。在应用程序中我们只需要将相应的中断服务程序入口地址保存在这里,发生中断的时候就会实现正确跳转。
在2440addr.h中,定义了一些宏,方便我们赋值。比如
注意_ISR_STARTADDRESS 与 ISR_BADDR是相同的,这就保证了跳转的一致性。假如我们中断服务程序为irq_int0,在应用程序中我们只需要一条赋值语句就实现了中断程序的装载
以上的中断处理,在Nandflash启动下是正常工作的,但是在Norflash启动的情况下,用supervivi的D功能下载程序到内存中运行,程序是不能正确响应中断的,在串口终端输出奇怪的信息。原因是当中断发生时,cpu默认去0x00地址处取得中断向量表,而我们的程序的中断向量表在0x30000000处,地址0x00是norfalsh的地址空间,而这里存放的是supervivi的代码,所以发生找不到中断向量的错误。但是在Nandflash启动的情况下,cpu自动将我们代码前4k拷贝到SRAM里,当然也包括中断向量表。在这种情况下,0x00处是SRAM地址空间,正确存放着中断向量。
对于下载到内存中运行这种情况,为了实现正确的中断跳转。解决的办法有两种:第一种是将内存中的中断向量表拷贝到0x00处,但是这种方法是不可行的。原因一:麻烦,我们知道在norflash启动下,0x00是norflash的地址空间,虽然norflash读时序很简单,只需要普通的赋值就可以了。但是写时序很复杂,需要复杂的时序操作。这样一来就需要额外的代码。原因二:有副作用,在norflash里我们存放了supervivi,如果重写norflash就会破坏supervivi,导致下一次norflash不能正确启动。所以这种方法不可取,另一种方法就是通过mmu重映射的方式,将物理地址0x30000000开始处的一小段代码,映射到0x00处,cpu取地址取的是虚拟地址,所以当中断发生的时候,cpu照常到0x00出取中断向量,但是却是取到0x30000000的内容,而这里就是正确的中断向量。我就是用这种方式实现的内存中中断正常跳转的。
验证代码在我的资源里: http://download.csdn.net/detail/yaozhenguo2006/3845811
程序验证的是按键中断,每按下一个按键,串口终端都会提示按下的按键号
中断对编写程序非常的重要,所以程序对中断处理的好坏将直接影响程序的优劣,对实时性要求较高的系统更是如此。对于ADS2.0,在编写中断处理程序的时候,只需要在程序前面加上"_irq"这个关键字,ADS就会自动为我们保存中断现场,等程序返回的时候自动恢复现场,细节无须我们关心。当然,也可以不加这个关键字,如果这样就得自己保存与恢复中断现场,考虑的问题就多了。arm-linux-gcc开发环境下,目前我还没有发现类似“_irq”这样的关键字可以通知编译器自动处理中断过程。因此,如果使用arm-linux-gcc编译带有中断的程序,就必须自己处理中断现场。除了保护中断现场外,程序还需要做的就是设置正确的中断向量表,使得每个中断发生时都能找到合适的跳转地址。这里我主要参考了2440init.S中的方法。有如下几步:
(1)首先定义宏
- .macro Handler Addr
- sub sp, sp, #4
- stmfd sp!, {r0}
- ldr r0, =\Addr
- ldr r0, [r0]
- str r0, [sp,#4]
- ldmfd sp!, {r0,pc}
- .endm
(2)宏展开
- HandlerUndef:
- Handler HandleUndef
- HandlerSWI:
- Handler HandleSWI
- HandlerPabort:
- Handler HandlePabort
- HandlerDabort:
- Handler HandleDabort
- HandlerIRQ:
- Handler HandleIRQ
- HandlerFIQ:
- Handler HandleFIQ
(3)在HandlerIRQ处有这样的代码
- HandleIRQ:
- .word IsrIRQ
(4) IsrIRQ 是interrupt.S中的标号
- .globl IsrIRQ
- IsrIRQ:
- @ Fix the return address
- sub lr,lr,#4
- stmfd sp!,{r0-r3,r12,lr}
- #define rINTOFFSET 0x4a000014
- LDR R0, =rINTOFFSET
- LDR R0, [R0]
- LDR R1, =HandleEINT0
- MOV LR, PC @ Save LR befor jump to the C function we need return back
- LDR PC, [R1, R0, LSL #2]
- ldmfd sp!,{r0-r3,r12,pc}^
rINTOFFSET 是start.S中的标号,在start.S的最后有这么一段代码:
- .equ ISR_BADDR, 0x33ffff00
- HandleReset:
- .word (ISR_BADDR+4*0)
- HandleUndef:
- .word (ISR_BADDR+4*1)
- HandleSWI:
- .word (ISR_BADDR+4*2)
- HandlePabort:
- .word (ISR_BADDR+4*3)
- HandleDabort:
- .word (ISR_BADDR+4*4)
- HandleReserved:
- .word (ISR_BADDR+4*5)
- HandleIRQ:
- .word IsrIRQ
- HandleFIQ:
- .word (ISR_BADDR+4*7)
- .globl HandleEINT0
- .equ HandleEINT0, (ISR_BADDR+4*8)
- .equ HandleEINT1, (ISR_BADDR+4*9)
- .equ HandleEINT2, (ISR_BADDR+4*10)
- .equ HandleEINT3, (ISR_BADDR+4*11)
- ........
在2440addr.h中,定义了一些宏,方便我们赋值。比如
- #define _ISR_STARTADDRESS 0x33ffff0
- #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))
- pISR_EINT0 = irq_int0
对于下载到内存中运行这种情况,为了实现正确的中断跳转。解决的办法有两种:第一种是将内存中的中断向量表拷贝到0x00处,但是这种方法是不可行的。原因一:麻烦,我们知道在norflash启动下,0x00是norflash的地址空间,虽然norflash读时序很简单,只需要普通的赋值就可以了。但是写时序很复杂,需要复杂的时序操作。这样一来就需要额外的代码。原因二:有副作用,在norflash里我们存放了supervivi,如果重写norflash就会破坏supervivi,导致下一次norflash不能正确启动。所以这种方法不可取,另一种方法就是通过mmu重映射的方式,将物理地址0x30000000开始处的一小段代码,映射到0x00处,cpu取地址取的是虚拟地址,所以当中断发生的时候,cpu照常到0x00出取中断向量,但是却是取到0x30000000的内容,而这里就是正确的中断向量。我就是用这种方式实现的内存中中断正常跳转的。
验证代码在我的资源里: http://download.csdn.net/detail/yaozhenguo2006/3845811
二. 完整工程验证
至此arm-linux-gcc编译裸机程序的所有问题都解决了,但是真正能否用于工程还是个未知数。所以得找一个完整的工程验证一下。因为在ADS下我曾经移植过ucosii,如果将ucosii编译通过,并且能够正确运行,那么就表明方法确实可以用。所以我进行了ucosii的移植,有了上面的基础,移植就简单多了,无非就是将arm汇编转换成gnu汇编,c代码基本上不用动,最后编写合适的makefile就可以了。
编译完成的代码在我的资源里: http://download.csdn.net/detail/yaozhenguo2006/3845817
将ucosii_mini2440.bin下载到mini2440中运行,会看到串口终端交替输出hello world 和my friend,同时led灯在闪烁。这说明程序能正确运行,两个任务互不影响的运行。如果要重新编译只需要根据自己系统的情况将链接选项修改一下就可以了。