AT91SAM7S系列使用的是 ARM7TDMI核心,处理器的中断源有IRQ与FIQ。
为了处理系统中不同的中断源,AT91SAM7S使用AIC(Advancde Interrupt Controller)来处理系统中所有的中断请求,然后交由处理器处理。
如果需要使用某个外设的中断,必须对AIC及该外设进行编程:
//*----------------------------------------------------------------------------
//* \fn AT91F_PMC_EnablePeriphClock
//* \brief Enable peripheral clock
//*----------------------------------------------------------------------------
__inline void AT91F_PMC_EnablePeriphClock (
AT91PS_PMC pPMC, // \arg pointer to PMC controller
unsigned int periphIds) // \arg IDs of peripherals
{
pPMC->PMC_PCER = periphIds;
}
//*----------------------------------------------------------------------------
//* \fn AT91F_AIC_ConfigureIt
//* \brief Interrupt Handler Initialization
//*----------------------------------------------------------------------------
__inline unsigned int AT91F_AIC_ConfigureIt (
AT91PS_AIC pAic, // \arg pointer to the AIC registers
unsigned int irq_id, // \arg interrupt number to initialize
unsigned int priority, // \arg priority to give to the interrupt
unsigned int src_type, // \arg activation and sense of activation
void (*newHandler) () ) // \arg address of the interrupt handler
{
unsigned int oldHandler;
unsigned int mask ;
oldHandler = pAic->AIC_SVR[irq_id];
mask = 0x1 << irq_id ;
//* Disable the interrupt on the interrupt controller
pAic->AIC_IDCR = mask ;
//* Save the interrupt handler routine pointer and the interrupt priority
pAic->AIC_SVR[irq_id] = (unsigned int) newHandler ;
//* Store the Source Mode Register
pAic->AIC_SMR[irq_id] = src_type | priority ;
//* Clear the interrupt on the interrupt controller
pAic->AIC_ICCR = mask ;
return oldHandler;
}
//*----------------------------------------------------------------------------
//* \fn AT91F_AIC_EnableIt
//* \brief Enable corresponding IT number
//*----------------------------------------------------------------------------
__inline void AT91F_AIC_EnableIt (
AT91PS_AIC pAic, // \arg pointer to the AIC registers
unsigned int irq_id ) // \arg interrupt number to initialize
{
//* Enable the interrupt on the interrupt controller
pAic->AIC_IECR = 0x1 << irq_id ;
}
本节简单给出使用AIC 时的中断处理序列。假设程序员已了解 ARM 处理器架构,特别是处理器中断模式及相关状态位。
假设如下:
LDR PC, [PC, # -&F20] ; ← 0×18 + 0×08 - 0xF20 = 0xFFFFF100 (#define AT91C_AIC_IVR ((AT91_REG *) 0xFFFFF100) 当nIRQ 出现,若CPSR 中位 “I” 为0(内核级中断使能),序列如下:
OSTimeTick 用于为µC/OS提供时钟节拍,用户系统需要定时调用该函数以使得任务调度器运转。
在AT91SAM7S上可以选择使用TC或者PIT来实现这个功能。
由于TC的功能多,如果仅用于这个功能似乎浪费,因此可以考虑使用PIT。
使用PIT来实现OSTimeTick时需要注意:
该函数作为第一级的中断处理函数,以汇编编程。主要完成中断前的环境保存,跳转中断,以及中断返回的工作。
下面看下这个函数的一些移植方式:
OS_CPU_IRQ_ISR
; Disable FIQ for a moment
MSR CPSR_c, #(NO_INT | IRQ32_MODE) ; Change to IRQ mode (to use the IRQ stack to handle interrupt)
STMFD SP!, {R1-R3} ; PUSH WORKING REGISTERS ONTO IRQ STACK
MOV R1, SP ; Save IRQ stack pointer
ADD SP, SP,#12 ; Adjust IRQ stack pointer
SUB R2, LR,#4 ; Adjust PC for return address to task
MRS R3, SPSR ; Copy SPSR (i.e. interrupted task's CPSR) to R3
MSR CPSR_c, #(NO_INT | SVC32_MODE) ; Change to SVC mode
; SAVE TASK'S CONTEXT ONTO TASK'S STACK
STMFD SP!, {R2} ; Push task's Return PC
STMFD SP!, {LR} ; Push task's LR
STMFD SP!, {R4-R12} ; Push task's R12-R4
LDMFD R1!, {R4-R6} ; Move task's R1-R3 from IRQ stack to SVC stack
STMFD SP!, {R4-R6}
STMFD SP!, {R0} ; Push task's R0 onto task's stack
STMFD SP!, {R3} ; Push task's CPSR (i.e. IRQ's SPSR)
; HANDLE NESTING COUNTER
LDR R0, ??OS_IntNesting ; OSIntNesting++;
LDRB R1, [R0]
ADD R1, R1,#1
STRB R1, [R0]
CMP R1, #1 ; if (OSIntNesting == 1) {
BNE OS_CPU_IRQ_ISR_1
LDR R4, ??OS_TCBCur ; OSTCBCur->OSTCBStkPtr = SP
LDR R5, [R4]
STR SP, [R5] ; }
OS_CPU_IRQ_ISR_1
MSR CPSR_c, #(NO_IRQ | IRQ32_MODE) ; Re-enable FIQ, Change to IRQ mode (to use the IRQ stack to handle interrupt)
LDR R0, ??OS_CPU_IRQ_ISR_Handler ; OS_CPU_IRQ_ISR_Handler();
MOV LR, PC
BX R0
MSR CPSR_c, #(NO_INT | SVC32_MODE) ; Change to SVC mode
LDR R0, ??OS_IntExit ; OSIntExit();
MOV LR, PC
BX R0
; RESTORE NEW TASK'S CONTEXT
LDMFD SP!, {R4} ; Pop new task's CPSR
MSR SPSR_cxsf, R4
LDMFD SP!, {R0-R12,LR,PC}^ ; Pop new task's context
;************** enter IRQ mode, interrupt was disabled ********************
SUB LR,LR,#4 ; IRQ return address
STMFD SP!,{LR}
STMFD SP!,{R0-R3} ; Save registers, as ATPCS required
MRS R3,SPSR ;
STMFD SP!,{R3} ; Save interrupted mode CPSR
LDR R0,_OS_IntNesting
LDRB R1,[R0]
ADD R1,R1,#1
STRB R1,[R0] ; increase OSIntNesting
CMP R1, #1 ; the first interrupt
BNE IRQ_Nesting ; the nesting irq
; this is the first interrupt
; save the interrupted task's stack info
ADD R2,SP,#4 ; keep IRQ stack for geting R0-R3
MOV R0,LR
;************** Save task context *****************************************
MSR CPSR_cxsf,#(NO_INT | SVC32_MODE) ; switch to SVC32 mode, disable all interrupts
STMFD SP!,{R0} ; PC
STMFD SP!,{R4-R12,LR} ; other registers, task LR
LDMIA R2!,{R4-R7} ; as R0-R3
STMFD SP!,{R4-R7} ; R0-R3
STMFD SP!,{R3} ; CPSR
LDR R4, _OS_TCBCur ; OSTCBCur->OSTCBStkPtr = SP
LDR R5, [R4]
STR SP, [R5]
;************** Execute ISR in IRQ mode, with interrupts enable ***********
IRQ_Nesting
MSR CPSR_cxsf,#(NO_INT | IRQ32_MODE) ; swith back to IRQ mode
LDR R0,_OS_CPU_IRQ_ISR_Handler
MOV LR,PC
BX R0 ; call ISR
MSR CPSR_cxsf,#(NO_IRQ | IRQ32_MODE) ; swith to IRQ mode, disable interrupts
MOV R14,SP
ADD SP,SP,#24 ; Remove contents in IRQ stack
; Pushed 6 items(4bytes each) in IRQ stack at the begining of this routine
; if Context switch happened below, the program will not return thus the
; IRQ stack has no chance to pop them out, this cause rubbish in IRQ stack
;************** Execute OSIntExit in SVC mode *****************************
MSR CPSR_cxsf,#(NO_INT | SVC32_MODE) ; disable interrupts
LDR R0,_OS_IntExit
MOV LR,PC
BX R0 ; OSIntExit will not return if another task is ready with higher priority
;************** IRQ end ***************************************************
; no higher priority task or OSIntNesting is not 0
; code for exiting from IRQ
; it will return to the orignal task or the interrupted ISR (interrupt nesting)
LDR R0,_OS_IntNesting ; if OSIntNesting = 0, return to the original task
LDRB R0,[R0]
CMP R0,#0
BNE IRQ_EXIT
;MSR CPSR_cxsf,#(NO_INT | SVC32_MODE)
LDMFD SP!,{R0} ; CPSR
MSR SPSR_cxsf,R0
LDMFD SP!,{R0-R12,LR,PC}^
; end of interrupt, return to orignal task
; the task stack was saved before
IRQ_EXIT
; interrupt nesting
; switch back to IRQ mode, pop out the IRQ registers
MSR CPSR_cxsf,#(NO_IRQ | IRQ32_MODE)
MOV SP,R14 ; restore the IRQ stack point
LDMFD SP!,{R0} ; CPSR
MSR SPSR_cxsf,R0
LDMFD SP!,{R0-R3,PC}^
OSIntCtxSw
;***********************************************************
; IRQ STACK <LOW> R4 R14 R0 R1 R2 R3 R12 SPSR LR(PC) <HIGH>
;***********************************************************
; SAVE TASK'S CONTEXT ONTO TASK'S STACK
ADD SP, SP, #(7 + 2)*4 ; Adjust IRQ stack pointer
; {R0-R3, R12, SPSR, LR}=7*4, OSIntExit="2"*4
SUB R0, SP, #3*4 ; R0->R12
MSR CPSR_c, #NO_INT | SYS_MODE ; Change to SYS mode
LDMIA R0, {R1-R3} ; R12,SPSR,LR(PC)=>R1,R2,R3
SUB R0, R0, #4*4 ; Moving (R3-R0)
STMFD SP!, {R3} ; R3(PC)=>[SP]
STMFD SP!, {R1, LR} ; R1(R12),LR=>[SP]
STMFD SP!, {R4-R11} ; R4-R11=>[SP]
LDMIA R0, {R4-R7} ; R0-R3=>R4-R7
STMFD SP!, {R2,R4-R7} ; R2(SPSR),R4-R11(R0-R3)=>[SP]
B OSCtxSw1
;*********************************************************************************************************
; IRQ Interrupt Service Routine
;*********************************************************************************************************
RSEG CODE:CODE:NOROOT(2)
CODE32
OS_CPU_IRQ_ISR
SUB LR, LR, #4 ;- Adjust and save LR_irq in IRQ stack
STMFD SP!, {LR}
MRS R14, SPSR ;- Save SPSR need to be saved for nested interrupt
STMFD SP!, {R12, R14}
; HANDLE NESTING COUNTER
LDR R14, ??OS_IntNesting ; OSIntNesting++;
LDRB R12, [R14]
ADD R12, R12,#1
STRB R12, [R14]
;- Write in the IVR to support Protect Mode
;- No effect in Normal Mode
;- De-assert the NIRQ and clear the source in Protect Mode
LDR R14, =AT91C_BASE_AIC
LDR R12 , [R14, #AIC_IVR]
STR R14, [R14, #AIC_IVR]
;IRQ_ISR_Handler {
MSR CPSR_c, #IRQ_MODE ;- Enable Interrupt
STMFD SP!, {R0-R3} ;- Save scratch/used registers and LR in IRQ Stack
MOV LR, PC ;- Branch to the routine pointed by the AIC_IVR
BX R12 ; IRQ_ISR_Handler()
MSR CPSR_c, #I_BIT | IRQ_MODE ;- Disable Interrupt and switch back in IRQ mode
;} IRQ_ISR_Handler
LDR R14, =AT91C_BASE_AIC ;- Mark the End of Interrupt on the AIC
STR R14, [R14, #AIC_EOICR]
LDR R12, ??OS_IntExit ; OSIntExit();
MOV LR, PC
BX R12
LDMIA SP!, {R0-R3} ;- Restore scratch/used registers and LR from User Stack
LDMIA SP!, {R12, R14} ;- Restore SPSR_irq and r12 from IRQ stack
MSR SPSR_cxsf, R14
LDMIA SP!, {PC}^ ;- Restore adjusted LR_irq from IRQ stack directly in the PC
比较以上各版本可以发现,官方版本在每次中断发生时都将寄存器保存到任务的堆栈而无视OS_IntNesting的值,会对任务堆栈造成压力。
Hotislandn的版本仅在OS_IntNesting为1时保存寄存器到任务,结构也比较清楚,但是程序比较长,中断时间较长。
leafboy的版本对官方版本做了改动,仅保存必要的寄存器(根据 ATPCS规范),仅在中断中发生任务切换时才保存寄存器到被中断的任务,而前面两种移植方式均在进入中断时就会保存寄存器到任务堆栈,因此时间消耗比较小(并不是每次中断都会造成任务切换)。
后两种移植方式都基于官方的移植。
以上三种移植方式都在IRQ模式下处理中断。
文章评论(0条评论)
登录后参与讨论