時間:2009-12-28 16:47:26來源:yangliu
摘 要:本文以AVR mega系列單片機為平臺詳細介紹了源代碼公開的實時嵌入式操作系統μC/OS-II的內核代碼及移植方法,并對系統的相關性能進行了測試,為在8位單片機上進行嵌入式系統開發提供了參考。
關鍵詞:μC/OS-II; AVR mega; 移植; 系統測試
1 簡介
隨著技術的發展,在近幾年中,嵌入式系統的設計及應用對人們生活產生了很大的影響,并將逐漸改變人們未來的生活方式。在特定的操作系統之上開發應用程序,可以使得開發人員忽略掉很多底層硬件細節,使得應用程序調試更方便,易于維護,同時開發周期也縮短,降低了開發成本,因而嵌入式實時操作系統深得開發人員的青睞。
μC/OS-II是一種專門為微處理器設計的搶占式實時多任務操作系統,具有源代碼公開、可移植性、可裁減、穩定性和可靠性高等特點。其內核主要提供進程管理、時間管理、內存管理等服務,系統最多支持56個任務,每個任務均有自己單獨的優先級。由于其內核為搶占式,所以總是運行優先級最高的任務。系統提供了豐富的API函數,便于實現進程間的通信及進程狀態的轉化。由于μC/OS-II是為嵌入式應用編寫的通用軟件,故在具體應用時需根據不同單片機的特點進行移植,其大部分代碼是用標準C語言所寫,只有與處理器相關的一部分代碼用匯編語言寫成,因而具有很強的移植性,能在多數8位、16位、32位單片機及數字信號處理器上實現運行。
AVR mega系列單片機是基于AVR RISC的,低功耗的8位單片機,內部有32個通用寄存器。通過在一個時鐘周期內執行一條指令,運行速度可以達到1MIPS/MHz的性能。AVR單片機內核有豐富的指令集,通過32個通用寄存器直接與邏輯運算單元相連接,允許在一個時鐘周期內一條單一的指令訪問兩個獨立的寄存器。這樣的結構使得代碼的執行效率比傳統的復雜指令集的微處理器快近10倍。AVR mega128是mega系列里功能最強大、資源最豐富的一款單片機,有128k的在系統可編程flash,4k字節的SRAM和EEPROM,為系統的移植提供了一個良好的平臺。要實現μC/OS-II在AVR mega 系列MCU上的移植,需要有AVR系列單片機的編譯器,這里采用的是GNU推出的AVR-GCC編譯器。
2 移植工作概要
移植工作主要包括以下幾個內容:
● 用#define設置一個常數的值(OS_CPU.H);
● 聲明9個數據類型(OS_CPU.H);
● 用#define聲明三個宏(OS_CPU.H);
● 用C語言編寫六個函數(OS_CPU_C.C);
● 編寫四個匯編語言函數(OS_CPU_A.ASM).
要移植μC/OS-II,必須編寫OS_CPU.H、OS_CPU_A.ASM、OS_CPU_C.C三個文件。這三個文件與芯片的硬件特性有關,它們主要提供任務切換與時鐘的功能。μC/OS-II其他內核代碼均用C語言寫成,它們為系統提供任務管理、進程間通信、時間管理以及內存管理等功能。μC/OS-II軟、硬件體系結構如下圖所示:
INCLUDES.H是一個頭文件,在所有后綴為.C的文件開始都包含INCLUDES.H文件,其主要包含CFG.H、OS_CPU.H、UCOS_II.H三個文件。對于不同類型的處理器,還需要改寫INCLUDES.H文件,增加自己的頭文件,但必須加在文件末尾。OS_CFG.H主要包含的是一些二值常量,通過對這些常量置1或0,可以方便的對內核進行裁減,這是μC/OS-II較為突出的一個優點。
3 移植代碼分析
3.1 OS_CPU.H文件
OS_CUP.H包括用#define定義的與處理器有關的常量,宏和類型定義。大體結構如下:
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U; /* 無符號8位數 */
typedef signed char INT8S; /* 帶符號8位數 */
typedef unsigned int INT16U; /* 無符號16位數 */
typedef signed int INT16S; /* 帶符號16位數 */
typedef unsigned long INT32U; /* 無符號32位數 */
typedef signed long INT32S; /* 帶符號32位數 */
typedef float FP32; /* 單精度浮點數 */
typedef unsigned char OS_STK; /* 堆棧入口寬度為8位 */
#define OS_STK_GROWTH 1 /* 堆棧由高地址向低地址增長 */
#define OS_ENTER_CRITICAL() asm volatile ("cli")/* 禁止中斷 */
#define OS_EXIT_CRITICAL() asm volatile ("sei")/* 允許中斷 */
#define OS_TASK_SW() OSCtxSw() /* 任務級的切換函數 */
(1)數據類型
由于不同的處理器有不同的字長,μC/OS-II的移植需要重新定義一系列的數據結構以確保其可移植性。用戶還需通過OS_STK聲明正確的C數據類型將任務堆棧的數據類型通知μC/OS-II。在AVR mega系列中堆棧是按8位字長進行操作的,所以堆棧數據類型OS_STK被聲明為8位。所有的任務堆棧都必須用OS_STK來聲明數據類型。
?。?)代碼臨界區
μC/OS-II在進入系統臨界代碼區之前需關中斷,退出臨界區后再開中斷,則μC/OS-II能夠保護臨界區代碼免受多任務或中斷服務例程的破壞。在AVR mega系列單片機中,通過設置狀態寄存器SREG中的中斷屏蔽位來實現。μC/OS-II中的宏OS_ENTER_CRITICAL()定義將狀態寄存器中的中斷屏蔽位清除,以屏蔽所有的中斷;OS_EXIT_CRITICAL()定義將狀態寄存器的中斷屏蔽位置位以允許所有的中斷。
?。?)堆棧方向
AVR mega系列微處理器的堆棧是由高地址向低地址遞減的,所以OS_STK_GROWTH必須設置為1。
?。?) OS_TASK_SW()函數的定義
在μC/OS-II中,OS_TASK_SW()用來實現任務切換。在進行宏定義時直接定義為OSCtxSw()函數,即任務級切換函數。
3.2 OS_CPU_A.ASM文件
3.2.1 相關函數
移植μC/OS-II時需要改寫OS_CPU_A.ASM中的四個匯編語言函數:
● OSStartHighRdy()
● OSCtxSw()
● OSIntCtxSw()
● OSTickISR()
3.2.2 函數介紹
?。?) OSStartHighRdy()函數
該函數由OSStart()函數調用,功能是運行優先級最高的就緒態任務。在調用OSStart()函數之前,用戶必須調用OSInit(),并且至少已經創建了一個任務。當調用OSStart()函數時,獲得了指向就緒任務中最高優先級任務的OS_TCB的地址指針OSTCBHighRdy,并把這個指針賦值給OSTCBCur。OSStartHighRdy()通過OSTCBHighRdy,就可以從任務的控制塊中獲得堆棧指針。由于OSTaskCreate()函數創建任務時堆棧初始化為模擬一次中斷發生的結構,于是當彈出所有保存的寄存器后,運行RET命令就可以切換到了新任務。
OSStartHighRdy()函數的匯編代碼及相關介紹如下:
CALL OSTaskSwHook /* 調用用戶自己定義的函數 */
LDS R16,OSRunning /* 設置標識系統開始運行的變量 */
INC R16
STS OSRunning,R16
LDS R30,OSTCBHighRdy /* Z指針指向最高優先級任務的OS_TCB */
LDS R31,OSTCBHighRdy+1
LD R28,Z+
out SPL,R28
LD R29,Z+
out SPH,R29 /* 從OS_TCB中獲取堆棧指針 */
POPRS /* 恢復所有的寄存器 */
RET /* 任務開始執行 */
(2) OSCtxSw()函數
OSCtxSw()是一個任務級任務切換函數,其只在任務中被調用,區別于中斷程序中調用的切換函數OSIntCtxSw();在μC/OS-II中,如果任務調用某個函數,而該函數的執行結果可能會造成系統任務重新調度,例如試圖喚醒了一個優先級更高的任務,則需進行任務切換。由于AVR mega128不支持軟中斷指令,移植時于OS_CPU.H文件直接定義了OSCtxSw()函數。
OSCtxSw()函數的匯編代碼及相關介紹如下:
PUSHRS /* 保存當前環境 */
LDS R30,OSTCBCur
LDS R31,OSTCBCur+1 /* Z指針指向當前任務的OS_TCB */
in r28,SPL
ST Z+,R28
in r29,SPH
ST Z+,R29 /* 保存堆棧指針到OS_TCB中 */
CALL OSTaskSwHook /* 調用用戶自己定義的函數 */
LDS R16,OSPrioHighRdy /* OSPrioCur = OSPrioHighRdy */
STS OSPrioCur,R16
LDS R30,OSTCBHighRdy
LDS R31,OSTCBHighRdy /* z指針指向高優先級任務的OS_TCB*/
STS OSTCBCur,R30
STS OSTCBCur+1,R31 /* OSTCBCur = OSTCBHighRdy */
LD R28,Z+
out SPL,R28
LD R29,Z+
out SPH,R29 /* 獲得堆棧指針 */
POPRS /* 更新寄存器內容 */
RET /* 任務切換 */
(3) OSIntCtxSw()函數
在μC/OS-II中,由于中斷的發生可能引起任務切換,故在中斷服務程序的最后會調用OSIntExit()函數檢查任務就緒狀態。如果要進行任務切換,將調用OSIntCtxSw()函數,因而OSIntCtxSw()又稱為中斷級的任務切換函數。由于在調用OSIntCtxSw()之前已經發生了中斷,OSIntCtxSw()默認寄存器已經保存在被中斷任務的堆棧中了。OSIntCtxSw()的代碼大部分和OSCtxSw()函數相同,不同之處在于:
● 由于中斷已經發生,寄存器入棧的工作已經完成,在此不需要再保存寄存器的內容;
● 在中斷服務子程序中調用OSIntExit()時,將返回地址推入了堆棧。OSIntExit()中的進入臨界函數OS_ENTER_CRITICAL()可能會將CPU的狀態字也推入了堆棧,這取決于中斷是怎么關掉的。同時OSIntCtxSw()的返回地址也會被推入堆棧中。
當任務掛起時,棧結構應該與μC/OS-II所規定的完全一致。所以當任務掛起時,OSIntCtxSw()需要對棧指針進行調整,即調整的棧結構要保證所有掛起的任務的棧結構看起來是一樣的。調整的方法很簡單,只要將堆棧指針加一個固定的值就可以了。
(4) OSTickISR()函數
在μC/OS-II中,當調用OSStart()啟動多任務環境后,時鐘中斷非常重要。在時鐘中斷中處理所有與定時器相關的工作,如任務的延時、等待操作等。在時鐘中斷中將查詢處于等待狀態的任務,判斷是否延時結束,以重新進行任務調度。OSTickISR()采用μC/OS-II中的其他中斷服務程序原型,移植時采用了AVR mega128的8位定時器0的溢出中斷來產生時鐘節拍,溢出時間為10ms,并在OSTickISR()中重新裝載定時器的起始值。
OSTickISR()函數匯編代碼及相關介紹如下:
OSTickISR:
SEI /* 重新開中斷 */
PUSHRS /* 保存所有寄存器 */
LDS R16,OSIntNesting /* μC/OS-II中斷服務程序原型 */
INC R16
STS OSIntNesting,R16
CALL OSTimeTick /* 調用時鐘節拍處理函數 */
CALL OSIntExit /* μC/OS-II中斷服務程序原型 */
LDI R16,256-(11059200/50/1024)
OUT TCNT0,R16 /* 重載定時器,定時10ms */
POPRS /* 恢復寄存器的值 */
RET /* 中斷返回 */
3.3 OS_CPU_C.C文件
μC/OS-II的移植需要用戶在OS_CPU_C.C中定義6個函數,而實際上需要定義的只有OSStkInit()一個函數,其他5個函數需要聲明,但不一定有實際內容。這五個函數是用戶自己定義的。使用時需要將OS_CFG.H里的OS_CPU_HOOKS_EN定義為1,設置為零表示不使用這些函數。在移植代碼中并不要求使用這幾個函數,故只定義其位空函數。這5個函數分別為:
● OSTaskCreateHook()函數
● OSTaskDelHook ()函數
● OSTaskSwHook ()函數
● OSTaskStatHook ()函數
● OSTimeTickHook ()函數
OSTaskStkInit()函數由任務創建函數OSTaskCreate()或OSTaskCreateExt()調用,用來初始化任務的堆棧。初始狀態的堆棧模擬發生一次中斷后的堆棧結構。按照中斷后的進棧次序預留各個寄存器的存儲空間,而中斷返回地址指向任務代碼的起始地址。由于AVR mega128的堆棧是8位寬的,OSTaskStkInit()將創建一個指向以字節為單位的內存區域的指針,同時要求堆棧指針指向空堆棧的頂端。堆棧初始化工作結束后,OSTaskStkInit()返回新的堆棧指針,OSTaskCreate()或OSTaskCreateExt()將這個堆棧指針保存到任務的OS_TCB上。這里要注意的是,堆棧中的SREG初始化為0x80,即使得任務啟動后允許中斷的發生;如果設置為0x00,則任務啟動后將禁止中斷。如果選擇任務啟動后允許中斷發生,則所有的任務運行期間中斷都允許;同樣,如果選擇任務啟動后禁止中斷,則所有的任務都禁止中斷發生,而不能有所選擇。如果某個任務選擇啟動后禁止中斷,那么其他的任務在運行的時候需要重新開啟中斷。同時還需要修改OSTaskIdle()和OSTaskStat()函數,在運行時開啟中斷。如果以上任何環節出現問題,系統就會崩潰。所以在初始化任務堆棧時設置狀態寄存器SREG為0x80,即允許任務啟動后中斷。
4 系統測試
隨著嵌入式產品的廣泛應用,嵌入式系統的研究也在不斷發展,出現了很多的實時系統。比如實時系統VxWorks,RT-Linux等。但這些操作系統主要在32位處理器上使用,并且需要較高的成本。而由于8位單片機本身資源的限制,以前很少有在8位機上運行的操作系統,隨著8位機功能和資源的增加,漸漸出現了不少使用在8位單片機上的實時系統,比如在AVR單片機上的AVRX,NUT/OS等。這些操作系統主要用匯編語言寫成,相對來講代碼效率較高,但也有他本身的限制,即作為一種專用的操作系統而言,它們很難移植到其他平臺上和在其他場合使用。μC/OS-II作為一個用C語言編寫成的操作系統,已被廣泛的移植到各種平臺上。同時因為其用C語言所寫,性能比匯編語言編寫的操作系統稍差,故在移植工作完成后,對系統進行了測試。
一個實時進程在系統中變成可運行到進程實際開始它的執行,是有一定的延遲時間的,即分配時間。這段時間與內核密切相關。為了測試這段時間,這里只建立了一個任務,該任務在等待一個信號量,收到信號量后對一個計數值進行簡單的加1,而給任務發信號量的過程采用定時器2的比較匹配中斷完成。AVR單片機的定時器有比較匹配中斷模式,當定時器計數與預設值相等時,可以再次從0開始計數。比較匹配中斷的程序采用μC/OS-II的中斷服務程序原型,并且在中斷程序中給任務發信號量,而且定義一個變量記錄中斷發生的次數。如果在所設定的比較時間里,任務程序中的計數值和中斷程序中的計數值相等,表明任務在該時間內能滿足分配時間。通過不斷縮小比較匹配的時間發現,當時間減小到24微秒時,任務已經沒有運行了,任務中的計數值為0,表明任務沒來得及運行又被比較匹配中斷程序占用了處理器。而當大于24微秒時,運行了六千次任務調度,可以完成。對于處理速度相對高檔CPU而言已是較慢的8位單片機而言,24微秒能滿足應用的需要。
在實時系統中,實時系統的實時性表現在系統對外部事件的響應能力上。系統通過中斷來響應外部事件的發生,并且用戶的中斷服務中做的事要盡量的少,把大部分工作留給任務去做,只是通過信號量或者消息隊列機制來通知任務運行。因而任務對外部事件的響應時間就是一個重要的性能指標,這里對這段時間進行了測試。程序中建立了一個任務,等待在一個信號量上。mega128的定時器2設置為比較匹配輸出模式,在匹配時間到了之后產生一定周期的脈沖輸出并產生中斷。設置定時器1為計數模式來計數產生的脈沖輸出。通過定時器2的比較匹配中斷服務子程序來發信號量通知任務運行時,并且在中斷子程序中不開中斷,而在任務收到信號量得到運行后才開中斷,以實現中斷處理與任務運行的同步。并且任務中對一個全局變量計數器加1,記錄任務運行的次數。如果運行一段時間后在設置的比較匹配時間里任務的計數器和定時器1計數值相等,則系統在這段時間里是能完全響應外部事件。當定時器2的比較匹配時間大于39微秒時,任務運行六千次后退出任務循環并停止定時器1的計數,這兩個計數器的值相等,都為六千次。當匹配時間小于39微秒時,定時器1的計數值大于任務的計數值六千,任務的處理時間不能完全響應外部事件的發生。即系統對外部事件的響應和處理時間為39微秒,而只有外部事件的發生大于這段時間才能保證系統的實時性。而當匹配時間減小到24微秒即系統的分配時間時,任務計數器的值為零,而定時器1不斷在計數,這表明任務已得不到運行,即與前述結論相符。
7 結束語
μC/OS-II作為通用的嵌入式實時操作系統,已廣泛使用在各種場合。通過在AVR Mage 系列單片機上的移植和測試,說明了其在8位單片機上的可能性。并且由于其公開了源代碼,結合8位機的特性,可以在此基礎上進行系統的裁減和擴展,使之能達到更好的效果,本文為嵌入式系統應用基礎提供了借鑒。
參考文獻
[1] 耿德根,宋建國,馬潮,葉勇建. AVR高速嵌入式單片機原理與應用.北京:北京航空航天大學出版社,2001.
[2] Jean J.Labrosse著.邵貝貝譯. μC/OS-II – 源碼公開的實時嵌入式操作系統.北京:中國電力出版社,2001.
[3] 屠祁,屠立德. 操作系統基礎.北京:清華大學出版社,2000.
標簽:
上一篇:變頻器技術及其在離心泵上的...
中國傳動網版權與免責聲明:凡本網注明[來源:中國傳動網]的所有文字、圖片、音視和視頻文件,版權均為中國傳動網(www.hysjfh.com)獨家所有。如需轉載請與0755-82949061聯系。任何媒體、網站或個人轉載使用時須注明來源“中國傳動網”,違反者本網將追究其法律責任。
本網轉載并注明其他來源的稿件,均來自互聯網或業內投稿人士,版權屬于原版權人。轉載請保留稿件來源及作者,禁止擅自篡改,違者自負版權法律責任。
產品新聞
更多>2025-04-30
2025-04-11
2025-04-08
2025-03-31
2025-03-26
2025-03-20