ucosii在stm32上的移植詳解3


    移植詳解1和2中主要講了移植需要用到的基礎知識,本文則對具體的移植過程進行介紹。
    首先從micrium網站上下載官方移植版本(編譯器使用ARM/Keil的,V2.86版本,V2.85有問題)。
    下載地址:
http://micrium.com/page/downloads/ports/st/stm32
    解壓縮後得到如下文件夾和文件:
    Micrium\
       AppNotes
       Licensing
       Software
       ReadMe.pdf

    AppNotes包含ucosii移植說明文件。這兩個文件中我們僅需關心Micrium\AppNotes\AN1xxx-RTOS\AN1018-uCOS-II-Cortex-M3\AN-1018.pdf。因爲這個文件對ucosii在CM3內核移植過程中需要修改的代碼進行了說明。
   Licensing包含ucosii使用許可證。
   Software下有好幾個文件夾,在本文的移植中僅需關心uCOS-II即可。
       CPU: stm32標準外設庫
       EvalBoards: micrium官方評估板相關代碼
       uc-CPU: 基於micrium官方評估板的ucosii移植代碼
       uC-LCD:micrium官方評估板LCD驅動代碼
       uc-LIB: micrium官方的一個庫代碼
       uCOS-II: ucosii源代碼
       uC-Probe: 和uC-Probe相關代碼
   ReadMe.pdf就不說了。

    好了,官方的東西介紹完了,該我們自己建立工程着手移植了。關於建立工程,並使用stm32標準外設庫在我之前的文章《stm32標準外設庫使用詳解》已有介紹,這裏請大家下載其中模板代碼http://download.csdn.net/source/3448543),本文的移植是基於這個工程的。
    建立文件夾template\src\ucosii, template\src\ucosii\src, template\src\ucosii\port;
    把Micrium\Software\uCOS-II\Source下的文件拷貝至template\src\ucosii\src;
    把Micrium\Software\uCOS-II\Ports\ARM-Cortex-M3\Generic\RealView下的文件拷貝至

template\src\ucosii\port;
   ucosii\src下的代碼是ucosii中無需修改部分,ucosii\port下的代碼是移植時需要修改的。爲防止對
源碼的誤改動造成移植失敗,可以把ucosii\src下的代碼文件設爲只讀。
   這裏根據AN-1018.pdf和移植詳解1、2中介紹的移植基礎知識,對ucosii\port下的代碼解釋一下。

os_cpu.h

#ifdef   OS_CPU_GLOBALS
#define  OS_CPU_EXT
#else
#define  OS_CPU_EXT  extern
#endif

typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;
typedef signed short INT16S;
typedef unsigned int INT32U;
typedef signed int INT32S;
typedef float FP32;
typedef double FP64;
就不解釋了。

typedef unsigned int OS_STK; 
typedef unsigned int OS_CPU_SR;

   因爲CM3是32位寬的,所以OS_STK(堆棧的數據類型)被類型重定義爲unsigned int。
   因爲CM3的狀態寄存器(xPSR)是32位寬的,因此OS_CPU_SR被類型重定義爲unsigned int。OS_CPU_SR
是在OS_CRITICAL_METHOD方法3中保存cpu狀態寄存器用的。在CM3中,移植OS_ENTER_CRITICAL(),OS_EXIT_CRITICAL()選方法3是最合適的。

#define  OS_CRITICAL_METHOD   3

#if OS_CRITICAL_METHOD == 3
#define  OS_ENTER_CRITICAL()  {cpu_sr = OS_CPU_SR_Save();}
#define  OS_EXIT_CRITICAL()   {OS_CPU_SR_Restore(cpu_sr);}
#endif

   具體定義宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL(),其中OS_CPU_SR_Save()和OS_CPU_SR_Restore()是用匯編代碼寫的,代碼在os_cpu_a.asm中,到時再解釋。

#define  OS_STK_GROWTH        1
   CM3中,棧是由高地址向低地址增長的,因此OS_STK_GROWTH定義爲1。

#define OS_TASK_SW() OSCtxSw()
   定義任務切換宏,OSCtxSw()是用匯編代碼寫的,代碼在os_cpu_a.asm中,到時再解釋。

#if OS_CRITICAL_METHOD == 3                        
OS_CPU_SR  OS_CPU_SR_Save(void);
void       OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
#endif

void       OSCtxSw(void);
void       OSIntCtxSw(void);
void       OSStartHighRdy(void);
void       OS_CPU_PendSVHandler(void);
 
void       OS_CPU_SysTickHandler(void);
void       OS_CPU_SysTickInit(void);
INT32U     OS_CPU_SysTickClkFreq(void);

    申明幾個函數,這裏要注意最後三個函數需要註釋掉,爲什麼呢?
    OS_CPU_SysTickHandler()定義在os_cpu_c.c中,是SysTick中斷的中斷處理函數,而stm32f10x_it.c,
中已經有該中斷函數的定義SysTick_Handler(),這裏也就不需要了,是不是很奇怪官方移植版爲什麼會這樣弄吧,後面我會解釋的。
    OS_CPU_SysTickInit()定義在os_cpu_c.c中,用於初始化SysTick定時器,它依賴於
OS_CPU_SysTickClkFreq(),而此函數我們自己會實現,所以註釋掉。
    OS_CPU_SysTickClkFreq()定義在BSP.C (Micrium\Software\EvalBoards)中,而本文移植中並未用到
BSP.C,後面我們會自己實現,因此可以把它註釋掉。

os_cpu_c.c
    ucosii移植時需要我們寫10個相當簡單的C函數。

    OSInitHookBegin()
    OSInitHookEnd()
    OSTaskCreateHook()
    OSTaskDelHook()
    OSTaskIdleHook()
    OSTaskStatHook()
    OSTaskStkInit()
    OSTaskSwHook()
    OSTCBInitHook()
    OSTimeTickHook()

    這些函數除了OSTaskStkInit(),都是一些hook函數。這些hook函數如果不使能的話,都不會用上,也都比較簡單,看看就應該明白了,所以就不介紹。
    下面就說一說OSTaskStkInit()。說之前還是得先說一下任務切換,因爲初始化任務堆棧,是爲任務
切換服務的。代碼在正常運行時,一行一行往下執行,怎麼才能跑到另一個任務(即函數)執行呢?首先大家可以回想一下中斷過程,當中斷髮生時,原來函數執行的地方(程序計數器PC、處理器狀態寄存器及所有通用寄存器,即當前代碼的現場)被保存到棧裏面去了,然後開始取中斷向量,跑到中斷函數裏面執行。執行完了呢,想回到原來函數執行的地方,該怎麼辦呢,只要把棧中保存的原來函數執行的信息恢復即可(把棧中保存的代碼現場重新賦給cpu的各個寄存器),一切就都回去了,好像什麼事都沒發生一樣。這個過程大家應該都比較熟悉,任務切換和這有什麼關係,試想一下,如果有3個函數foo1(), foo2(), foo3()像是剛被中斷,現場保存到棧裏面去了,而中斷返回時做點手腳(調度程序的作用),想回哪個回哪個,是不是就做了函數(任務)切換了。看到這裏應該有點明白OSTaskStkInit()的作用了吧,它被任務創建函數調用,所以要在開始時,在棧中作出該任務好像剛被中斷一樣的假象。(關於任務切換的原理邵老師書中的3.06節有介紹)。
    那麼中斷後棧中是個什麼情形呢,<<ARM Cortex-M3權威指南>>中9.1.1有介紹,xPSR,PC,LR,R12
,R3-R0被自動保存到棧中的,R11-R4如果需要保存,只能手工保存。因此OSTaskStkInit()的工作就是在任務自己的棧中保存cpu的所有寄存器。這些值裏R1-R12都沒什麼意義,這裏用相應的數字代號(如R1用0x01010101)主要是方便調試。
    其他幾個:
    xPSR = 0x01000000L,xPSR T位(第24位)置1,否則第一次執行任務時Fault,
    PC肯定得指向任務入口,
    R14 = 0xFFFFFFFEL,最低4位爲E,是一個非法值,主要目的是不讓使用R14,即任務是不能返回的。
    R0用於傳遞任務函數的參數,因此等於p_arg。
   
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        */
    *(--stk)  = (INT32U)task;         /* Entry Point */
    /* R14 (LR) (init value will cause fault if ever used)*/
    *(--stk)  = (INT32U)0xFFFFFFFEL;  
    *(--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);
}

把OS_CPU_SysTickHandler(), OS_CPU_SysTickInit()註釋掉。

#define  OS_CPU_CM3_NVIC_ST_CTRL    (*((volatile INT32U *)0xE000E010)) 
#define  OS_CPU_CM3_NVIC_ST_RELOAD  (*((volatile INT32U *)0xE000E014)) 
#define  OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U *)0xE000E018)) 
#define  OS_CPU_CM3_NVIC_ST_CAL     (*((volatile INT32U *)0xE000E01C))

#define  OS_CPU_CM3_NVIC_ST_CTRL_COUNT                    0x00010000   
#define  OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC                  0x00000004   
#define  OS_CPU_CM3_NVIC_ST_CTRL_INTEN                    0x00000002   
#define  OS_CPU_CM3_NVIC_ST_CTRL_ENABLE                   0x00000001 

把上面這些宏定義也註釋掉,因爲它們都用於OS_CPU_SysTickHandler(), OS_CPU_SysTickInit()。

os_cpu_a.asm
這個文件包含着必須用匯編寫的代碼。

    EXTERN  OSRunning    ; External references
    EXTERN  OSPrioCur
    EXTERN  OSPrioHighRdy
    EXTERN  OSTCBCur
    EXTERN  OSTCBHighRdy
    EXTERN  OSIntNesting
    EXTERN  OSIntExit
    EXTERN  OSTaskSwHook
    申明這些變量是在其他文件定義的,本文件只做引用(有幾個好像並未引用,不過沒有關係)。

    EXPORT  OS_CPU_SR_Save   ; Functions declared in this file
    EXPORT  OS_CPU_SR_Restore
    EXPORT  OSStartHighRdy
    EXPORT  OSCtxSw
    EXPORT  OSIntCtxSw
    EXPORT  OS_CPU_PendSVHandler
    申明這些函數是在本文件中定義的。

NVIC_INT_CTRL   EQU     0xE000ED04   ;中斷控制及狀態寄存器ICSR的地址
NVIC_SYSPRI14   EQU     0xE000ED22   ;PendSV優先級寄存器的地址
NVIC_PENDSV_PRI EQU           0xFF   ;PendSV中斷的優先級爲255(最低)
NVIC_PENDSVSET  EQU     0x10000000   ;位28爲1
    定義幾個常量,類似C語言中的#define預處理指令。

OS_CPU_SR_Save
    MRS     R0, PRIMASK   ;讀取PRIMASK到R0中,R0爲返回值
    CPSID   I             ;PRIMASK=1,關中斷(NMI和硬fault可以響應)
    BX      LR            ;返回

OS_CPU_SR_Restore
    MSR     PRIMASK, R0   ;讀取R0到PRIMASK中,R0爲參數
    BX      LR            ;返回

OSStartHighRdy()由OSStart()調用,用來啓動最高優先級任務,當然任務必須在OSStart()前已被創建。

OSStartHighRdy
    ;設置PendSV中斷的優先級 #1
    LDR     R0, =NVIC_SYSPRI14    ;R0 = NVIC_SYSPRI14
    LDR     R1, =NVIC_PENDSV_PRI  ;R1 = NVIC_PENDSV_PRI
    STRB    R1, [R0]              ;*(uint8_t *)NVIC_SYSPRI14 = NVIC_PENDSV_PRI

    ;設置PSP爲0 #2
    MOVS    R0, #0                ;R0 = 0
    MSR     PSP, R0               ;PSP = R0

    ;設置OSRunning爲TRUE
    LDR     R0, =OSRunning        ;R0 = OSRunning
    MOVS    R1, #1                ;R1 = 1
    STRB    R1, [R0]              ;OSRunning = 1
 
    ;觸發PendSV中斷 #3
    LDR     R0, =NVIC_INT_CTRL    ;R0 = NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET   ;R1 = NVIC_PENDSVSET
    STR     R1, [R0]              ;*(uint32_t *)NVIC_INT_CTRL = NVIC_PENDSVSET

    CPSIE   I                     ;開中斷                 

OSStartHang                       ;死循環,應該不會到這裏
    B       OSStartHang

#1.PendSV中斷的優先級應該爲最低優先級,原因在<<ARM Cortex-M3權威指南>>的7.6節已有說明。
#2.PSP設置爲0,是告訴具體的任務切換程序(OS_CPU_PendSVHandler()),這是第一次任務切換。做過
切換後PSP就不會爲0了,後面會看到。
#3.往中斷控制及狀態寄存器ICSR(0xE000ED04)第28位寫1即可產生PendSV中斷。這個<<ARM Cortex-M3權
威指南>>8.4.5 其它異常的配置寄存器有說明。

    當一個任務放棄cpu的使用權,就會調用OS_TASK_SW()宏,而OS_TASK_SW()就是OSCtxSw()。OSCtxSw()應該做任務切換。但是在CM3中,所有任務切換都被放到PendSV的中斷處理函數中去做了,因此OSCtxSw()只需簡單的觸發PendSV中斷即可。OS_TASK_SW()是由OS_Sched()調用。

void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr = 0;
#endif


    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0) {
        if (OSLockNesting == 0) {
            OS_SchedNew();
            if (OSPrioHighRdy != OSPrioCur) {
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
                OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
                OSCtxSwCtr++; 
                OS_TASK_SW();    /* 觸發PendSV中斷 */
            }
        }
    }
    /* 一旦開中斷,PendSV中斷函數會執行(當然要等更高優先級中斷處理完) */
    OS_EXIT_CRITICAL();  
}

OSCtxSw
    ;觸發PendSV中斷
    LDR     R0, =NVIC_INT_CTRL    ;R0 = NVIC_INT_CTRL 
    LDR     R1, =NVIC_PENDSVSET   ;R1 = NVIC_PENDSVSET
    STR     R1, [R0]              ;*(uint32_t *)NVIC_INT_CTRL = NVIC_PENDSVSET
    BX      LR                    ;返回

    當一箇中斷處理函數退出時,OSIntExit()會被調用來決定是否有優先級更高的任務需要執行。如果有OSIntExit()對調用OSIntCtxSw()做任務切換。

OSIntCtxSw
    ;觸發PendSV中斷
    LDR     R0, =NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

    看到這裏有些同學可能奇怪怎麼OSCtxSw()和OSIntCtxSw()完全一樣,事實上,這兩個函數的意義是不一樣的,OSCtxSw()做的是任務之間的切換,如任務A因爲等待某個資源或是做延時切換到任務B,而OSIntCtxSw()則是中斷退出時,由中斷狀態切換到另一個任務。由中斷切換到任務時,CPU寄存器入棧的工作已經做完了,所以無需做第二次了(參考邵老師書的3.10節)。這裏只不過由於CM3的特殊機制導致了在這兩個函數中只要做觸發PendSV中斷即可,具體切換由PendSV中斷來處理。

    前面已經說過真正的任務切換是在PendSV中斷處理函數裏做的,由於CM3在中斷時會有一半的寄存器自動保存到任務堆棧裏,所以在PendSV中斷處理函數中只需保存R4-R11並調節堆棧指針即可。

PendSV中斷處理函數僞代碼如下:
OS_CPU_PendSVHandler()
{
        if (PSP != NULL) {
                Save R4-R11 onto task stack;
                OSTCBCur->OSTCBStkPtr = SP;
        }
        OSTaskSwHook();
        OSPrioCur = OSPrioHighRdy;
        OSTCBCur = OSTCBHighRdy;
        PSP = OSTCBHighRdy->OSTCBStkPtr;
        Restore R4-R11 from new task stack;
        Return from exception; 
}

OS_CPU_PendSVHandler             ;xPSR, PC, LR, R12, R0-R3已自動保存
    CPSID   I                    ;任務切換期間需要關中斷
    
    MRS     R0, PSP              ;R0 = PSP
    ;如果PSP == 0,跳到OS_CPU_PendSVHandler_nosave執行 #1
    CBZ     R0, OS_CPU_PendSVHandler_nosave

    ;保存R4-R11到任務堆棧
    SUBS    R0, R0, #0x20        ;R0 -= 0x20                         
    STM     R0, {R4-R11}         ;保存R4-R11到任務堆棧

    ;OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, =OSTCBCur        ;R1 = &OSTCBCur
    LDR     R1, [R1]             ;R1 = *R1 (R1 = OSTCBCur)
    STR     R0, [R1]             ;*R1 = R0 (*OSTCBCur = SP) #2                      

OS_CPU_PendSVHandler_nosave
    ;調用OSTaskSwHook()
    PUSH    {R14}                ;保存R14,因爲後面要調用函數            
    LDR     R0, =OSTaskSwHook    ;R0 = &OSTaskSwHook 
    BLX     R0                   ;調用OSTaskSwHook()
    POP     {R14}                ;恢復R14

    ;OSPrioCur = OSPrioHighRdy;
    LDR     R0, =OSPrioCur       ;R0 = &OSPrioCur
    LDR     R1, =OSPrioHighRdy   ;R1 = &OSPrioHighRdy
    LDRB    R2, [R1]             ;R2 = *R1 (R2 = OSPrioHighRdy)
    STRB    R2, [R0]             ;*R0 = R2 (OSPrioCur = OSPrioHighRdy)

    ;OSTCBCur = OSTCBHighRdy;
    LDR     R0, =OSTCBCur        ;R0 = &OSTCBCur      
    LDR     R1, =OSTCBHighRdy    ;R1 = &OSTCBHighRdy
    LDR     R2, [R1]             ;R2 = *R1 (R2 = OSTCBHighRdy)
    STR     R2, [R0]             ;*R0 = R2 (OSTCBCur = OSTCBHighRdy)

    LDR     R0, [R2]             ;R0 = *R2 (R0 = OSTCBHighRdy), 此時R0是新任務的SP
                                 ;SP = OSTCBHighRdy->OSTCBStkPtr #3   
    LDM     R0, {R4-R11}         ;從任務堆棧SP恢復R4-R11       
    ADDS    R0, R0, #0x20        ;R0 += 0x20
    MSR     PSP, R0              ;PSP = R0,用新任務的SP加載PSP 
    ORR     LR, LR, #0x04        ;確保LR位2爲1,返回後使用進程堆棧 #4      
    CPSIE   I                    ;開中斷
    BX      LR                   ;中斷返回                

    END

#1 如果PSP == 0,說明OSStartHighRdy()啓動後第一次做任務切換,而任務剛創建時R4-R11已經保存在堆棧中了,所以不需要再保存一次了。
#2 OSTCBStkPtr是任務控制塊結構體的第一個變量,所以*OSTCBCur = SP(不是很科學)就是OSTCBCur->OSTCBStkPtr = SP;
#3 和#2類似。
#4 因爲在中斷處理函數中使用的是MSP,所以在返回任務後必須使用PSP,所以LR位2必須爲1。

os_dbg.c
用於系統調試,可以不管。

    需要修改的代碼就介紹到這裏,如果還有不明白之處,就再看看AN-1018.pdf,邵老師的書和<<ARM Cortex-M3權威指南>>。


轉載請註明出處:http://blog.csdn.net/lbl1234

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章