注意ucos代码(也就是main函数中的代码)和启动代码都是在Supervisor模式下工作的。而IRQ是工作在外部中断模式下的,这两种模式用的寄存器组是不一样的,因此切换模式时要注意保持相应的寄存器。
假如我设定定时器2每自减100就产生一次IRQ中断,定时的时间到了之后就产生中断(定时器如何设定这里不讨论):
(1):如果让CPU能够处理中断,即不屏蔽IRQ中断,则需用户手动设定CRPS中I_bit位为0,这个过程需要用户用程序控制。
(2):定时器中断(定时器中断的设置是在TCFG1寄存器中,详细请见寄存器设置)产生后,CPU根据INTMODE寄存器的设置(INTMODE &= ~(1<<12),该寄存器的设置详细请见《嵌入式系统原理及接口技术》P250)强制进入IRQ模式。这个过程是由硬件自动进行,无需用户干预。
(3):CPU在IRQ模式下把CPSR拷贝到SPSR_irq中,PC值保存在LR_irq中,置CPSR中的I位以关闭IRQ中断。这一过程是由硬件自动处理,无需用户干预。
(4):数据保存之后,CPU强行从0X00000018开始执行,这一过程由硬件自动处理,无需用户干预。
(5):
在启动代码中一开始是:
LDR PC,=HANDLE_ResetInit ;地址是0x00
LDR PC,=HANDLE_HandlerUndef ;地址是0x04
LDR PC,=HANDLE_HandlerSWI ;地址是0x08
LDR PC,=HANDLE_HandlerPabort ;地址是0x0c
LDR PC,=HANDLE_HandlerDabort ;地址是0x10
LDR PC,. ;地址是0x14
LDR PC,=OS_CPU_IRQ_ISR ;地址是0x18
LDR PC,=HANDLE_HandlerFIQ ;地址是0x1c
在0X00000018是LDR PC,=OS_CPU_IRQ_ISR这条指令,那么CPU就转而到OS_CPU_IRQ_ISR地址处执行。
下面就是OS_CPU_IRQ_ISR函数的设置了,该函数(严格来说不是函数,只是汇编文件的一个地址标号)主要完成以下几个功能:
保存被中断的Supervisor模式下运行的寄存器(任务上下文的保护)到Supervisor模式下任务的栈中,保存栈的指针到任务控制块中,这两步是保存被中断的任务,然后进入中断处理函数。
OS_CPU_IRQ_ISR函数(实际上OS_CPU_IRQ_ISR是个地址吧标号,与函数名是一样的,但是OS_CPU_IRQ_ISR是汇编代码)原型如下:
语句一:STMFD SP!, {R1-R3}
详解:完成R1-R3的入栈操作,即R3-->[SP-4] R2-->[SP-8] R1-->[SP-12]。子所以要把这三个寄存器入账,是因为下面要用到这三个寄存器。因为在刚进入OS_CPU_IRQ_ISR函数中,CPU是在IRQ模式下,而被中断的任务是在Supervisor模式下工作的,用到的寄存器是不一样的,其中R0-R12,PC,CPSR是一样的,LR,SP(栈指针存放在这里)和SPSR是不一样,其中在IRQ模式中LR是LR_irq,SP是SP_irq,SPSR是SPSR_irq。而Supervisor模式下LR是LR_ svc,SP是SP_svc,SPSR是SPSR_svc。任务被中断前是工作在Supervisor模式下的,因此要保存Supervisor模式下的寄存器到Supervisor模式下的栈中(注意:ARM不同模式工作的栈的地址是不一样的,例如在我的开发板的启动代码中Supervisor模式下栈的基址是0X33FF0000-10*2*1024,而IRQ栈的基址是0X33FF0000-10*3*1024,大小都是10*1024),因此STMFD SP!, {R1-R3}是把R1-R3入IRQ模式下的栈
第二条语句:MOV R1, SP
详解:假如SP_irq基址是78,那么STMFD SP!, {R1-R3}执行后栈中内容如下:
74:[R3] 70:[R2] 66:[R1],此时SP_irq寄存器中的值就是66,执行MOV R1, SP后R1中的值也就是66,这个SP的保存主要是为了通过这个值访问IRQ模式下的堆栈空间,实现对数据的访问。
第三条语句:ADD SP, SP, #12
详解:调整IRQ模式下的堆栈指针SP_irq,将这个指针指向IRQ堆栈的开始位置,方便下一次中断的处理操作。执行后SP_irq又回到原来的栈的基址,即74
第四条语句:SUB R2, LR, #4
详解:(LR_irq-4)-->R2
在(3)中已经说明了,在发生IRQ中断后,PC值保存在LR_irq中,所以这句就是把程序的返回地址存入R2中,子所以减4是因为ARM采用三级流水,即取指,译码,执行。当执行当前语句(例如地址是30000016,实际PC中已经装入30000024,所以第二条指令,地址30000020还没有执行,因此中断返回时要执行30000020处的指令,因此LR_irq要减去4)。
第五条语句:MRS R3, SPSR
详解:SPSR_irq-->R3
正如(3)所说因为发生了IRQ中断,此时CPU进入IRQ模式中,这时的SPSR_irq中保存了svc模式下的CPSR状态,而任务堆栈中保存的刚好是SVC模式下的状态寄存器,因此需要将SVC模式下的状态寄存器首先读出来,然后保存进任务的堆栈中,因此用R3来暂时保存CPSR值,待会还要保存到SVC模式下热任务的栈中。
第六条语句: MSR CPSR_cxsf, #SVCMODE:OR:NOINT
详解:进入SVC模式,这才要真正保存任务的寄存器和栈。正如(3)提到的从开始进入OS_CPU_IRQ_ISR到执行到此处IRQ一直是关着的,因为刚发生中断时系统置CPSR中的I位以关闭IRQ中断,执行到此处一直没有开中断,而这条语句也是关中断,因此OS_CPU_IRQ_ISR是一直在关闭中断的条件下执行,不会出现中断套嵌。所以下文中保存任务在SVC模式下的寄存器是有意义的,因为这是第一次中断,如果这条语句是开中断,那么下面在保存被中断的任务在SVC模式下的寄存器之前应该先检查是否有中断套嵌,如果有就不需要保存了,因为在第一次中断发生时已经保存了。
第七条语句:STMFD SP!, {R2}
详解:R2 -->[SP_svc-4]
注意这才是真正把要保存的任务的寄存器入任务的栈中,R2的内容就是第四条语句中的30000020,即被中断任务的下一条未执行的指令。注意SP_svc的变化,假设一开始SVC模式下SP的基址是1078,那么这条语句执行后的栈中数据变化是1074:30000020,执行后SP_svc 的值是1074
第八条语句:STMFD SP!, {R4-R12, LR}
详解:LR_svc-->[1070] R12-->[1066] R11-->[1062] R10-->[1066] R9-->[1062]
R8-->[1058] R7-->[1054] R6-->[1050] R5-->[1046] R4-->[1042] ,此时SP_svc的值是1042。至于为什么要保存LR_svc,我一开始不是很明白,现在想明白了,保存任务的寄存器是保存任务的所有寄存器,而我一直是任务只要保存R0-R12,返回地址和CPSR,因为返回地址已经从LR_irq中被保存到[1074]中了,为什么还要保存LR_svc呢,现在想想返回地址和LR_svc不是一回事,LR_svc也是程序被中断前需要的寄存器,因此需要保存,注意此时LR_svc中的内容仍旧是任务被中断前的数据。
第九条语句: LDMFD R1!, {R4-R6}
STMFD SP!, {R4-R6}
STMFD SP!, {R0}
详解:这三条语句是把R剩下的R0-R4保存到栈中。
第十二条语句:STMFD SP!, {R3}
详解:保存被中断任务的CPSR,R3在第五条语句中已经是把CPSR赋值到R3中了。
寄存器保存完了,接下来就要把SP_svc栈指针保存到任务控制块中了,但是考虑到是否存在中断套嵌,因此要判断一下。因为假如存在中断套嵌,则说明是在中断处理程序中发生的中断,那么就不要保存栈指针到任务控制块了,因为这个工作是在第一次中断发生时干的,也就是在第一次中断的时候已经保存了,后面套嵌的中断自然不需要在保存了。
第十三条语句:
LDR R0,=OSIntNesting
LDRB R1,[R0]
ADD R1,R1,#1
STRB R1,[R0]
CMP R1,#1
BNE %F1
详解:上面的几条语句首先让中断套嵌标志OSIntNesting加1,然后判断是否是1,如果是就表明没有中断套嵌。BNE %F1就是不相等就跳转到后面1标号出处执行。
第十九条语句:
LDR R4,=OSTCBCur
LDR R5,[R4]
STR SP,[R5]
详解:这几条语句就是把SP_svc赋值到被中断的任务的任务控制块中。但是执行的条件就是没有中断套嵌。
第二十二条:
1
MSR CPSR_c,#IRQMODE:OR:NOINT
详解:注意1就是标号,如果有中断套嵌就不执行上面三条语句,直接跳到这个位置执行。该语句是进入IRQ模式。但是如果发生中断套嵌,这任然被执行,只不过在19-21执行之后执行的。下面的内容就是进入中断服务函数中执行用户实现的功能。
第二十三条:LDR LR, =IRQIsrVect
LDR pc, =IRQ_Dispatch
详解:这条语句是设置LR_svc的值为IRQIsrVect,也就是中断服务函数的返回地址,然后就进入IRQ_Dispatch处执行,注意IRQ_Dispatch是函数地址,这个函数中是用户写的代码。
第二十五条:
IRQIsrVect
MSR CPSR_c,#SVCMODE:OR:NOINT
BL OSIntExit
LDMFD SP!,{R4}
MSR SPSR_cxsf,R4
LDMFD SP!,{R0-R12,LR,PC}^
详解:注意这几天语句就是在第二十三条语句中对应的地址,也就是 LDR pc, =IRQ_Dispatch执行完中断服务函数后返回执行的地址。这几条语句是恢复被中断任务的寄存器,让任务继续运行。
在IRQIsrVect 中,首先进入SVC模式,然后调用OSIntExit 函数退出中断处理程序,然后恢复被中断的任务的寄存器。
以上就是一个中断从产生到处理结束后返回的全过程。这段程序并没有判定具体是哪个中断发生了,因此在中断处理程序IRQ_Dispatch(C语言编写)中需要检查SRCPEND,EINTPEND和INTOFFSET这三个寄存器来查看是哪个中断然后才能进一步处理,也可以在汇编代码(启动代码中实现具体是哪个中断)。