劉洪利,趙 萍
(上海電力學(xué)院計算機(jī)與信息工程學(xué)院,上海 200090)
近年來,隨著科技的飛速發(fā)展,嵌入式應(yīng)用已經(jīng)滲透到生產(chǎn)和生活的各個領(lǐng)域.對于國內(nèi)中小型系統(tǒng)的設(shè)計,免費(fèi)軟件和開放代碼是最佳選擇[1].μC/OS-Ⅱ是目前源碼開放的嵌入式系統(tǒng)之一,它提供了操作系統(tǒng)最基本的功能,其核心代碼短小精悍,易于移植,受到人們的青睞.μC/OS-Ⅱ已通過聯(lián)邦航空局商用航行器認(rèn)證,成功移植到40多種CPU上,但其數(shù)據(jù)和程序存儲器的開銷很大,至少要達(dá)到8 k字節(jié).μC/OS-Ⅱ是源碼完全公開的嵌入式實時操作系統(tǒng),最多可分配64個任務(wù),其中系統(tǒng)任務(wù)占用8個,其余56個任務(wù)用戶可以自由分配.
μC/OS-Ⅱ的代碼90%都是用ANSI C寫的,可移植性好,安全性高,代碼的容量至少為8 kB.由于AT89S51單片機(jī)的ROM只有4 kB,所以要擴(kuò)展的外部程序存儲器容量要大于8 kB;每個任務(wù)都有自己的硬件棧和仿真棧,硬件棧用于保存任務(wù)運(yùn)行時系統(tǒng)棧內(nèi)的數(shù)據(jù).用戶棧中保存的仿真棧與硬件棧相向生長,中間為空閑間隔.硬件棧的保存恢復(fù)是通過拷貝實現(xiàn)的.而對于仿真堆棧的保存,μC/OS-Ⅱ只提供堆??臻g和只操作堆棧指針,不進(jìn)行內(nèi)存拷貝,因此其效率相對較高.
盡管μC/OS-Ⅱ?qū)⒉煌蝿?wù)使用不同空間看成是優(yōu)點(diǎn),但為了在51單片機(jī)上有效實現(xiàn)任務(wù)重入,建議用戶使用統(tǒng)一的固定大小的堆??臻g.用戶堆??臻g的大小是可以精確計算出來的,用戶堆??臻g=硬件堆??臻g+仿真堆??臻g.硬件棧占用內(nèi)部RAM,內(nèi)部RAM執(zhí)行效率高、速度快.如果堆??臻g過大,會影響KEIL編譯的程序性能,如果堆??臻g小,在中斷嵌套和程序調(diào)用時會造成系統(tǒng)崩潰,因此綜合考慮,可將硬件堆??臻g大小確定為64 B,用戶可以根據(jù)實際情況自行設(shè)定.仿真堆棧大小取決于形參和局部變量的類型及數(shù)量,并可以精確算出.因為所有用戶棧使用相同空間,所以取占用空間最大的任務(wù)函數(shù)的空間大小為仿真堆??臻g大小,這樣用戶堆??臻g大小就唯一確定了.將用戶堆棧空間大小用宏定義在OS_CFG.H文件中,宏名為MaxStkSize.
由于AT89S51片內(nèi)只有128個數(shù)據(jù)存儲器單元和4 k個ROM單元,因此需要根據(jù)系統(tǒng)中任務(wù)個數(shù)的多少來外擴(kuò)程序存儲器和數(shù)據(jù)存儲器[2].為了便于調(diào)試,系統(tǒng)還添加了液晶顯示模塊和按鍵輸入模塊.其中,數(shù)據(jù)RAM采用靜態(tài)數(shù)據(jù)存儲器6164(8 k×8位);程序存儲器采用EPROM27128(16 k×8位);液晶顯示器采用金鵬公司的C系列OCMJ4X8C顯示模塊,可以顯示字母、數(shù)字、漢字及圖形等,也可用于顯示鍵盤輸入值.鍵盤采用矩陣式鍵盤,可以節(jié)省單片機(jī)接口.其硬件結(jié)構(gòu)如圖1所示.
圖1 系統(tǒng)原理示意
處理器和編譯器需要滿足下列要求[3]:
(1)所用的C編譯器可產(chǎn)生可重入型代碼;
(2)用C語言就可以打開和關(guān)閉中斷;
(3)處理器能產(chǎn)生中斷,通常在10~100 Hz;
(4)處理器帶有能容納一定數(shù)量數(shù)據(jù)的硬件堆棧.
(5)處理器有將堆棧指針和其他的CPU寄存器從內(nèi)存中讀出和存到堆?;騼?nèi)存中的指令.
AT89S51單片機(jī)和KEIL C51編譯器完全可以滿足上述要求,因此可以將μC/OS-Ⅱ移植到AT89S51單片機(jī)上.
μC/OS-Ⅱ操作系統(tǒng)的軟件結(jié)構(gòu)如圖2所示[4],其中“核心代碼(處理器無關(guān))”部分從網(wǎng)上直接下載無需修改;“設(shè)置代碼(應(yīng)用相關(guān))”部分在移植時根據(jù)項目要求僅作少許修改即可;OS_CPU.H,OS_CPU_A.ASM,OS_CPU_C.C 是與處理器緊密相關(guān)的代碼,移植的主要工作就是根據(jù)處理器編寫這3個函數(shù).
圖2 μC/OS-Ⅱ的軟件結(jié)構(gòu)
OS_CPU.H主要定義與KEIL C51編譯器相關(guān)的數(shù)據(jù)類型、宏和常量.在μC/OS-Ⅱ執(zhí)行臨界段代碼前要先關(guān)中斷,執(zhí)行完后又要開中斷.在KEIL C51編譯器中,可以用C語言直接開/關(guān)中斷.用 EA=0可以關(guān)中斷,則有#define OS_ENTER_CRITICAL EA=0;用EA=1可以開中斷,則有#define OS_EXIT_CRITICAL EA=1.開/關(guān)中斷的方法有3種,在這里采用最簡單的方法,即進(jìn)入臨界段代碼前先關(guān)閉中斷,執(zhí)行完臨界段代碼后再打開中斷,則有#define OS_CRITICAL_METHOD 1.
AT89S51單片機(jī)的堆棧增長方向都是從低地址向高地址增長的,所以有#define OS_STK_GROWTH 0.在μC/OS-Ⅱ中任務(wù)間進(jìn)行切換時要求采用軟中斷實現(xiàn),但AT89S51單片機(jī)沒有軟中斷指令,且函數(shù)調(diào)用與中斷的堆棧結(jié)構(gòu)相同,因此可以用函數(shù)調(diào)用來實現(xiàn)入棧,即#define OS_TASK_SW()OSCtxSw(),用中斷返回指令RETI實現(xiàn)出棧.
在OS_CPU_C.C中主要是編寫函數(shù)OSTaskStkInit(),該函數(shù)的主要任務(wù)是初始化新建任務(wù)的私有堆棧.μC/OS-Ⅱ處于就緒態(tài)的任務(wù)堆??雌饋硐駝倓偘l(fā)生過中斷一樣,所有CPU寄存器中的數(shù)據(jù)都保存在任務(wù)的私有堆棧中.AT89S51單片機(jī)在函數(shù)調(diào)用時只將程序計數(shù)器PC的值(16位數(shù)據(jù))自動壓入系統(tǒng)棧中,PSW,ACC,B,DPL,DPH,R0,R1,R2,R3,R4,R5,R6,R7,SP則需要手動依次保存到系統(tǒng)棧.在任務(wù)切換時,為了實現(xiàn)任務(wù)的私有堆棧和系統(tǒng)棧之間的復(fù)制,任務(wù)私有堆棧的結(jié)構(gòu)和系統(tǒng)棧結(jié)構(gòu)應(yīng)該基本相同,主要區(qū)別是在任務(wù)私有堆棧中還要保存仿真棧的地址,每個任務(wù)都有自己的仿真棧,仿真棧由編譯器自動分配.任務(wù)私有堆棧不是真正的堆棧,是外部RAM,所以CPU寄存器的值都要手動保存,而系統(tǒng)棧只需手動保存除PC寄存器之外的其他寄存器.任務(wù)私有堆棧和系統(tǒng)棧的結(jié)構(gòu)如圖3所示.
圖3 任務(wù)私有堆棧和系統(tǒng)棧的結(jié)構(gòu)
OS_CPU_A.ASM包含了與處理器AT89S51緊密相關(guān)的匯編代碼,由 OSStartHighRdy(),OSCtxSw(),OSIntCtxSw(),OSTickISR()4 個函數(shù)組成.
(1)OSStartHighRdy() 用來查找就緒表中優(yōu)先級最高的任務(wù)并加以運(yùn)行.首先根據(jù)最高優(yōu)先級任務(wù)的任務(wù)控制塊指針,找到該任務(wù)的私有堆棧指針,然后找到私有堆棧的長度,并將私有堆棧中的數(shù)據(jù)復(fù)制到系統(tǒng)棧,再通過POPALL將除PC之外的寄存器值從系統(tǒng)棧彈到CPU寄存器后,用RETI指令將系統(tǒng)棧中PC的值恢復(fù)到PC寄存器[5].
(2)OSCtxSw() 主要任務(wù)是保存當(dāng)前任務(wù)的上下文,然后將就緒表中優(yōu)先級最高任務(wù)的私有堆棧中的數(shù)據(jù)恢復(fù)到CPU的各個寄存器中.先將CPU寄存器(除PC外)中的數(shù)據(jù)用PUSHALL指令保存到系統(tǒng)棧中,并從系統(tǒng)棧復(fù)制到任務(wù)的私有堆棧中,然后將新的棧頂指針保存到當(dāng)前任務(wù)的任務(wù)控制塊中,由此當(dāng)前任務(wù)的上下文就保存完畢.將就緒表中優(yōu)先級最高的任務(wù)設(shè)置為當(dāng)前任務(wù),堆棧指針指向該任務(wù)的私有堆棧,將私有堆棧中的數(shù)據(jù)復(fù)制到系統(tǒng)棧,然后用POPALL指令將系統(tǒng)棧中的數(shù)據(jù)彈至CPU寄存器中,最后用RETI指令將PC的值彈出,CPU轉(zhuǎn)去執(zhí)行就緒表中優(yōu)先級最高的任務(wù).
(3)OSIntCtxSw() 由于當(dāng)前執(zhí)行的是中斷服務(wù)程序,不需要返回,只需將就緒表中優(yōu)先級最高的任務(wù)設(shè)置為當(dāng)前任務(wù),然后恢復(fù)其寄存器即可.它與OSCtxSw()的區(qū)別在于不用保存當(dāng)前任務(wù)的上下文.
(4)OSTickISR() 主要任務(wù)是安裝時鐘,并設(shè)置時鐘節(jié)拍.在AT89S51中,采用定時器零.在執(zhí)行OSTickISR()前,先關(guān)中斷,保存CPU寄存器的值到系統(tǒng)棧,設(shè)置時鐘頻率,啟動定時器零,調(diào)用時鐘服務(wù)函數(shù),調(diào)度一次,恢復(fù)CPU寄存器的值,然后中斷返回.
原則上,與處理器無關(guān)的代碼不用修改.由于KEIL編譯器在缺省情況下編譯的代碼不可重入,所以要在每個C函數(shù)及其聲明后標(biāo)注reentrant關(guān)鍵字.另外,“pdata”和“data”在 uCOS中用作一些函數(shù)的形參,但它同時又是KEIL的關(guān)鍵字,容易導(dǎo)致編譯錯誤.文獻(xiàn)[6]將“pdata”改成“ppdata”,“data”改成“ddata’,以避免此類錯誤的發(fā)生.
在系統(tǒng)運(yùn)行前,必須設(shè)置時鐘節(jié)拍發(fā)生器定時器零的初值,定時器零工作模式設(shè)置為1,16位定時器,其初值的計算公式為:
式中:f——時鐘節(jié)拍的頻率,此處設(shè)置為50;
F0SC——晶振頻率,設(shè)置為12 MHz;
x——定時器初值,x=B1E0H.
設(shè)置好時鐘節(jié)拍發(fā)生器定時器零后,再配置μC/OS-Ⅱ,這樣代碼可以在仿真環(huán)境中通過斷點(diǎn)和跟蹤等手段進(jìn)行調(diào)試.為了更直觀地看到程序運(yùn)行結(jié)果,在此設(shè)置兩個任務(wù):Mytask和Yourtask.Mytask在屏上顯示為Mytask,優(yōu)先級為5;Yourtask在屏上顯示為Yourtask發(fā)送的消息,優(yōu)先級為6.
本文主要討論了 μC/OS-Ⅱ操作系統(tǒng)在AT89S51單片機(jī)上的應(yīng)用方法及其應(yīng)注意的問題.實踐證明,該方法切實可行,簡單易掌握,具有較強(qiáng)的實用性.但在使用過程還應(yīng)注意如下幾個問題.一是μC/OS-Ⅱ是一個基于優(yōu)先級的實時操作系統(tǒng),優(yōu)先級是任務(wù)唯一的標(biāo)識,每個任務(wù)的優(yōu)先級必須不同.為避免優(yōu)先級反轉(zhuǎn),需要使用互斥型信號量來管理共享資源.二是μC/OS-Ⅱ是多任務(wù)操作系統(tǒng),需要為每個任務(wù)分配私有堆棧(私有堆棧中包括中斷堆棧,且中斷可以嵌套達(dá)255層),但由于AT89S51單片機(jī)的內(nèi)部RAM只有128 B,且AT89S51的硬件堆棧不能放在片外,所以任務(wù)的私有堆棧只能放在外部RAM中.三是由于μC/OS-Ⅱ操作系統(tǒng)本身有大量的代碼,引入OS需要占用CPU10% ~20%的負(fù)荷能力.此外頻率決定了CPU的耗費(fèi),頻率越高耗費(fèi)越大,至一定程度時需更換更強(qiáng)的CPU.
[1]騰凌巧,劉常春,戴琨.嵌入式操作系統(tǒng)的移植與測試[J].平頂山工學(xué)院學(xué)報,2003,12(4):33-35.
[2]LABROSSE Jean.μC/OS-Ⅱ源碼公開的實時嵌入式操作系統(tǒng)[M].邵貝貝,譯.北京:中國電力出版社,2001:56-67.
[3]田志鑫,張雷,趙明揚(yáng).在51單片機(jī)上移植μC/OS-Ⅱ關(guān)鍵問題的解決[J].微計算機(jī)信息,2007,23(12):45-48.
[4]任哲,潘樹林,房紅征.嵌入式操作系統(tǒng)基礎(chǔ)UCOS-Ⅱ和Linux[M].北京:北京航空航天大學(xué)出版社,2001:65-68.
[5]姚念龍,尹航,姜久春.μC/OS-Ⅱ在MC9S12A64上的移植和應(yīng)用[J].微計算機(jī)信息,2006,22(8):12-15.
[6]孟慶峰.實時內(nèi)核μC/OS-Ⅱ在S3C44B0X上移植的研究與實現(xiàn)[J].安徽電子信息職業(yè)技術(shù)學(xué)院學(xué)報,2008,7(7):34-36.