首先到ucos的官網上下載ucosii的源碼(實際上是很多已經移植好的目標板,尋找下你說需要的板子是否在其上),找到一個相似的板子的源碼。我們的設備是開發板Help2416;採用的源碼是Micrium-uCOS-II-V290,參考源碼Micrium_STM32xxx_uCOS-II。在Micrium\AppNotes\AN1xxx-RTOS\AN1018-uCOS-II-Cortex-M3\這個路徑下的AN-1018.pdf非常重要,這文檔詳細的介紹了文件夾結構,關係,以及移植的細節。下面我們來慢慢分析。
首先參考STM32工程建立Ports\arm2416\Generic\MDK路徑,此路徑主要存放移植相關的代碼。新建
- OS_CPU.H
- OS_CPU_C.C
- OS_CPU_A.ASM
- OS_DBG.C //needless
OS_CPU.h中一般聲明瞭常量,宏和基本類型定義。先按照AN1018.pdf中的步驟來一步一步製作:
先定義全局變量與外部定義符號
- #ifdef OS_CPU_GLOBALS
- #define OS_CPU_EXT
- #else
- #define OS_CPU_EXT extern
- #endif
這是一個很智能的宏,在一些必要函數前加上,宏的定義位置就取決於文件中是否定義了OS_CPU_GLOBALS.
2.定義了基本數據
- 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;
由於都是32位小端,基本上基本類型都沒有什麼可修改的,繼續往下看。(在實際的工作中,需要按照目標CPU的位寬修改基本類型,以滿足有/無符號及位寬。
1.設置臨界區模式
這裏我們用常用的模式3,具體爲什麼不用模式1, 模式2,還不是很清楚,求瞭解的大大告之:[email protected]。
這裏的實質就是將CPSR暫存於cpu_sr這個unsigned long型的臨時變量裏面(內核使用到的時候已經自動添加了定義)。
- #define OS_CRITICAL_METHOD 3
- #define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
- #define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
2.設置堆棧增長方向
1表示從高地址到低地址(實際上,除了個別8位單片機及特殊用途的芯片,大多數都是這樣的)。 3.定義上下文切換函數
- #define OS_STK_GROWTH 1
OSCtxSw()稍後我們會在os_cpu_a.s中實現。
- #define OS_TASK_SW() OSCtxSw()
4.定義函數原型
- #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);
接下來是OS_CPU_C.C這個文件的編寫
- OSInitHookBegin()
- OSInitHookEnd()
- OSTaskCreateHook()
- OSTaskDelHook()
- OSTaskIdleHook()
- OSTaskStatHook()
- OSTaskStkInit()
- OSTaskSwHook()
- OSTCBInitHook()
- OSTimeTickHook()
看似有很多需要編寫,實際上最主要的只需要一個 OSTaskStkInit(),那我們首先來編寫他
- OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
- {
- INT32U *stk;
- opt = opt; /* 'opt' is not used, prevent warning */
- stk = (INT32U *)ptos; /* Load stack pointer */
- *(--stk) = (INT32U)task; /* Entry Point .. -> PC */
- *(--stk) = (INT32U)0; /* r14 */
- *(--stk) = (INT32U)0; /* r12 */
- *(--stk) = (INT32U)0; /* r11 */
- *(--stk) = (INT32U)0; /* r10 */
- *(--stk) = (INT32U)0; /* r9 */
- *(--stk) = (INT32U)0; /* r8 */
- *(--stk) = (INT32U)0; /* r7 */
- *(--stk) = (INT32U)0; /* r6 */
- *(--stk) = (INT32U)0; /* r5 */
- *(--stk) = (INT32U)0; /* r4 */
- *(--stk) = (INT32U)0; /* r3 */
- *(--stk) = (INT32U)0; /* r2 */
- *(--stk) = (INT32U)0; /* r1 */
- *(--stk) = (INT32U)pdata; /* r0 : argument */
- *(--stk) = (INT32U)0x0; /* CPSR model tel here 0x00000013L SVC mode */
- return ((void *)stk);
- }
注意:爲了演示各種各樣有可能的錯誤,我會在代碼中將正確選項標識或註解的方式給出。
在網上搜索這個壓棧順序,說是在arm核心pdf上有,但是實際上我只在cortexM3上看到過,其他芯片上沒有具體說,但是,我們只需要瞭解這裏的壓棧順序必須和OSCtxSw()的彈棧順序一一對應就OK了。
其他都是些預留的暫時不用的鉤子函數,只需要定義一個空的執行體就行了。
文件OS_CPU_A.S
需要我們編寫的代碼:
OS_CPU_SR_Save()
OS_CPU_SR_Restore()
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
- OSStartHighRdy
- MSR CPSR_cxsf, #SVCMODE:OR:NOINT ;//switch to svcmode& disable irq&fiq
- BL OSTaskSwHook
- LDR R0, =OSRunning
- MOV R1, #1
- STRB R1, [R0]
- LDR R0, =OSTCBHighRdy
- LDR R0, [R0]
- LDR SP, [R0]
- LDMFD SP!, {R0}
- MSR SPSR_cxsf, R0
- LDMFD SP!, {R0-R12, LR, PC}^
這裏就只是將系統運行打開,然後將最高優先級任務的棧指針賦值給SP,然後彈棧。
- OSCtxSw
- STMFD SP!, {LR}
- STMFD SP!, {R0-R12, LR}
- MRS R0, CPSR
- STMFD SP!, {R0}
- LDR R0, =OSTCBCur
- LDR R0, [R0]
- STR SP, [R0]
- OSIntCtxSw
- BL OSTaskSwHook
- LDR R0, =OSTCBHighRdy
- LDR R1, =OSTCBCur
- LDR R0, [R0]
- STR R0, [R1]
- LDR R0, =OSPrioHighRdy
- LDR R1, =OSPrioCur
- LDRB R0, [R0]
- STRB R0, [R1]
- LDR R0, =OSTCBCur
- LDR R0, [R0]
- LDR SP, [R0]
- LDMFD SP!, {R0}
- MSR SPSR_cxsf, R0
- LDMFD SP!, {R0-R12, LR, PC}^
這裏我偷了一個懶,將OSCtxSw的下半部同時給OSIntCtxSw使用。
上下文切換的實質就是OSCurTCB中開始地址存放了當前將要壓棧的任務的棧地址。OSTCBHighRdy中存放的是將要運行的任務的張地址。我們要先將當前的寄存器 PC CPSR壓棧進去,就完成了對原任務的保護工作;然後調用用戶自定義鉤子函數OSTaskSwHook,完成後再把當前最高優先級的任務賦值給OSTCBCur, 將當前優先級改爲新的最高優先級任務的優先級。然後讀取出TCB結構體中的棧地址給SP,完成彈棧操作。
因爲OSIntCtxSw是在中斷處理完成後調用的,進入中斷時已經完成了壓棧保護工作,所以可以偷懶將上述代碼的後半部給它。
當然 還有一個可選的函數,可選是指你可以在這個彙編文件中實現,也可以在C中去實現,這裏給出彙編的實現,C的實現就對應很簡單了。
- OS_CPU_IRQ_ISR
- STMFD SP!, {R1-R3}
- ;//----------------------------------------------------------------------------
- ;// R1--SP (IRQ MODE)
- ;// R2--PC (TASK POINTER)
- ;// R3--SPSR (TASK CPSR)
- ;//----------------------------------------------------------------------------
- MOV R1, SP
- ADD SP, SP, #12
- SUB R2, LR, #4
- MRS R3, SPSR
- MSR CPSR_cxsf, #SVCMODE:OR:NOINT
- ;//take care! this sp is not the one before!svc mode's SP
- STMFD SP!, {R2}
- STMFD SP!, {R4-R12, LR}
- LDMFD R1!, {R4-R6}
- STMFD SP!, {R4-R6}
- STMFD SP!, {R0}
- STMFD SP!, {R3}
- ;LDR R0, =OSIntNesting
- ;LDRB R1, [R0]
- ;ADD R1, R1, #1
- ;STRB R1, [R0]
- ;CMP R1, #1
- ;BNE %F1
- LDR R4, =OSTCBCur
- LDR R5, [R4]
- LDR SP, [R5]
- BL OSIntEnter
- MSR CPSR_c, #IRQMODE:OR:NOINT
- ;/**
- ;LDR R0, =INTOFFSET
- ;LDR R0, [R0]
- ;LDR R1, IRQIsrVect
- ;MOV LR, PC
- ;LDR PC, [R1, R0, LSL #2]
- ;*/
- BL irq_process
- MSR CPSR_c, #SVCMODE:OR:NOINT
- BL OSIntExit
- LDMFD SP!, {R4}
- MSR SPSR_cxsf, R4
- LDMFD SP!, {R0-R12, LR, PC}^
其大致過程是:SVC模式下原環境保護壓棧,調用OSIntEnter, 進入中斷模式,調用irq_process, 調用OSIntExt。
上述步驟大家可以在所有ucos的移植教程或步驟中找到。
接下來的一些列步驟有點類似於一直uboot,希望大家先行閱讀相關的板子啓動介紹文檔。
除去nand與SD啓動的BL0 與BL1這些初始化步驟,我們ucosii的引導步驟實際上是在uboot已經爲我們初始化好了一些必要設備後進行的,所以相對簡單,我們可以參考uboot的start.S文件來製作我們自己的start.S
IMPORT 與EXPORT 符號
- ;******************************************************
- PRESERVE8 ;//字節對齊
- CODE32 ;//ARM代碼
- AREA RESET,CODE,READONLY ;//reset名,代碼段,只讀
- ENTRY ;//程序入口
這裏的erea 取名是根據2416.sct中的名字來的,這個文件存儲了鏈接時各個文件的存儲位置與順序等,詳細的介紹請看uboot的相關介紹,如果想要一個最純淨的,請到u-boot/arch/arm/cpu對應的路徑下找u-boot.lds文件。
在entry後緊跟
- b HANDLE_ResetInit
這個就是我們的第一個函數,也是我們的第一個初始化函數。這個函數裏面的很多操作都可以直接拷貝armv5.pdf上的例子,因爲涉及到太多的協處理器的操作。
- mov r0, #0
- mcr p15, 0, r0, c7, c7, 0 ;flush v3/v4 cache
- mcr p15, 0, r0, c8, c7, 0 ; flush v4 TLB
1.關閉Icache與Dcache
- mov r1, #0xd3 ;//svc mode
- msr cpsr_cxsf,r1
- ldr sp,=0x31000000
2.關閉IRQ FIQ, 設置工作模式SVC模式, 初始化堆棧地址。
PS:說實話,這個start.S也是我在2440中找到拿來改的,但是建議參考u-boot的順序來寫,比如這裏應該先修改運行模式,再設置cache相關的東西。
- ;set to high vector address
- ;read c1 to r5
- MRC p15,0,r5,c1,c0,0
- ;set bit 13 of c1
- orr r5, r5, #0x2000
- ;write r5 to c1
- mcr p15, 0, r5, c1, c0, 0
這幾步是將中斷向量表的入口地址改爲高地址 0xFFFF0000開始,因爲我使用的是SD卡啓動模式,低地址0被物理映射到了BL0所在的IROM中,這裏面是隻讀的,所以我們系統啓動之後,無法將中斷向量表寫入這個地址,所以將中斷向量表的地址改爲高地址以便寫入。實際上u_boot與linux都是做了類似的操作。用nand啓動方式似乎可以寫入(未進行測試,有朋友測試後求告知結果)。
接下來就是建立mmu映射表,原因同上
- bl make_mmu_table
- ;enable domain access
- ldr r5, =0x0000ffff
- mcr p15, 0, r5, c3, c0, 0 ;load domain access register
- ldr r1,=0x37ff4000
- mcr p15, 0, r1, c2, c0, 0
這幾句實際上是打開MMU寄存器的訪問許可權,同時將內存映射表的開始地址賦給MMU的寄存器。
- mrc p15, 0, r0, c1, c0, 0
- orr r0, r0, #1 ;Set CR_M to enable MMU
- mcr p15, 0, r0, c1, c0, 0
- nop
- nop
- nop
- nop
使能MMU
接下來針對每個模式初始化設置他們的棧地址
- mov r1, #0x11 ;//FIQ mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3ff4000
- mov r1, #0x12 ;//IRQ mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3ff0000
- mov r1, #0x1f ;//sys mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fec000
- mov r1, #0x17 ;//abt mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fe8000
- mov r1, #0x1b ;//undef mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fe0000
- mov r1, #0xd3 ;//svc mode
- msr cpsr_cxsf,r1
- ldr sp,=0xc3fe4000
最後將bss段清零然後跳轉到主函數
- BL InitRORWZI
- LDR PC,=main
在app.c這個文件中添加一個
Int main(void) 函數
{
Timer_init();
Uart_init();
OSinit();
App();
OSstart();
}
接下來就是編譯調試錯誤, 像缺少頭文件這種錯誤,請自行修改頭文件名稱。
有2點直接說,工程文件中不能加入ucos_ii.c這個文件;os_cfg_r.h與os_dbg_r.c
要修改爲os_cfg.h, os_dbg.c
編譯
錯誤
.\output\obj\ucos2416prob.axf: Warning: L6305W: Image does not have an entry point. (Not specified or not set due to multiple choices.)
引起原因 app.c文件中出現了main被當作默認C入口,而start.S中又指定了ENTRY
將start.S中的mian都改爲Main或者其他什麼名字。
還有一個錯誤是我自己引起的,將軟定時器當作了系統定時器。