本页内容发表于MCUZone,但不代表MCUZone的观点。
如果你对本页的技术内容有疑问,请到 MCUZone技术论坛 发帖。
如果你认为本页的内容侵害了你的权益,请与hotislandn@hotmail.com;hdapple_2000@hotmail.com 联系,我们会在确认后移除。

AT91SAM7Sxx: µC/OS-II 的移植

本文的重点在移植 µC/OS-II AT91SMA7S系列的中断处理。

AT91SAM7Sxx 中断系统

AT91SAM7S系列使用的是 ARM7TDMI核心,处理器的中断源有IRQ与FIQ。
为了处理系统中不同的中断源,AT91SAM7S使用AIC(Advancde Interrupt Controller)来处理系统中所有的中断请求,然后交由处理器处理。

AIC

AIC的功能如下:

  • 能处理多个中断请求
  • 8优先级
  • 向量模式中断

具体可以参考 AT91SAM7S数据手册上有关AIC的章节。

AIC 编程

如果需要使用某个外设的中断,必须对AIC及该外设进行编程:

  • 使能该外设时钟以启用该外设(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;
}
  • 设置该外设的接口中使能该外设中断
  • 在AIC中配置该外设的中断
    1. 中断类型(电平/边沿)
    2. 中断优先级
    3. 中断服务程序
 
//*----------------------------------------------------------------------------
//* \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;
}
  • 在AIC中使能该外设的中断
//*----------------------------------------------------------------------------
//* \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 处理器架构,特别是处理器中断模式及相关状态位。
假设如下:

  1. 高级中断控制器已编程, AIC_SVR 寄存器载入相应的中断服务程序地址且中断使能。
  2. ARM 中断异常向量指令地址处放置下面的指令:

LDR PC, [PC, # -&F20] ; ← 0×18 + 0×08 - 0xF20 = 0xFFFFF100 (#define AT91C_AIC_IVR ((AT91_REG *) 0xFFFFF100) 当nIRQ 出现,若CPSR 中位 “I” 为0(内核级中断使能),序列如下:

  1. CPSR 存于SPSR_irq,程序计数器当前值载入中断链接寄存器(R14_irq) 且程序计数器(R15) 中载入0×18。 在由地址0x1C 取指周期中,ARM内核调整R14_irq,以4递减。
  2. ARM 内核进入中断(IRQ)模式 。
  3. 当加载地址为0×18的指令执行时,程序计数器载入由AIC_IVR读出的值。读AIC_IVR有以下效果:
    • 将当前中断挂起并使能最高优先级中断。当前级为当前中断优先级。
    • 将处理器上的nIRQ 线屏蔽。因此即使向量未用,也必须对AIC_IVR进行读操作以使nIRQ 失效。
    • 若中断源的中断类型被编程为边沿触发则自动清除中断标志,:!:对于电平型中断无此效果。
    • 将当前等级及当前中断序号推入AIC的硬件堆栈。
    • 返回相应于当前中断写入AIC_SVR 的值。
  4. 先前的步骤将使得程序跳转到相应的中断服务程序。
  5. 如果需要中断嵌套,可通过清除CPSR中的“I”位来不加以屏蔽,使得内核又可重新响应nIRQ。使得高优先级的中断可以中断当前中断的执行。
  6. 中断处理程序可按要求执行,开始时保存寄存器,结束时恢复它们的值。在此过程中,高于当前中断优先级的中断将使序列返回步骤1。
    • :!:若中断编程为电平敏感,中断源必须在此过程中清除。
  7. 必须将CPSR 中“I” 位置位以使在退出前屏蔽中断,保证中断退出时严格按照必须步骤完成。
  8. 须写中断结束命令寄存器(AIC_EOICR),以指示AIC 当前中断已完成。这将使得当前优先级由堆栈中弹出,恢复先前优先级。若有其它中断挂起,等于或低于原先优先级却高于当前新的优先级, nIRQ 线将有效,但由于此时CPSR“I”被置位,因此不会立即启动中断序列。然后恢复SPSR_irq,最后存于链接寄存器中的值写入PC。这对下面情况有用:中断返回到中断执行前的状态,将存于SPSR 中的值载入CPSR,根据保存在SPSR_irq中的状态决定是否屏蔽中断。
    • :!:SPSR中的“I” 位非常重要。置位时,则其表示屏蔽指令中断时,ARM 内核处于中断屏蔽的边缘。因此,当SPSR 恢复时,屏蔽指令完成(中断被屏蔽)。

移植中的中断处理

OSTimeTick

OSTimeTick 用于为µC/OS提供时钟节拍,用户系统需要定时调用该函数以使得任务调度器运转。
在AT91SAM7S上可以选择使用TC或者PIT来实现这个功能。
由于TC的功能多,如果仅用于这个功能似乎浪费,因此可以考虑使用PIT。
使用PIT来实现OSTimeTick时需要注意:

  • PIT使用SCLK,也就是片上的32768Hz的RC振荡作为时钟源,这个信号源的准确度不高
  • PIT中断使用SYS中断源,与其它外设复用
  • PIT中断类型应设置为电平类型,否则在读取IVR时的清楚标志位的动作会影响SYS中的其余中断(比如DBGU中断)

OS_CPU_IRQ_ISR

该函数作为第一级的中断处理函数,以汇编编程。主要完成中断前的环境保存,跳转中断,以及中断返回的工作。
下面看下这个函数的一些移植方式:

Micrium 官方代码
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
Hotislandn 移植版本
;************** 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}^
Leafboy 移植版本
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模式下处理中断。

µC/OS-View 的移植

相关链接

µC/OS 官方链接

移植参考

移植范例

AT91SAM7S 相关资源

 
user\dracula\sam_ucos2_porting.txt · 最后更改: 2007/03/07 19:46 由 dracula
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki