丁旖 武穎 丁柄文
摘要:嵌入式系統(tǒng)的應(yīng)用和發(fā)展是計(jì)算機(jī)技術(shù)自桌面系統(tǒng)和網(wǎng)絡(luò)技術(shù)之后,IT產(chǎn)業(yè)和IT技術(shù)又一 個(gè)新的重大進(jìn)展。而對(duì)于規(guī)模比較小的系統(tǒng),UC/OSII 則以實(shí)時(shí)行強(qiáng),內(nèi)核公開,系統(tǒng)穩(wěn)定,易于學(xué)習(xí)和開發(fā)等特點(diǎn)受到廣大技術(shù)人員和嵌入式愛好者的青睞。本文以UC/OSII為例介紹嵌入式uC/OS-II實(shí)時(shí)操作系統(tǒng)在嵌入式平臺(tái)上的移植程序及方法。
關(guān)鍵詞:嵌入式;UC/OSII;實(shí)時(shí)操作系統(tǒng);開發(fā)流程;移植μC/OS-Ⅱ
1 引言
在日常生活等各個(gè)領(lǐng)域.當(dāng)前流行的嵌入式操作系統(tǒng)有l(wèi)inux,wince, UC/OSII 等等。我國(guó)正在成為世界電子制造業(yè)的重要基地之一,對(duì)嵌入式系統(tǒng)技術(shù)及相關(guān)技術(shù)人才存在巨大需求。
2 uC/OS-II的移植程序
將μC/OS-Ⅱ移植到不同的處理器上。所謂移植,就是使一個(gè)實(shí)時(shí)內(nèi)核能在某個(gè)微處理器或微控制器上運(yùn)行。
要使μC/OS-Ⅱ正常運(yùn)行,處理器必須滿足以下要求:
1. 處理器的C編譯器能產(chǎn)生可重入代碼。
用C語言就可以打開和關(guān)閉中斷。
處理器支持中斷,并且能產(chǎn)生定時(shí)中斷(通常在10至100Hz之間)。
處理器支持能夠容納一定量數(shù)據(jù)(可能是幾千字節(jié))的硬件堆棧。
處理器有將堆棧指針和其它CPU寄存器讀出和存儲(chǔ)到堆?;騼?nèi)存中指令。
像Motorola 6805系列的處理器不能滿足上面的第4條和第5條要求,所以μC/OS-Ⅱ不能在這類處理器上運(yùn)行。
移植工作包括以下幾個(gè)內(nèi)容:
用#define設(shè)置一個(gè)常量的值(OS_CPU.H)
聲明10個(gè)數(shù)據(jù)類型(OS_CPU.H)
用#define聲明三個(gè)宏(OS_CPU.H)
用C語言編寫六個(gè)簡(jiǎn)單的函數(shù)(OS_CPU_C.C)
編寫四個(gè)匯編語言函數(shù)(OS_CPU_A.ASM)
一旦代碼移植結(jié)束,下一步工作就是測(cè)試。
3 UCOS在51單片機(jī)的移植
任務(wù)函數(shù)中帶有形參和局部變量時(shí)若使用reentrant關(guān)鍵字會(huì)引起重入,從C51.PDF 129-131頁的內(nèi)容知:為了函數(shù)重入,形參和局部變量必須保存在堆棧里,由于51硬件堆棧太小,KEIL將根據(jù)內(nèi)存模式在相應(yīng)內(nèi)存空間仿真堆棧(生長(zhǎng)方向由上向下,與硬件棧相反)。對(duì)于大模式編譯,函數(shù)返回地址保存在硬件堆棧里,形參和局部變量放在仿真堆棧中,棧指針為?C_XBP,XBPSTACK=1時(shí),起始值在startup.a51中初始化為FFFFH+1。
為了支持重入,重新設(shè)計(jì)了堆棧結(jié)構(gòu)。增加了保存仿真堆棧指針?C_XBP和堆棧內(nèi)容的數(shù)據(jù)結(jié)構(gòu)。相應(yīng)改變的文件有:OS_CPU_A.ASM、OS_CPU_C.C、OS_CPU.H、YY.C。建議使用統(tǒng)一的固定大小的堆棧空間,盡管uCOSII原作者把不同任務(wù)使用不同空間看成是優(yōu)點(diǎn),但為了在51上有效實(shí)現(xiàn)任務(wù)重入,還是不使用這個(gè)優(yōu)點(diǎn)。
用戶堆??臻g的大小是可以精確計(jì)算出來的。用戶堆??臻g=硬件堆棧空間+仿真堆??臻g。硬件棧占用內(nèi)部RAM,內(nèi)部RAM執(zhí)行效率高,如果堆??臻g過大,會(huì)影響KEIL編譯的程序性能。如果堆??臻g小,在中斷嵌套和程序調(diào)用時(shí)會(huì)造成系統(tǒng)崩潰。綜合考慮,可把硬件堆棧空間大小定成了64字節(jié),用戶根據(jù)實(shí)際情況可以自行設(shè)定。仿真堆棧大小取決于形參和局部變量的類型及數(shù)量,可以精確算出。因?yàn)樗杏脩魲J褂孟嗤臻g大小,所以取占用空間最大的任務(wù)函數(shù)的空間大小為仿真堆??臻g大小。這樣用戶堆??臻g大小就唯一確定了。可將用戶堆??臻g大小用宏定義在OS_CFG.H文件中,宏名為MaxStkSize。
51的SP只有8位,無法在64K空間中自由移動(dòng),只好采用拷貝全部硬件堆棧內(nèi)容的笨辦法。51本來就弱,這么一來缺點(diǎn)更明顯了。其實(shí),引入OS必然要付出代價(jià),一般OS要占用CPU10%-20%的負(fù)荷能力,需權(quán)衡利弊決定。切換頻率決定了CPU的耗費(fèi),頻率越高耗費(fèi)越大,大到一定程度就該換更強(qiáng)的CPU了。我選了50Hz的切換頻率,不高也不低,用戶可以根據(jù)需要自行定奪。
4μC/OS-II在嵌入式開發(fā)平臺(tái)上進(jìn)行移植的一般方法和技巧
在選定了系統(tǒng)平臺(tái)和開發(fā)工具之后,進(jìn)行μC/OS-II的移植工作,一般需要遵循以下的幾個(gè)步驟:
深入了解所采用的系統(tǒng)核心
分析所采用的C語言開發(fā)工具的特點(diǎn)
編寫移植代碼
進(jìn)行移植的測(cè)試
針對(duì)項(xiàng)目的開發(fā)平臺(tái),封裝服務(wù)函數(shù) (類似80x86版本的PC.C和PC.H)
系統(tǒng)核心
無論項(xiàng)目所采用的系統(tǒng)核心是MCU、DSP、MPU,進(jìn)行μC/OS-II的移植時(shí),所需要關(guān)注的細(xì)節(jié)都是相近的。
首先,是芯片的中斷處理機(jī)制,如何開啟、屏蔽中斷,可否保存前一次中斷狀態(tài)等。還有,芯片是否有軟中斷或是陷阱指令,又是如何觸發(fā)的。
此外,還需關(guān)注系統(tǒng)對(duì)于存儲(chǔ)器的使用機(jī)制,諸如內(nèi)存的地址空間,堆棧的增長(zhǎng)方向,有無批量壓棧的指令等。
植代碼
在深入了解了系統(tǒng)核心與開發(fā)工具的基礎(chǔ)上,真正編寫移植代碼的工作就相對(duì)比較簡(jiǎn)單了。
μC/OS-II自身的代碼絕大部分都是用ANSI C編寫的,而且代碼的層次結(jié)構(gòu)十分干凈,與平臺(tái)相關(guān)的移植代碼僅僅存在于OS_CPU_A.ASM、OS_CPU_C.C以及OS_CPU.H這三個(gè)文件當(dāng)中。
在移植的時(shí)候,結(jié)合前面兩個(gè)步驟中已經(jīng)掌握的信息,基本上按照《嵌入式實(shí)時(shí)操作系統(tǒng)μC/OS-II》一書的相關(guān)章節(jié)的指導(dǎo)來做就可以了。
但是,由于系統(tǒng)核心、開發(fā)工具的千差萬別,在實(shí)際項(xiàng)目中,一般都會(huì)有一些處理方法上的不同,需要特別注意。以C6711的移植為例:
※ 中斷的開啟和屏蔽的兩個(gè)宏定義為:
#define OS_ENTER_CRITICAL() Disable_int()
#define OS_EXIT_CRITICAL() Enable_int()
Disable_int和Enable_int是用匯編語言編寫的兩個(gè)函數(shù)。在這里使用了控制狀態(tài)寄存器(CSR)的一個(gè)特性——CSR中除了控制全局中斷的GIE位之外,還有一個(gè)PGIE位,可用于保存之前的GIE狀態(tài)。
因此在Disable_int中先將GIE的值寫入PGIE,然后再將GIE寫0,屏蔽中斷。而在Enable_int中則從PGIE讀出值,寫入GIE,從而回復(fù)到之前的中斷設(shè)置。
這樣,就可以避免使用這兩個(gè)宏而意外改變了系統(tǒng)的中斷狀態(tài)——此外,也沒有使用堆?;蚓植孔兞浚仍髡咄扑]的方法要好?!?任務(wù)的切換:
前文說過,C6711中沒有軟中斷機(jī)制,所以任務(wù)的切換需要用匯編語言自行編寫一個(gè)函數(shù)_OSCtxSw來實(shí)現(xiàn),并且 #define OS_TASK_SW() OSCtxSw() 在C6711中需要入棧保護(hù)的寄存器包括A0-A15、B0-B15、CSR、IER、IRP和AMR,這些再加上當(dāng)前的程序地址構(gòu)成一個(gè)存儲(chǔ)幀,需要入棧保存。
_OSCtxSw函數(shù)中,需要像發(fā)生了一次中斷那樣,將上述存儲(chǔ)幀入棧,然后獲取被激活任務(wù)的TCB指針,將其存儲(chǔ)幀的內(nèi)容彈出,從而完成任務(wù)切換。
需要特別注意的是,在這里OS_TASK_SW是作為函數(shù)調(diào)用的,所以如前文所述,調(diào)用時(shí)的當(dāng)前程序地址是保存在B3寄存器中的,這也就是任務(wù)重新激活時(shí)的返回地址。
※ 中斷的編寫:
如前文所述,如果用“interrupt”關(guān)鍵字聲明函數(shù),CCS在編譯時(shí),會(huì)自動(dòng)將該函數(shù)中使用到的寄存器入棧、出棧保護(hù)。但是,這會(huì)導(dǎo)致各種中斷發(fā)生時(shí),出入棧的內(nèi)容各不相同。這對(duì)于μC/OS-II是會(huì)引起嚴(yán)重錯(cuò)誤的。因?yàn)棣藽/OS-II要求中斷發(fā)生時(shí)的入棧操作使用和發(fā)生任務(wù)切換時(shí)完全一樣的存儲(chǔ)幀結(jié)構(gòu)。
因此,在移植時(shí)、基于μC/OS-II進(jìn)行開發(fā)時(shí),都不應(yīng)當(dāng)使用“interrupt”關(guān)鍵字,而應(yīng)用如下結(jié)構(gòu)編寫中斷函數(shù):
void OSTickISR (void)
{
DSP_C6x_Save(); // 服務(wù)函數(shù),入棧
OSIntEnter();
if (OSIntNesting == 1) // v2.51版本新增加
{
OSTCBCur->OSTCBStkPtr
=(OS_STK*) DSP_C6x_GetCurrentSP(); // 服務(wù)函數(shù)
} // 獲取當(dāng)前SP的值
// 允許中斷嵌套 則在此處開中斷
OSTimeTick();
OSIntExit();
DSP_C6x_Resume(); // 服務(wù)函數(shù),出棧
}
DSP_C6x_Save 和DSP_C6x_Resume是兩個(gè)服務(wù)函數(shù),分別完成中斷的出、入棧操作。它們與OS_TASK_SW函數(shù)的區(qū)別在于:中斷發(fā)生時(shí)的當(dāng)前程序地址是自 動(dòng)保存在IRP寄存器的,應(yīng)將其作為任務(wù)返回地址,而不再是B3。此外DSP_C6x_Resume是一個(gè)永遠(yuǎn)不會(huì)返回的函數(shù),在將所有內(nèi)容出棧后,它就直接跳轉(zhuǎn)回到中斷發(fā)生前的程序地址處,繼續(xù)執(zhí)行。
進(jìn)行移植的測(cè)試
在編寫完了所有的移植代碼之后,就可以編寫幾個(gè)簡(jiǎn)單的任務(wù)程序進(jìn)行測(cè)試了,大體上可以分三個(gè)步驟來進(jìn)行,相關(guān)資料比較詳盡,這里就不多作贅述 了。
封裝服務(wù)函數(shù)
最后這個(gè)步驟,往往是容易被忽視的,但對(duì)于保持項(xiàng)目代碼的簡(jiǎn)潔、易維護(hù)有很重要的意義。
μC/OS-II的原作者強(qiáng)烈建議將源代碼分路徑進(jìn)行存儲(chǔ),
5. 結(jié)論
μC/OS-II具有免費(fèi)、簡(jiǎn)單、可靠性高、實(shí)時(shí)性好等優(yōu)點(diǎn),但也有缺乏便利 開發(fā)環(huán)境等缺點(diǎn),尤其不像商用嵌入式系統(tǒng)那樣得到廣泛使用和持續(xù)的研究更新。但開放性又使得開發(fā)人員可以自行裁減和添加所需的功能,在許多應(yīng)用領(lǐng) 域發(fā)揮著獨(dú)特的作用。當(dāng)然,是否在單片機(jī)系統(tǒng)中嵌入μC/OS-II應(yīng)視所開發(fā)的項(xiàng)目而定,對(duì)于一些簡(jiǎn)單的、低成本的項(xiàng)目來說,就沒必要使用了。
參考文獻(xiàn)
[1] JEAN J.LABROSSE,邵貝貝譯.
[2] μC/OS-II ―源碼公開的實(shí)時(shí)嵌入式操作系統(tǒng). 中國(guó)電力出版社
[3] uC/OS-II-源碼公開的實(shí)時(shí)嵌人式操作系統(tǒng)【M】,邵貝貝譯.中國(guó)電力出版社.2001
[4] 忠梅等 ‘ARM 嵌入式處理器結(jié)構(gòu)與應(yīng)用基礎(chǔ).北京:北京虢譬航天大學(xué)出版社,2002