新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > ucosii在stm32上的移植詳解3

        ucosii在stm32上的移植詳解3

        作者: 時間:2016-11-09 來源:網絡 收藏
        移植詳解1和2中主要講了移植需要用到的基礎知識,本文則對具體的移植過程進行介紹。

        首先從micrium網站上下載官方移植版本(編譯器使用ARM/Keil的,V2.86版本,V2.85有問題)。
        下載地址:http://micrium.com/page/downloads/ports/st/stm32
        解壓縮后得到如下文件夾和文件:
        Micrium
        AppNotes
        Licensing
        Software
        ReadMe.pdf

        本文引用地址:http://www.104case.com/article/201611/318070.htm

        AppNotes包含ucosii移植說明文件。這兩個文件中我們僅需關心MicriumAppNotesAN1xxx-RTOSAN1018-uCOS-II-Cortex-M3AN-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),本文的移植是基于這個工程的。
        建立文件夾templatesrcucosii, templatesrcucosiisrc, templatesrcucosiiport;
        把MicriumSoftwareuCOS-IISource下的文件拷貝至templatesrcucosiisrc;
        把MicriumSoftwareuCOS-IIPortsARM-Cortex-M3GenericRealView下的文件拷貝至

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

        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 (MicriumSoftwareEvalBoards)中,而本文移植中并未用到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節有介紹)。
        那么中斷后棧中是個什么情形呢,<>中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中斷的優先級應該為最低優先級,原因在<>的7.6節已有說明。
        #2.PSP設置為0,是告訴具體的任務切換程序(OS_CPU_PendSVHandler()),這是第一次任務切換。做過切換后PSP就不會為0了,后面會看到。
        #3.往中斷控制及狀態寄存器ICSR(0xE000ED04)第28位寫1即可產生PendSV中斷。這個<>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,邵老師的書和<>。



        關鍵詞: ucosiistm32移植詳

        評論


        技術專區

        關閉
        主站蜘蛛池模板: 湖北省| 金沙县| 凉城县| 邵武市| 山丹县| 郁南县| 新竹市| 双牌县| 施甸县| 馆陶县| 石城县| 淳安县| 三门峡市| 雅安市| 南雄市| 钦州市| 隆安县| 新邵县| 古丈县| 辽宁省| 怀来县| 湟源县| 湖北省| 久治县| 武宁县| 满城县| 安仁县| 北宁市| 丹江口市| 达拉特旗| 西畴县| 元朗区| 台安县| 新余市| 揭东县| 垫江县| 东乡族自治县| 青龙| 手游| 白河县| 大化|