1. 程式人生 > >[轉載] 構造一個51微控制器的實時作業系統

[轉載] 構造一個51微控制器的實時作業系統

作者 xgywinner 日期 2009-3-18 11:53:00

作者:長沙市希麥特電子科技有限公司 彭光紅

摘  要:從Keil C51的記憶體空間管理方式入手,著重討論實時作業系統在任務排程時的重入問題,分析一些解決重入的基本方式與方法:分析實時作業系統任務排程的佔先性,提出非佔先的任務排程是能更適合於Keil C51的一種排程方式。為此,構造這一實時作業系統,並有針對性地介紹此係統的堆管理方法、任務的建立以廈任務的切換等。
關鍵詞:51微控制器 實時作業系統 任務重八排程

    目前,大多數的產品開發是在基於一些小容量的微控制器上進行的。51系列微控制器,是我國目前使用最多的微控制器系列之一,有非常廣大的應用環境與前景,多年來的資源積累,使51系列微控制器仍是許多開發者的首選。針對這種情況,近幾年湧現出許多基於51核心的擴充套件晶片,功能越來越齊全,速度越來越快,也從一個側面說明了51系列微控制器在國內的生命力。

    多年來我們一直想找一個合適的實時作業系統,作為自己的開發基礎。根據開發需求,整合一些常用的嵌入式構件,以節約開發時間,盡最大可能地減少開發工作量;另外,要求這個實時作業系統能非常容易地嵌入到小容量的晶片中。畢竟,大系統是少數的,而小應用是多數而廣泛的。顯而易見,μC/OS—II是不太適合於以上要求的,而Keil C所帶的RTX Tiny不帶原始碼,不具透明性,至於其FULL版本就更不用說了。

1 KeiI C51與重入問題
    說到實時作業系統,就不能不考慮重入問題。對於PC機這樣的大記憶體處理器而言,這似乎並不是一個很麻煩的問題,借用μC/OS—II RTOS的說法,即要求在重入的函式內,使用區域性變數。但5l系列微控制器堆疊空間很小,僅侷限在256位元組之內,無法為每個函式都分配一個區域性堆空間。正是由於這個原因,Keil C51使用了所謂的可覆蓋技術:
    ①區域性變數儲存在全域性RAM空間(不考慮擴充套件外部儲存器的情況);
    ②在編譯連結時,即已經完成區域性變數的定位;
    ③如果各函式之間沒有直接或間接的呼叫關係,則其區域性變數空間便可覆蓋。

    正是由於以上的原因,在Keil C51環境下,純粹的函式如果不加處理(如增加一個模擬棧),是無法重人的。那麼在Keil C5l環境下,如何使其函式具有可重人性呢?下面分析在實時作業系統下面,任務的基本結構與模式:
vold TaskA(void*ptr){
UINT8 vaL_a;
//其他一些變數定義
do{
//實際的使用者任務處理程式碼
}while(1);
}
void TaskB(void*ptr){
UINT8 vaLb;
//其他一些變數定義
do{
Funcl();
//其他實際的使用者任務處理程式碼
)while(1);
void Funcl(){
UlNT8 v al_fa;
//其他變數的定義
//函式的處理程式碼
}

    在上面的程式碼中,TaskA與TaskB並不存在直接或間接的呼叫關係,因而其區域性變數val_a與val_b便是可以被互相覆蓋的,即其可能都被定位於某一個相同的RAM空間。這樣,當TaskA執行一段時間,改變了val_a後,TaskB取得CPU控制權並執行時,便可能會改變val_b。由於其指向相同的RAM空間,導致TaskA重新取得CPU控制權時,val—a的值已經改變,從而導致程式執行不正確,反過來亦然。另一方面,Funcl()與TaskB有直接的呼叫關係,因而其區域性變數val_fa與val_b不會被互相覆蓋,但也不能保證其區域性變數val_fa不會與TaskA或其他任務的區域性變數形成可覆蓋關係。

    將val_a、val_b以及val_fa等區域性變數定義為靜態變數(加上static指示符)可以解決這一問題。但問題是,定義大量的static型別變數,將導致RAM空間的大量佔用,有可能直接導致RAM空間不夠用。尤其是在一些小容量的微控制器內,一般只有128或256位元組,大量的靜態變數定義,在如此小的RAM資源狀況下顯然就不太合適了。由此而有了另一種的解決方法,如下程式碼所示:
void TaskC(void){
UINT8 x,v;
whlk(1){
OS_ENTER_CRITICAL();
x=GetX(); (1)
y=GetY(); (2)
//任務的其他程式碼
OS_EXIT_CRITICAL(); (3)
0SSleep(100); (4)
}
}

    以上程式碼TaskC中使用了臨界保護的方法來保護程式碼不被中斷佔先,確實有效地解決了RAM空間太小,不宜大量定義靜態變數的問題。然而如果每個任務都採用此種結構,任務一開始,就關閉中斷,將使實時性得不到保證。事實證明,這種延時是相當可觀的。用一個例項來說明,如果想在系統中使用一個動態重新整理的LED顯示器,就難以保證顯示的穩定與連續,哪怕在系統中是使用一個單獨的定時器來做這一工作(進入臨界區後,EA=0)。其次,這種結構事實上將佔先的任務排程轉化為非佔先的任務排程。實際上如果在(3)與(4)之間沒有碰巧發生中斷並導致一個任務排程,那就可以理解為是任務主動放棄CPU的控制。如果在(3)和(4)之間碰巧產生了一箇中斷並導致了一個任務排程,只是執行了一次多餘的任務排程而已,而且並不希望在(3)之後發生2次甚至多次的任務排程,相信讀者也有這一願望。

    除此之外,還可以發現任務的一個特點:當任務從(1)重新開始時,區域性變數x和y是一個什麼值並不在乎,即x和y即使在(3)之後改變了,也已經不再重要,不會影響程式的正確性。其實這一特點也是大部分任務,至少是太部分任務的大部分區域性變數的一個共性——如果任務在整個執行過程中,不會(被佔先)放棄CPU控制權,則其區域性變數大多數並不需要進行特別的保護,即其作用域只是任務的當次執行,針對上面的程式碼,就是臨界保護區內的程式碼區域。

2 實時作業系統要不要佔先
    由上面的分析,如果要保持一個函式可重人,就得使用靜態變數,系統的RAM資源將是一個嚴峻的考驗;如果使用臨界區來保護執行環境,系統的實時性又得不到保證,而且有將佔先式任務排程轉為非佔先任務排程之虞。顯然,使用靜態變數簡單,但有更多的不適用性,對將來功能的調整也是一個阻礙,一般不被採用。那麼,就只能從環境保護上來下功夫了,但是果真只能以進入臨界區犧牲系統的實時性來保證任務不被佔先?下面看看臨界保護這一方法的基本思路:
    ①在一個任務中,如果區域性變數在其作用域內不被佔先切換,則這些變數在任務被剝奪了CPU控制權後,不關心其值也不會影響任務的正確執行;
    ②使用臨界區保護,可以達到上面所提到的要求;
    ③由此導致的實時效能與佔先切換的減弱可以接受。由此可知,不被佔先是任務保護區域性變數的關鍵。既然如此,何不捨棄佔先式的任務排程?這不失為一個好的出發點。針對Keil C51,非佔先式任務排程,可能是一種更好的方法,更能協調51系列微控制器的既定資源。下面編寫這樣一個系統:
    ①使用非佔先式任務排程;
    ②可以在小容量的晶片中使用,開發目標是,即使是8051這樣小的晶片,也可使用這個實時作業系統;
    ③支援優先順序排程,儘可能保證其實時性。

3 實時作業系統的實現
    基於以上的分析與目的,近日完成了這個作業系統。在堆疊上借用RTx的管理方法,即當前任務使用全部的堆空間,如圖1所示。

3.1 堆疊的初始化與任務的建立
    堆疊的初始化實際是初始化0STaskStackBotton陣列,並將當前任務指定為空閒任務,下一個執行任務指定為最高優先順序任務,即優先順序為零的任務。初始化時,將SP的值存人OSTaslkStackBotton[O],SP+2的值存入OSTaskStacKBotton[1],依此類推。而任務是呼叫0STa-skCreate函式建立的。實際上只是將任務(假設為n號任務)的地址填人到對應OSTaskStackBotton[n]所指向的位置,並將SP向後移動2個位元組,如圖2所示。


    為什麼要以這樣一種規律而不是其他的方式呢?這是由於在任務建立後,還未進行任務排程之前,各任務的堆疊實際上是它們自身的地址,因而其堆疊深度為2,為了程式的簡便而直接填入。
void main(void){
OSInit(); /*初始化OSTaskStackBcBotton佇列*/
TMOD=(TMOD&0XFO)│ 0XOl;
TL0=0xBF;
TH0=0xFC;
TRO=1;
ETO=1;
TFO=O:
OSTaskCreate(TaskA,NULL,0);
OSTaskCreate(TaskB.NULL,1);
OSTaskCreate(TaskC,NULL,2);
OSStart();

    上面這段程式碼中,所有任務建立後,便呼叫OSStart()開始任務排程。OSStart()是一個巨集定義,如下所示:
#deflne OSStart() d0{\
OSTaskCreate(TaskIdle,NULL,OS_MAX_TASKS);\
EA=l:\
return;\
}while(O)

    首先,它建立了一個空閒任務並開啟中斷,然後便返回。返回到哪裡了呢?我們知道,空閒任務是優先順序最低的任務,當調OSTaskCreate建立時,會將其地址填人到SP的位置,並把SP向後移動2個位元組(見圖2及說明),因而此時處在堆疊頂端的,一定是空閒任務Taslddle。這就使得這裡的return一定會返回到空閒任務。至此,系統進入正常執行狀態。

3.2 任務的切換

    任務的切換分兩種情況,在當前任務優先順序低於下一個取得CPU控制權的任務時,將下一個取得CPU控制權的任務的棧頂到當前任務的棧頂之間的內容向RAM空間的高階搬移,以空出全部的RAM空間作下一個任務的堆空間,同時更新對應的OSTaskStackBotton,使其指向新的正確任務的堆疊棧底。如果當前任務的優先順序高於下一個任務的優先順序,則作相反的搬移,如圖3與圖4所示。

    所有任務必須主動呼叫OSSleep,放棄CPU的控制權。任務呼叫OSSleep後,將選擇優先順序最高的就緒任務執行。

結 語
    系統完成後,核心的程式碼量在400多個位元組左右,佔用1個定時器中斷及小量的記憶體空間。系統設定容量為8個任務,使用者實際可用任務為7個,能夠滿足一般需求,也達到了在小容量晶片中應用的開發要求。由於沒有采用佔先式的任務排程,除開全程相關的個別任務的一些區域性變數外,其他區域性變數已經不存在覆蓋關係,由於是任務主動放棄CPU控制權,對於個別需要保護的變數單獨進行處理也變得容易。在系統中,全程不需要反覆地開關中斷,實時效能也很好。對個別時序要求嚴格的外設(如DSl8820)除外。