天天看點

STM32移植uCOS-II的詳細注釋

1   uCOS移植到STM32中,代碼移植的詳細注釋,參考了網絡上的很多資料,我隻是用我自己的思路整理了一下。

     有不對的地方請大家多多指教,互相學習

2   需要準備兩個資料用于随時檢視一些重點,《Cortex-M3權威指南》和《Cortex-M3_技術參考手冊》

     在代碼的注釋過程中都注明了對應的知識點在哪頁,可以找到對應的地方仔細閱讀,重點需要閱讀的是《Cortex-     M3權威指南》

3   系統複位後預設使用的堆棧指針是MSP,複位後的狀态是特權級線程狀态

4   假如在使用者模式下使用的是PSP,那麼寄存器的數值被儲存到任務堆棧的空間,進入中斷程式後就開始使用MSP,

     如果還有一個高優先級的中斷那麼就繼續的使用MSP,在程式退出最後一級中斷的時候就用使用者堆棧恢複寄存器

5   uCOS-II的任務使用的程序堆棧指針(PSP),異常使用的是主堆棧指針(MSP)

    是以在起始檔案中設定的Stack_Mem SPACE Stack_Size是設定的主堆棧的空間大小

    在中斷嵌套不深的時候可以讓主堆棧占用的空間小一些

os_cpu_c.c

隻有OSTaskStkInit函數的注釋,其他的hook函數沒有包含進來

<span style="font-size:18px;">/*
*********************************************************************************************************
*                                        INITIALIZE A TASK'S STACK
*
* Description: This function is called by either OSTaskCreate() or OSTaskCreateExt() to initialize the
*              stack frame of the task being created.  This function is highly processor specific.
*
* Arguments  : task          is a pointer to the task code
*
*              p_arg         is a pointer to a user supplied data area that will be passed to the task
*                            when the task first executes.
*
*              ptos          is a pointer to the top of stack.  It is assumed that 'ptos' points to
*                            a 'free' entry on the task stack.  If OS_STK_GROWTH is set to 1 then
*                            'ptos' will contain the HIGHEST valid address of the stack.  Similarly, if
*                            OS_STK_GROWTH is set to 0, the 'ptos' will contains the LOWEST valid address
*                            of the stack.
*
*              opt           specifies options that can be used to alter the behavior of OSTaskStkInit().
*                            (see uCOS_II.H for OS_TASK_OPT_xxx).
*
* Returns    : Always returns the location of the new top-of-stack once the processor registers have
*              been placed on the stack in the proper order.
*
* Note(s)    : 1) Interrupts are enabled when your task starts executing.
*              2) All tasks run in Thread mode, using process stack.
*********************************************************************************************************
*/

//做出任務好像被中斷過一樣的假象,用于任務的第一次執行的時候來恢複寄存器的内容
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
    OS_STK *stk;

    (void)opt;                         /* 'opt' is not used, prevent warning                 */
    stk       = ptos;                  /* Load stack pointer                                 */

                                       /* Registers stacked as if auto-saved on exception    */
    *(stk)    = (INT32U)0x01000000L;   /* xPSR,<Cortex-M3技術參考手冊P21>,當T為(第24位)0時,執行指令會引起INVSTATE異常*/
    *(--stk)  = (INT32U)task;          /* 任務的入口位址                                       */
    *(--stk)  = (INT32U)0xFFFFFFFEL;   /*任務第一次被排程從堆棧中恢複寄存器的值的時候LR的值會恢複為0xFFFFFFFEL
					這個值也沒什麼用,因為在進入中斷之後LR的意義發生改變,此時的值也是由硬體自動設定的*/
				       /*LR是連接配接寄存器,用于在執行BL等分支指令的時候自動儲存PC的值,用于儲存子程式的傳回位址*/
									   
				       /* R14 (LR) (init value will cause fault if ever used)*/
	                               /* LR:進入異常時LR的用法将被重新解釋,其值也被更新成一種特殊的值,稱為“EXC_RETURN”,
                                          并且在異常傳回時使用。EXC_RETURN的二進制值除了最低4位外全為1,而其最低4
	                                  位則有另外的含義(見表9.3和表9.4)。<權威指南PDF136頁和139頁>

					合法的EXC_RETURN值及其功能:
					0xFFFF_FFF1 傳回handler模式
					0xFFFF_FFF9 傳回線程模式,并使用主堆棧(SP=MSP)
					0xFFFF_FFFD 傳回線程模式,并使用線程堆棧(SP=PSP)

					如果主程式線上程模式下運作, 并且在使用MSP 時被中斷, 則在服務例程中
					LR=0xFFFF_FFF9(主程式被打斷前的LR已被自動入棧)。
												     
                                        如果主程式線上程模式下運作, 并且在使用PSP 時被中斷, 則在服務例程中
					LR=0xFFFF_FFFD(主程式被打斷前的LR已被自動入棧)。

					如果主程式在Handler模式下運作,則在服務例程中LR=0xFFFF_FFF1(主程式被打斷前的
					LR已被自動入棧)。這時的“主程式”,其實更可能是被搶占的服務例程。事實上,在嵌套
					時,更深層ISR所看到的LR總是0xFFFF_FFF1,

					無效傳回時<權威指南PDF142頁>
					如果LR中的EXC_RETURN不是合法的值(合法值見表9.4,包括企圖傳回ARM狀态),則
					引起用法fault。如果用法fault被除能,也上訪成硬fault。此時,用法Fault狀态寄存器(UFSR,
					位址:0xE000_ED2A)中的INVPC位(位偏移:2),或者是INVSTATE位(位偏移:1)置位。
					*/
					/*無效的中斷傳回(LR 中包含了無效/錯誤的值)會産生用法fault <權威指南PDF119>*/
    *(--stk)  = (INT32U)0x12121212L;    /* R12                                                */
    *(--stk)  = (INT32U)0x03030303L;    /* R3                                                 */
    *(--stk)  = (INT32U)0x02020202L;    /* R2                                                 */
    *(--stk)  = (INT32U)0x01010101L;    /* R1                                                 */
    *(--stk)  = (INT32U)p_arg;          /* R0 : argument                                      */

                                        /* Remaining registers saved on process stack         */
    *(--stk)  = (INT32U)0x11111111L;    /* R11                                                */
    *(--stk)  = (INT32U)0x10101010L;    /* R10                                                */
    *(--stk)  = (INT32U)0x09090909L;    /* R9                                                 */
    *(--stk)  = (INT32U)0x08080808L;    /* R8                                                 */
    *(--stk)  = (INT32U)0x07070707L;    /* R7                                                 */
    *(--stk)  = (INT32U)0x06060606L;    /* R6                                                 */
    *(--stk)  = (INT32U)0x05050505L;    /* R5                                                 */
    *(--stk)  = (INT32U)0x04040404L;    /* R4                                                 */

    return (stk);
}</span>
           

os_cpu_a.asm

<span style="font-size:18px;">;/*********************** (C) COPYRIGHT 2010 Libraworks *************************
;* File Name	: os_cpu_a.asm 
;* Author		: Librae 
;* Version		: V1.0
;* Date			: 06/10/2010
;* Description	: μCOS-II asm port	for STM32
;*******************************************************************************/

		IMPORT  OSRunning           ; External references
		IMPORT  OSPrioCur
		IMPORT  OSPrioHighRdy
		IMPORT  OSTCBCur
		IMPORT  OSTCBHighRdy
		IMPORT  OSIntNesting
		IMPORT  OSIntExit
		IMPORT  OSTaskSwHook
			   
		EXPORT  OSStartHighRdy               
		EXPORT  OSCtxSw
		EXPORT  OSIntCtxSw
		EXPORT  OS_CPU_SR_Save      
		EXPORT  OS_CPU_SR_Restore       
		EXPORT  PendSV_Handler
        	
     
NVIC_INT_CTRL   	EQU     0xE000ED04  ; 中斷控制寄存器,<權威指南P131>表8.5
NVIC_SYSPRI2    	EQU     0xE000ED20  ; 系統異常優先級寄存器陣列 <權威指南P128> 表8.3B
					    ; 0xE000ED20:調試螢幕的優先級配置寄存器位址
					    ; 0xE000ED21:一個沒有使用的位址
				<span style="white-space:pre">	</span>    ; 0xE000ED22:PendSV的優先級配置寄存器位址
					    ; 0xE000ED23:SysTick的優先級配置寄存器位址
										
NVIC_PENDSV_PRI 	EQU     0xFFFF0000  ; 軟體中斷和系統節拍中斷
					    ; (都為最低,0xff).
NVIC_PENDSVSET  	EQU     0x10000000  ; 觸發軟體中斷的值.<權威指南P131>表8.5



		PRESERVE8 
		
		AREA    |.text|, CODE, READONLY
        THUMB 
    
           

;********************************************************************************************************
;                                   CRITICAL SECTION METHOD 3 FUNCTIONS
;
; Description: Disable/Enable interrupts by preserving the state of interrupts.  Generally speaking you
;              would store the state of the interrupt disable flag in the local variable 'cpu_sr' and then
;              disable interrupts.  'cpu_sr' is allocated in all of uC/OS-II's functions that need to
;              disable interrupts.  You would restore the interrupt disable state by copying back 'cpu_sr'
;              into the CPU's status register.
;
; Prototypes :     OS_CPU_SR  OS_CPU_SR_Save(void);
;                  void       OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
;
;
; Note(s)    : 1) These functions are used in general like this:
;
;                 void Task (void *p_arg)
;                 {
;                 #if OS_CRITICAL_METHOD == 3          /* Allocate storage for CPU status register */
;                     OS_CPU_SR  cpu_sr;
;                 #endif
;
;                          :
;                          :
;                     OS_ENTER_CRITICAL();             /* cpu_sr = OS_CPU_SaveSR();                */
;                          :
;                          :
;                     OS_EXIT_CRITICAL();              /* OS_CPU_RestoreSR(cpu_sr);                */
;                          :
;                          :
;                 }
;********************************************************************************************************

OS_CPU_SR_Save
    MRS     R0, PRIMASK    	;Set prio int mask to mask all (except faults)
				;PRIMASK是隻有一個位的寄存器,置1的時候關閉所有的可屏蔽中斷
				;隻有NMI和硬fault可以響應,預設值為0,表示沒有關中斷
				;MRS,加載特俗功能寄存器的值到Rn,這裡使 R0=PRIMASK
							
    CPSID   I		        ;快速關閉中斷的指令,将PRIMASK置1
    BX      LR		   	;BX reg 轉移到由寄存器reg給出的位址,這個指令不儲存下一條指令的PC值到LR寄存器
				;reg 的最低位訓示出在轉移後,将進入的狀态是ARM(LSB=0)還是Thumb(LSB=1)
				;CM3隻在Thumb中運作,就必須保證reg 的LSB=1,否則fault<權威指南P107>表7.1系統異常清單
							
				;BL Label; 轉移到Label 處對應的位址,并且把轉移前的下條指令位址儲存到LR

OS_CPU_SR_Restore
    MSR     PRIMASK, R0    	;MSR,存儲Rn 的值到特殊功能寄存器,這裡使PRIMASK=R0
    BX      LR


;/**************************************************************************************
;* 函數名稱: OSStartHighRdy
;*
;* 功能描述: 使用排程器運作第一個任務
;* 
;* 參    數: None
;*
;* 返 回 值: None
;**************************************************************************************/  

OSStartHighRdy
        LDR     R4, =NVIC_SYSPRI2      	;set the PendSV exception priority
					;加載立即數NVIC_SYSPRI2到R4,這裡LDR是僞指令
										
					;<權威指南 P128>
					;NVIC_SYSPRI2 EQU 0xE000ED20
					;0xE000ED20:調試螢幕的優先級配置寄存器位址
					;0xE000ED21:一個沒有使用的位址
					;0xE000ED22:PendSV的優先級配置寄存器位址
					;0xE000ED23:SysTick的優先級配置寄存器位址
										     			       
        LDR     R5, =NVIC_PENDSV_PRI   	;
        STR     R5, [R4]	        ;設定PendSV和SysTick異常的優先級為FF
					;這樣之後,就把PendSV,SysTick優先級設定為了FF
					;調試螢幕的優先級就設定為了00

        MOV     R4, #0                 	;set the PSP to 0 for initial context switch call
        MSR     PSP, R4		        ;設定線程堆棧指針為0,PSP=R4
					;用于在PendSV_Handler函數中判斷系統是否是進行的第一次任務排程

        LDR     R4, =OSRunning         	;OSRunning = TRUE,表示系統開始運作
        MOV     R5, #1			;在ucos-ii.h中OSRunning被定義為 OS_EXT  BOOLEAN OSRunning; 就是一個unsigned char 類型
        STRB    R5, [R4]               	;STRB:把一個寄存器的低位元組存儲到存儲器中

					;
        LDR     R4, =NVIC_INT_CTRL     	;rigger the PendSV exception (causes context switch)
					;NVIC_INT_CTRL  EQU 0xE000ED04; 中斷控制寄存器,<權威指南P131>表8.5
        LDR     R5, =NVIC_PENDSVSET    	;NVIC_PENDSVSET EQU 0x10000000; 軟體觸發PendSV中斷,在PendSV中進行任務切換
        STR     R5, [R4]               	;

        CPSIE   I                      	;enable interrupts at processor level
					;開中斷
										
OSStartHang                             ;正常情況下不會執行到下面的死循環代碼
        B       OSStartHang            	;should never get here

;/**************************************************************************************
;* 函數名稱: OSCtxSw
;*
;* 功能描述: 任務級上下文切換         
;*
;* 參    數: None
;*
;* 返 回 值: None
;***************************************************************************************/
  
OSCtxSw
	PUSH    {R4, R5}                ;寄存器的PUSH 和POP 操作永遠都是4 位元組對齊的——也就是說他們的位址必須是
					;0x4,0x8,0xc,……。這樣一來,R13 的最低兩位被硬線連接配接到0,并且總是讀出0(Read As Zero
					;堆棧指針的最低兩位永遠是0,這意味着堆棧總是4位元組對齊的 <權威指南P27>
					;這就是為什麼 定義OS_STK為32位的,typedef unsigned int   OS_STK;
					;PUSH,POP的批量操作<權威指南P47>圖3.10
					;不管在寄存器清單中,寄存器的序号是以什麼順序給出的,彙編器都将把它們升
					;序排序。然後PUSH 指令按照從大到小的順序依次入棧,POP 則按從小到大的順序依次出棧
										
        LDR     R4, =NVIC_INT_CTRL  	;觸發PendSV異常 (causes context switch)
        LDR     R5, =NVIC_PENDSVSET		;
        STR     R5, [R4]
		POP     {R4, R5}
        BX      LR

;/**************************************************************************************
;* 函數名稱: OSIntCtxSw
;*
;* 功能描述: 中斷級任務切換
;*
;* 參    數: None
;*
;* 返 回 值: None
;***************************************************************************************/

OSIntCtxSw
	PUSH    {R4, R5}
        LDR     R4, =NVIC_INT_CTRL      ;觸發PendSV異常 (causes context switch)
        LDR     R5, =NVIC_PENDSVSET     ;
        STR     R5, [R4]
		POP     {R4, R5}
        BX      LR
        NOP

;/**************************************************************************************
;* 函數名稱: OSPendSV
;*
;* 功能描述: OSPendSV is used to cause a context switch.
;*
;* 參    數: None
;*
;* 返 回 值: None
;***************************************************************************************/

PendSV_Handler
    CPSID   I                           ; 關中斷,阻止任務切換被打斷
    MRS     R0, PSP                     ; 如果在用PSP堆棧,則可以忽略儲存寄存器,參考CM3權威中的雙堆棧-白菜注
    CBZ     R0, PendSV_Handler_Nosave	; Skip register save the first time
				        ; CBZ 比較,如果結果為0 就轉移
				        ; PendSV_Handler_Nosave這個函數隻在第一次進行任務排程的時候調用一次
					; 我的了解是

					; 第一次會跳過執行下面的五句代碼,以後每次任務切換的時候都會執行
					; 第一次不執行是因為任務堆棧裡面的寄存器資料都是手動填進去的,不需要再儲存一次
    SUBS    R0, R0, #0x20               ; Save remaining regs r4-11 on process stack
    STM     R0, {R4-R11}

    LDR     R1, =OSTCBCur               ; OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, [R1]
    STR     R0, [R1]                    ; R0 is SP of process being switched out
                                        ; At this point, entire context of process has been saved
                                        ; 
PendSV_Handler_Nosave
    PUSH    {R14}                       ; Save LR exc_return value
    LDR     R0, =OSTaskSwHook           ; OSTaskSwHook()
    BLX     R0			<span style="white-space:pre">	</span>; 調用鈎子函數
    POP     {R14}							

    LDR     R0, =OSPrioCur              ; OSPrioCur = OSPrioHighRdy;   
					; OS_EXT  INT8U OSPrioCur;
    LDR     R1, =OSPrioHighRdy          ; OS_EXT  INT8U OSPrioHighRdy;
    LDRB    R2, [R1]
    STRB    R2, [R0]

    LDR     R0, =OSTCBCur               ; OSTCBCur  = OSTCBHighRdy;
					; 
					; typedef struct os_tcb {OS_STK *OSTCBStkPtr;...}                                              
					; OS_EXT  OS_TCB   *OSTCBCur;
    					       
    LDR     R1, =OSTCBHighRdy           ; OS_EXT  OS_TCB   *OSTCBHighRdy;
    LDR     R2, [R1]                    ; R2是最高優先級任務TCB管理塊所在的位址
    STR     R2, [R0]			; 使OSTCBCur指向優先級最高的任務的TCB管理塊

    LDR     R0, [R2]                    ; R0是新任務的堆棧指針SP,SP = OSTCBHighRdy->OSTCBStkPtr;			
                                        ; OS_TCB結構體定義中第一個位元組是一個堆棧指針,是以這裡R0中存放的就是最高優先級任務的堆棧指針
											
    LDM     R0, {R4-R11}                ; 從最高優先級的堆棧中恢複R4-R1寄存器的值,其他的寄存器由硬體自動恢複
					; LDM:從一片連續的位址空間中加載多個字到若幹寄存器
    ADDS    R0, R0, #0x20               ; 修改堆棧指針的指向,這裡恢複了8個寄存器,每個寄存器占用4個位元組,一共32個位元組,也就是0X20個
											
    MSR     PSP, R0                     ; 更新程序堆棧,用新任務的SP加載PSP
    ORR     LR, LR, #0x04               ; 確定LR位2為1,傳回後使用程序堆棧,因為在第一次排程任務之前系統預設使用的是MSP且線上程模式下運<span style="white-space:pre">							</span>; 行,第一次進入異常的時候LR=FFFFFFF9
				<span style="white-space:pre">	</span>; 為了確定第一次進入任務排程之後能正确的傳回線程模式,并使用線程堆棧(SP=PSP),是以或上#0X04,
				<span style="white-space:pre">	</span>; 第一次 0XFFFFFFF9 | 0X04 = 0XFFFFFFFD
											
					; 改變處理器的模式也有其它的方式:在異常傳回時,通過修改LR 的位2,也能實作模式切換 <權威指南P42>
					; 通過修改CONTROL寄存器的CONTROL[1]的值也可以選擇堆棧指針,0=主堆棧指針(MSP),1=程序堆棧指針(PSP)
					; ORR.W Rd, Rn, #imm12 ; Rd = Rn | imm12 按

					;<權威指南P139>
					;在進入異常服務程式後,LR的值被自動更新為特殊的EXC_RETURN
					;這是一個高28位全為1的值,隻有[3:0]的值有特殊含義,如表9.3所示。當異常服務例程把這個
					;值送往PC時,就會啟動處理器的中斷傳回序列。因為LR的值是由CM3自動設定的,是以隻要沒有特殊需求,<span style="white-space:pre">						</span>;就不要改動它											
					;或上#0X04就是置位位2,表示從程序堆棧中做出棧操作,傳回後使用PSP
    					       
    CPSIE   I
    BX      LR                          ; 中斷傳回的時候由硬體自動傳回最高優先級堆棧中的其他寄存器的值
				        ; 這裡才是整個PendSV_Handler函數的傳回點,前面的CBZ R0, PendSV_Handler_Nosave隻是做一下判斷
					; 是否執行CBZ R0, PendSV_Handler_Nosave後面的五句代碼
					; 中斷傳回時,處理器會自動把R0-R3,R12,LR,PC,PSR 彈出堆棧

 end  </span>
           

os_cpu.h

在這個頭檔案就是一些聲明,沒有什麼需要注釋的

繼續閱讀