今天突然有個(gè)想法,是否在其他結(jié)構(gòu)比較簡單的平臺(tái)上移植比較容易一點(diǎn),正好同學(xué)有一個(gè)凌陽的精簡板,反正今天是星期天,就當(dāng)是休息了。 首先肯定是去熟悉SPCE061A的結(jié)構(gòu)和IDE了。主要是存儲(chǔ)器結(jié)構(gòu)、指令系統(tǒng)和中斷這幾個(gè)部分。本來不是做這個(gè)的,沒有必要深究,總體看看,知道在哪些地方查就行,所以看到很快。于是擺好uCOS系統(tǒng)的資料,按照移植步驟,一個(gè)個(gè)文件、函數(shù)地寫好,其他沒有什么,就是時(shí)間節(jié)拍比較難一點(diǎn),用了不少時(shí)間寫,主要是去熟悉凌陽的中斷系統(tǒng),了解幾個(gè)寄存器的用法。按照標(biāo)準(zhǔn)移植函數(shù)步驟寫下來,代碼也就10來行。 在這里我想說的不是如何移植,而是編譯。凌陽的IDE說實(shí)話肯定是不太完善的。因?yàn)槲彝瑢W(xué)本科的時(shí)候做過,那個(gè)時(shí)候似乎聽他提到過這個(gè)問題。不過我今天算是感受到了。 寫好文件,編譯——我的錯(cuò),有一個(gè)函數(shù)寫錯(cuò)了,編譯沒有通過。然后我改了。編譯,???怎么回事,還是這個(gè)錯(cuò)誤? 大體是這樣的,我寫了一個(gè)OSTaskSw函數(shù)(原本想寫OSCtxSw的),結(jié)果,這個(gè)IDE居然還真的認(rèn)出來一個(gè)OSTaskSw,我當(dāng)時(shí)就暈了,我好像在內(nèi)核里沒有看到過這個(gè)函數(shù)嘛。我趕緊去內(nèi)核查找一下,沒有嘛。我把OSTaskSw函數(shù)改成OSCtxSw(OS_CPU.H里),再編譯,還是有。更暈了~ 這個(gè)錯(cuò)誤是這樣報(bào)的: Error L0080: The external symbol "_memset" has not a public definition. Error L0080: The external symbol "_OSTaskSw" has not a public definition. memset嘛好說,這應(yīng)該是我沒有包含某個(gè)庫文件,我只是知道這是個(gè)字符串處理函數(shù),應(yīng)該在string.h里面,但是包含了它,還是有這個(gè)錯(cuò)誤(現(xiàn)在還沒有解決,慚愧~),但是OSTaskSw都沒有了還給我報(bào)什么? 后來我想,這個(gè)IDE是GCC的,是不是因?yàn)樵隽烤幾g,鏈接的時(shí)候用了以前的文件?干脆把所有以前編譯生成的文件刪除了(用clear沒有用),再編譯,嘿嘿,還真的沒有這個(gè)錯(cuò)誤了。I 服了HIM。確實(shí)沒有用過,問題都不好找。 今天比較晚了,明天來解決另一個(gè)問題吧!我懷疑最大的可能是在工程文件的組織上有問題,應(yīng)該好好梳理一下。因?yàn)槲野l(fā)現(xiàn),編譯應(yīng)用程序文件是沒有錯(cuò)的,Build的時(shí)候才出現(xiàn)。
心頭憋得慌,一大早就跑到實(shí)驗(yàn)室來調(diào)試。弄了半天,把include文件夾中的memset搜索了一遍,就只有一個(gè)string.h里面有嘛。哪里還有其他的哩?難道我包含的地方不對(duì)?“SPCE061A.h”包含在includes.h中,能夠使用,按說所有.c文件都包含includes.h,放在這里是沒有問題的嘛。不過上天要它說不行,我也沒轍啊~ 沒辦法,上網(wǎng)查查吧。 一說是版本問題!我意識(shí)到,好像以前看過一個(gè)版本,在OSTask.c中好像是沒有用到memset函數(shù)。那下載一個(gè)老版本的來實(shí)驗(yàn)一下,總不能讓我去改內(nèi)核吧,改出來更多問題,得不償失。我下的是2.00的。 經(jīng)過一番掙扎一般的調(diào)試(很久都沒有怎么用過凌陽的IDE,很多功能不會(huì)用了,又不知道其Bug,出了問題就只好老老實(shí)實(shí)重新編譯等等,確實(shí)很累),總算調(diào)通了。 經(jīng)過記錄下來——為了系統(tǒng)化,把uCOS標(biāo)準(zhǔn)移植偽碼加上對(duì)比。
移植準(zhǔn)備: ucOS-II的移植主要涉及以下三個(gè)文件: OS_CPU.H OS_CPU_A.ASM OS_CPU_C.C 移植的工作包括以下幾個(gè)內(nèi)容: 1. 在 OS_CPU.H 文件中 聲明10個(gè)數(shù)據(jù)類型 BOOLEAN INT8U INT8S INT16U INT16S INT32U INT32S FP32 FP64 OS_STK 聲明三個(gè)宏 OS_ENTER_CRITICAL() OS_EXIT_CRITICAL() OS_TASK_SW() 設(shè)置一個(gè)常量的值 OS_STK_GROWTH 2. 編寫四個(gè)匯編語言函數(shù) (OS_CPU_A.ASM) OSStartHighRdy() OSCtxSw() OSIntCtxSw() OSTickISR() 3. 用C語言編寫六個(gè)簡單的函數(shù) (OS_CPU_C.C) OSTaskStkInit() OSTaskCreateHook() OSTaskDelHook() OSTaskSwHook() OSTaskStatHook() OSTimeTickHook() ……………… 4. 一般認(rèn)為上面幾個(gè)方面就構(gòu)成了整個(gè)移植要做的工作,其實(shí)我認(rèn)為還應(yīng)該包含對(duì)Includes.h和OS_CFG.H的配置,因?yàn)檫@些決定了操作系統(tǒng)的功能和編譯的環(huán)境。 移植工作:
1、OS_CPU.H 根據(jù)凌陽的數(shù)據(jù)結(jié)構(gòu)特點(diǎn),定義如下: typedef unsigned char BOOLEAN; typedef unsigned char INT8U; typedef signed char INT8S; typedef unsigned int INT16U; typedef signed int INT16S; typedef unsigned long INT32U; typedef signed long INT32S; typedef float FP32; typedef double FP64;
typedef unsigned int OS_STK;
#define OS_CRITICAL_METHOD 2
#if OS_CRITICAL_METHOD == 2 /*一般都使用第二種方法*/ #define OS_ENTER_CRITICAL() __asm__("INT OFF /n/t") /*關(guān)中斷*/ #define OS_EXIT_CRITICAL() __asm__("INT IRQ /n/t" "INT FIQ /n/t") /*開中斷*/ #endif
#define OS_STK_GROWTH 1 /*堆棧增長方式,凌陽是從高向低增長*/
#define OS_TASK_SW() OSCtxSw() /*任務(wù)切換函數(shù)*/
*這里需要說明的是,很多編譯器由于支持軟中斷,所以,這里通常使用的是軟中斷進(jìn)入管理模式,然后進(jìn)行任務(wù)切換(例如在ARM7中)。但是凌陽沒有軟中斷,只好通過函數(shù)調(diào)用的方式進(jìn)行,而不是讓某個(gè)中斷向量指向OSCtxSw()。
到這里,OS_CPU.H的工作大體就是這么多了。 2、OS_CPU_C.C 在這個(gè)文件中,最重要的是OSTaskStkInit()函數(shù)的編寫,其他Hook函數(shù)用來擴(kuò)展內(nèi)核功能而不去修改內(nèi)核結(jié)構(gòu)。 先來看看一般堆棧需要初始化成什么樣子: l 保存參數(shù)(這個(gè)視情況而定,因?yàn)橛行┨幚砥鞯膮?shù)是通過寄存器傳遞的;如ARM7) l 保存任務(wù)地址,用于保存當(dāng)前任務(wù)(由于凌陽的段寄存器沒用,為0,保存task+1) l 處理器狀態(tài)字(凌陽應(yīng)該是SR吧,反正我保存的是SR) l 中斷返回地址保存(這個(gè)好像不太重要,我看ARM7里面就沒有保存) l 寄存器組 按照這個(gè)步驟,編寫如下: 這里注意,版本不同,聲明的類型也有不同,在2.52中是OS_STK。 void *OSTaskStkInit (void (*task)(void *pd), void *pdata, void *ptos, INT16U opt) { OS_STK *stk;
opt = opt; stk = (OS_STK *)ptos; *stk-- = *((INT16U*)pdata); // *stk-- = *((INT16U*)task); //為0,不需要保存 *stk-- = *((INT16U*)task + 1); *stk-- = (INT16U)0x0000; //SR不能亂放東西的,因?yàn)檫@個(gè)是程序執(zhí)行用到的 *stk-- = (INT16U)0x5555; //這些就可以隨便放了為了調(diào)試方便,可以放一些有意義的數(shù)值 *stk-- = (INT16U)0x4444; *stk-- = (INT16U)0x3333; *stk-- = (INT16U)0x2222; *stk-- = (INT16U)0x1111; return ((void*)stk); }
*說明:在標(biāo)準(zhǔn)偽碼里面提到一個(gè)模擬ISR的壓棧,暫時(shí)沒有明白是什么意思。(就是說每次任務(wù)的調(diào)度都是一次中斷)
3、OS_CPU_A.ASM 首先聲明需要的外部變量和要輸出的變量: .external _OSTCBCur .external _OSTCBHighRdy .external _OSRunning .external _OSPrioCur .external _OSPrioHighRdy .external _OSIntNesting
.external _OSTaskSwHook .external _OSIntEnter .external _OSIntExit .external _OSTimeTick
.public _OSStartHighRdy .public _OSIntCtxSw .public _OSCtxSw .public _OSTickISR 大概就這些吧
1)函數(shù)_OSStartHighRdy 標(biāo)準(zhǔn)函數(shù)偽碼: void OSStartHighRdy(void) { 調(diào)用用戶定義的OSTaskSwHook(); OSRunning = TRUE; 得到將要恢復(fù)運(yùn)行的任務(wù)的堆棧指針: Stack Pointer = OSTCBHighRdy——>OSTCBStkPtr; 從新任務(wù)堆棧中恢復(fù)處理器的所有寄存器; 執(zhí)行中斷返回指令; }
按這個(gè)標(biāo)準(zhǔn)函數(shù),編寫函數(shù)如下: _OSStartHighRdy: CALL _OSTaskSwHook R1 = 1 [_OSRunning] = R1; R1 = [_OSTCBHighRdy] SP = [R1] //注意是所指向的內(nèi)容,調(diào)這個(gè)錯(cuò)誤用了不少時(shí)間 POPALL //此函數(shù)只做了任務(wù)切換工作的一半,并沒有保存當(dāng)前任務(wù)的寄存器 RETI 2)函數(shù)_OSCtxSw
標(biāo)準(zhǔn)函數(shù)偽碼:
void OSCtxSw(void) { 保存處理器寄存器; 在當(dāng)前任務(wù)的任務(wù)控制塊中保存當(dāng)前任務(wù)的堆棧指針: OSTCBCur——>OSTCBStkPtr = Stack Pointer; OSTaskSwHook(); OSTCBCur = OSTCBHighRdy; OSPrioCur = OSPrioHighRdy; 得到將要運(yùn)行的任務(wù)的堆棧指針: Stack Pointer = OSTCBHighRdy——>OSTCBStkPtr; 從新任務(wù)的任務(wù)堆棧中恢復(fù)處理器所有寄存器的值; 執(zhí)行中斷返回指令; }
按這個(gè)標(biāo)準(zhǔn)函數(shù),編寫函數(shù)如下:
_OSCtxSw: PUSHALL R1 = [_OSTCBCur] [R1] = SP CALL _OSTaskSwHook R1 = [_OSTCBHighRdy] [_OSTCBCur] = R1 R2 = [_OSPrioHighRdy] [_OSPrioCur] = R2 SP = [R1] //所指向內(nèi)容 POPALL RETI
3)函數(shù)_OSIntCtxSw
標(biāo)準(zhǔn)函數(shù)偽碼:
void OSIntCtxSw(void) { OSTaskSwHook(); OSTCBCur = OSTCBHighRdy; OSPrioCur = OSPrioHighRdy; 得到將要運(yùn)行的任務(wù)的堆棧指針: Stack Pointer = OSTCBHighRdy——>OSTCBStkPtr; 從新任務(wù)的任務(wù)堆棧中恢復(fù)處理器所有寄存器的值; 執(zhí)行中斷返回指令; }
按這個(gè)標(biāo)準(zhǔn)函數(shù),編寫函數(shù)如下:
_OSIntCtxSw: R1 = [_OSTCBCur] //在這之前要不要調(diào)整指針,還說不好,因?yàn)椴徽{(diào)整暫時(shí)也可以運(yùn)行 [R1] = SP //注意要保存當(dāng)前任務(wù)的堆棧,否則不能返回—— //當(dāng)然最好是放在OSTickISR中,這樣可以避免在后面調(diào)整堆棧指針—— CALL _OSTaskSwHook R1 = [_OSTCBHighRdy] [_OSTCBCur] = R1 R2 = [_OSPrioHighRdy] [_OSPrioCur] = R2 SP = [R1] //所指向內(nèi)容 POPALL RETI
看起來,這個(gè)函數(shù)同OSCtxSw沒有太多的不同,區(qū)別在于如果ISR保存了CPU寄存器,這里不用再保存了。 4)函數(shù)_OSTickISR 標(biāo)準(zhǔn)函數(shù)偽碼: void OSTickISR(void) { 保存處理器寄存器; 調(diào)用OSIntEnter()或者直接給OSIntNesting加1; if(OSIntNesting == 1) { OSTCBCur——>OSTCBStkPtr = Stack Pointer; //在2.51之前是沒有的 } 給產(chǎn)生中斷的設(shè)備清中斷; 重新使能允許中斷(可選); OSTimeTick(); OSIntExit(); 恢復(fù)處理器寄存器; 執(zhí)行中斷返回指令; }
按這個(gè)標(biāo)準(zhǔn)函數(shù),編寫函數(shù)如下: 需要注意到是,要在OS_CFG.H中設(shè)置節(jié)拍數(shù) text //中斷應(yīng)該映射到0地址
.public _IRQ6 _IRQ6: _OSTickISR: PUSHALL R1=C_IRQ6_TMB2 //判斷是否為IRQ_TMB2中斷 TEST R1,[P_INT_Ctrl] JNZ IRQ_TMB2 //是,進(jìn)入IRQ_TMB2;否,進(jìn)入IRQ_TMB1 IRQ_TMB1: R1=C_IRQ6_TMB1 //清中斷標(biāo)志 [P_INT_Clear]=R1 R1=0x0001 [P_Watchdog_Clear]=R1 //清看門狗 R1=[_OSIntNesting] // 中斷嵌套標(biāo)志加1 R1+=1 // 也可call _OSIntEnter [_OSIntNesting]=R1 // //最好在這里加入保存步驟,省去調(diào)整SP; //當(dāng)然如果這里加了,前面就不要再保存了 call _OSTimeTick call _OSIntExit POPALL reti IRQ_TMB2: //中斷子程序IRQ_TMB2 R1=C_IRQ6_TMB2 //清中斷標(biāo)志 [P_INT_Clear]=R1 POPALL reti
*說明:由于凌陽提供兩個(gè)時(shí)基中斷,需要判斷中斷控制器的值。具體參照手冊(cè)。 到這里,主要的移植工作就完成了。
后記: 整個(gè)過程受制于對(duì)凌陽的不熟悉,所以中間編譯的過程比較頭疼,這也反映了移植工作是建立在對(duì)目標(biāo)系統(tǒng)熟悉的基礎(chǔ)之上。 有幾個(gè)問題需要注意: 移植版本: 移植版本不同,內(nèi)核所需要的庫函數(shù)也不同;這個(gè)移植過程遇到了一個(gè)string.h的問題,加上都沒有用,我郁悶。 OSIntCtxSw(): 這是我后來在網(wǎng)上看到的,好幾個(gè)實(shí)例中都調(diào)整了SP的值,目的是舍棄多余的數(shù)據(jù)。而在我的移植中,沒有進(jìn)行這個(gè)調(diào)整,也能夠運(yùn)行??赡軈^(qū)別在于對(duì)內(nèi)存的合理使用上。 翻看uCOS_II第二版的304-307頁,我們可以找到答案: 在以前版本中,沒有在OSTickISR()中進(jìn)行中斷時(shí)的堆棧保存: if(OSIntNesting == 1) { OSTCBCur——>OSTCBStkPtr = Stack Pointer; //在2.51之前是沒有的 } 因此,在調(diào)用OSIntCtxSw()的時(shí)候,需要先做這個(gè)工作:調(diào)整堆棧指針,標(biāo)準(zhǔn)代碼中給出的是: OSIntExit(); OSIntCtxSw(); 不是太明白,這個(gè)調(diào)用不成了自己調(diào)用遞歸了么?實(shí)驗(yàn)過,不行的。 然后保存當(dāng)前任務(wù)的堆棧指針到當(dāng)前任務(wù)控制塊中。 OSTCBCur——>OSTCBStkPtr = Stack Pointer 為了避免調(diào)整指針,最好在OSTickISR中保存堆棧先—— 在凌陽中,具體操作上將SP的值加7,就是拋棄這7個(gè)數(shù)據(jù)的值。為什么要說7,據(jù)說同編譯器有關(guān),這里也沒有時(shí)間去詳細(xì)追究,網(wǎng)上也沒有相關(guān)的說明,只有自己調(diào)試的時(shí)候打開調(diào)試窗口觀察吧。 我調(diào)試了一會(huì)兒。在任務(wù)切換處設(shè)置一個(gè)斷點(diǎn)觀測。當(dāng)執(zhí)行到POPALL時(shí),SP跳到00d0處(這個(gè)地址值為空),將之前壓棧的值彈出到寄存器中。從下面可以看到,彈棧前,SP指向的是00d0——棧頂;彈棧后,堆棧中的值(包括PC)彈出到寄存器中,SP指向的是00d7,所以這個(gè)值顯然是7了。這也比較符合凌陽的寄存器結(jié)構(gòu)。
其實(shí)最好的方法是在OSTickISR中加入中斷嵌套堆棧保存的步驟,這樣就不需要去根據(jù)編譯器調(diào)整堆棧指針了,移植性更強(qiáng)。參考uCOS_II P305、P307。 就在函數(shù)中添加這樣的代碼: CMP R1,1 //R1中保存了OSIntNesting的值 JNZ NOT_SAVE_SP SAVE_SP: R1 = [_OSTCBCur] [R1] = SP NOT_SAVE_SP:
|
|