《電子技術(shù)應(yīng)用》
您所在的位置:首頁 > 嵌入式技術(shù) > 設(shè)計(jì)應(yīng)用 > 教你構(gòu)造一個(gè)51單片機(jī)的實(shí)時(shí)操作系統(tǒng)
教你構(gòu)造一個(gè)51單片機(jī)的實(shí)時(shí)操作系統(tǒng)
摘要: 目前,,大多數(shù)的產(chǎn)品開發(fā)是在基于一些小容量的單片機(jī)上進(jìn)行的,。51系列單片機(jī),,是我國目前使用最多的單片機(jī)系列之一,,有非常廣大的應(yīng)用環(huán)境與前景,,多年來的資源積累,,使51系列單片機(jī)仍是許多開發(fā)者的首選,。
Abstract:
Key words :

  目前,,大多數(shù)的產(chǎn)品開發(fā)是在基于一些小容量的單片機(jī)上進(jìn)行的。51系列單片機(jī),,是我國目前使用最多的單片機(jī)系列之一,,有非常廣大的應(yīng)用環(huán)境與前景,多年來的資源積累,,使51系列單片機(jī)仍是許多開發(fā)者的首選,。針對這種情況,近幾年涌現(xiàn)出許多基于51內(nèi)核的擴(kuò)展芯片,,功能越來越齊全,,速度越來越快,也從一個(gè)側(cè)面說明了51系列單片機(jī)在國內(nèi)的生命力,。

  多年來我們一直想找一個(gè)合適的實(shí)時(shí)操作系統(tǒng),,作為自己的開發(fā)基礎(chǔ)。根據(jù)開發(fā)需求,,整合一些常用的嵌入式構(gòu)件,,以節(jié)約開發(fā)時(shí)間,盡最大可能地減少開發(fā)工作量;另外,,要求這個(gè)實(shí)時(shí)操作系統(tǒng)能非常容易地嵌入到小容量的芯片中,。畢竟,大系統(tǒng)是少數(shù)的,,而小應(yīng)用是多數(shù)而廣泛的。顯而易見,,μC/OS—II是不太適合于以上要求的,,而Keil C所帶的RTX Tiny不帶源代碼,不具透明性,,至于其FULL版本就更不用說了,。

  1 Keil C51與重入問題

  說到實(shí)時(shí)操作系統(tǒng),就不能不考慮重入問題,。對于PC機(jī)這樣的大內(nèi)存處理器而言,,這似乎并不是一個(gè)很麻煩的問題,借用μC/OS—II RTOS的說法,,即要求在重入的函數(shù)內(nèi),,使用局部變量。但5l系列單片機(jī)堆??臻g很小,,僅局限在256字節(jié)之內(nèi),無法為每個(gè)函數(shù)都分配一個(gè)局部堆空間,。正是由于這個(gè)原因,,Keil C51使用了所謂的可覆蓋技術(shù):

 ?、倬植孔兞看鎯?chǔ)在全局RAM空間(不考慮擴(kuò)展外部存儲(chǔ)器的情況);

  ②在編譯鏈接時(shí),,即已經(jīng)完成局部變量的定位;

 ?、廴绻骱瘮?shù)之間沒有直接或間接的調(diào)用關(guān)系,則其局部變量空間便可覆蓋,。

  正是由于以上的原因,,在Keil C51環(huán)境下,純粹的函數(shù)如果不加處理(如增加一個(gè)模擬棧),,是無法重人的,。那么在Keil C5l環(huán)境下,如何使其函數(shù)具有可重人性呢,?下面分析在實(shí)時(shí)操作系統(tǒng)下面,,任務(wù)的基本結(jié)構(gòu)與模式:

  vold TaskA(void*ptr){

  UINT8 vaL_a;

  //其他一些變量定義

  do{

 ?。瘜?shí)際的用戶任務(wù)處理代碼

  }while(1);

  }

  void TaskB(void*ptr){

  UINT8 vaLb;

 ?。渌恍┳兞慷x

  do{

  Funcl();

  //其他實(shí)際的用戶任務(wù)處理代碼

 ?。﹚hile(1);

  void Funcl(){

  UlNT8 v al_fa;

 ?。渌兞康亩x

  //函數(shù)的處理代碼

  }

  在上面的代碼中,,TaskA與TaskB并不存在直接或間接的調(diào)用關(guān)系,,因而其局部變量val_a與val_b便是可以被互相覆蓋的,即其可能都被定位于某一個(gè)相同的RAM空間,。這樣,,當(dāng)TaskA運(yùn)行一段時(shí)間,改變了val_a后,,TaskB取得CPU控制權(quán)并運(yùn)行時(shí),,便可能會(huì)改變val_b。由于其指向相同的RAM空間,,導(dǎo)致TaskA重新取得CPU控制權(quán)時(shí),,val—a的值已經(jīng)改變,從而導(dǎo)致程序運(yùn)行不正確,,反過來亦然,。另一方面,F(xiàn)uncl()與TaskB有直接的調(diào)用關(guān)系,,因而其局部變量val_fa與val_b不會(huì)被互相覆蓋,,但也不能保證其局部變量val_fa不會(huì)與TaskA或其他任務(wù)的局部變量形成可覆蓋關(guān)系。

  將val_a,、val_b以及val_fa等局部變量定義為靜態(tài)變量(加上static指示符)可以解決這一問題,。但問題是,,定義大量的static類型變量,將導(dǎo)致RAM空間的大量占用,,有可能直接導(dǎo)致RAM空間不夠用,。尤其是在一些小容量的單片機(jī)內(nèi),一般只有128或256字節(jié),,大量的靜態(tài)變量定義,,在如此小的RAM資源狀況下顯然就不太合適了。由此而有了另一種的解決方法,,如下代碼所示:

  void TaskC(void){

  UINT8 x,,v;

  whlk(1){

  OS_ENTER_CRITICAL();

  x=GetX(); (1)

  y=GetY(); (2)

  //任務(wù)的其他代碼

  OS_EXIT_CRITICAL(); (3)

  0SSleep(100); (4)

  }

  }

  以上代碼TaskC中使用了臨界保護(hù)的方法來保護(hù)代碼不被中斷占先,,確實(shí)有效地解決了RAM空間太小,,不宜大量定義靜態(tài)變量的問題。然而如果每個(gè)任務(wù)都采用此種結(jié)構(gòu),,任務(wù)一開始,,就關(guān)閉中斷,將使實(shí)時(shí)性得不到保證,。事實(shí)證明,,這種延時(shí)是相當(dāng)可觀的。用一個(gè)實(shí)例來說明,,如果想在系統(tǒng)中使用一個(gè)動(dòng)態(tài)刷新的LED顯示器,,就難以保證顯示的穩(wěn)定與連續(xù),哪怕在系統(tǒng)中是使用一個(gè)單獨(dú)的定時(shí)器來做這一工作(進(jìn)入臨界區(qū)后,,EA=0),。其次,這種結(jié)構(gòu)事實(shí)上將占先的任務(wù)調(diào)度轉(zhuǎn)化為非占先的任務(wù)調(diào)度,。實(shí)際上如果在(3)與(4)之間沒有碰巧發(fā)生中斷并導(dǎo)致一個(gè)任務(wù)調(diào)度,那就可以理解為是任務(wù)主動(dòng)放棄CPU的控制,。如果在(3)和(4)之間碰巧產(chǎn)生了一個(gè)中斷并導(dǎo)致了一個(gè)任務(wù)調(diào)度,,只是執(zhí)行了一次多余的任務(wù)調(diào)度而已,而且并不希望在(3)之后發(fā)生2次甚至多次的任務(wù)調(diào)度,,相信讀者也有這一愿望,。

  除此之外,還可以發(fā)現(xiàn)任務(wù)的一個(gè)特點(diǎn):當(dāng)任務(wù)從(1)重新開始時(shí),,局部變量x和y是一個(gè)什么值并不在乎,,即x和y即使在(3)之后改變了,也已經(jīng)不再重要,,不會(huì)影響程序的正確性,。其實(shí)這一特點(diǎn)也是大部分任務(wù),,至少是太部分任務(wù)的大部分局部變量的一個(gè)共性——如果任務(wù)在整個(gè)執(zhí)行過程中,不會(huì)(被占先)放棄CPU控制權(quán),,則其局部變量大多數(shù)并不需要進(jìn)行特別的保護(hù),,即其作用域只是任務(wù)的當(dāng)次執(zhí)行,針對上面的代碼,,就是臨界保護(hù)區(qū)內(nèi)的代碼區(qū)域,。

 

  目前,大多數(shù)的產(chǎn)品開發(fā)是在基于一些小容量的單片機(jī)上進(jìn)行的,。51系列單片機(jī),,是我國目前使用最多的單片機(jī)系列之一,有非常廣大的應(yīng)用環(huán)境與前景,,多年來的資源積累,,使51系列單片機(jī)仍是許多開發(fā)者的首選。針對這種情況,,近幾年涌現(xiàn)出許多基于51內(nèi)核的擴(kuò)展芯片,,功能越來越齊全,速度越來越快,,也從一個(gè)側(cè)面說明了51系列單片機(jī)在國內(nèi)的生命力,。

  多年來我們一直想找一個(gè)合適的實(shí)時(shí)操作系統(tǒng),作為自己的開發(fā)基礎(chǔ),。根據(jù)開發(fā)需求,,整合一些常用的嵌入式構(gòu)件,以節(jié)約開發(fā)時(shí)間,,盡最大可能地減少開發(fā)工作量;另外,,要求這個(gè)實(shí)時(shí)操作系統(tǒng)能非常容易地嵌入到小容量的芯片中。畢竟,,大系統(tǒng)是少數(shù)的,,而小應(yīng)用是多數(shù)而廣泛的。顯而易見,,μC/OS—II是不太適合于以上要求的,,而Keil C所帶的RTX Tiny不帶源代碼,不具透明性,,至于其FULL版本就更不用說了,。

 

  1 Keil C51與重入問題

 

  說到實(shí)時(shí)操作系統(tǒng),就不能不考慮重入問題,。對于PC機(jī)這樣的大內(nèi)存處理器而言,,這似乎并不是一個(gè)很麻煩的問題,借用μC/OS—II RTOS的說法,即要求在重入的函數(shù)內(nèi),,使用局部變量,。但5l系列單片機(jī)堆棧空間很小,,僅局限在256字節(jié)之內(nèi),,無法為每個(gè)函數(shù)都分配一個(gè)局部堆空間。正是由于這個(gè)原因,,Keil C51使用了所謂的可覆蓋技術(shù):

 ?、倬植孔兞看鎯?chǔ)在全局RAM空間(不考慮擴(kuò)展外部存儲(chǔ)器的情況);

  ②在編譯鏈接時(shí),,即已經(jīng)完成局部變量的定位;

 ?、廴绻骱瘮?shù)之間沒有直接或間接的調(diào)用關(guān)系,則其局部變量空間便可覆蓋,。

  正是由于以上的原因,,在Keil C51環(huán)境下,純粹的函數(shù)如果不加處理(如增加一個(gè)模擬棧),,是無法重人的,。那么在Keil C5l環(huán)境下,如何使其函數(shù)具有可重人性呢,?下面分析在實(shí)時(shí)操作系統(tǒng)下面,,任務(wù)的基本結(jié)構(gòu)與模式:

  vold TaskA(void*ptr){

  UINT8 vaL_a;

  //其他一些變量定義

  do{

 ?。瘜?shí)際的用戶任務(wù)處理代碼

  }while(1);

  }

  void TaskB(void*ptr){

  UINT8 vaLb;

 ?。渌恍┳兞慷x

  do{

  Funcl();

  //其他實(shí)際的用戶任務(wù)處理代碼

 ?。﹚hile(1);

  void Funcl(){

  UlNT8 v al_fa;

 ?。渌兞康亩x

  //函數(shù)的處理代碼

  }

  在上面的代碼中,,TaskA與TaskB并不存在直接或間接的調(diào)用關(guān)系,,因而其局部變量val_a與val_b便是可以被互相覆蓋的,即其可能都被定位于某一個(gè)相同的RAM空間,。這樣,,當(dāng)TaskA運(yùn)行一段時(shí)間,改變了val_a后,,TaskB取得CPU控制權(quán)并運(yùn)行時(shí),便可能會(huì)改變val_b,。由于其指向相同的RAM空間,,導(dǎo)致TaskA重新取得CPU控制權(quán)時(shí),val—a的值已經(jīng)改變,從而導(dǎo)致程序運(yùn)行不正確,,反過來亦然,。另一方面,F(xiàn)uncl()與TaskB有直接的調(diào)用關(guān)系,,因而其局部變量val_fa與val_b不會(huì)被互相覆蓋,,但也不能保證其局部變量val_fa不會(huì)與TaskA或其他任務(wù)的局部變量形成可覆蓋關(guān)系。

  將val_a,、val_b以及val_fa等局部變量定義為靜態(tài)變量(加上static指示符)可以解決這一問題,。但問題是,定義大量的static類型變量,,將導(dǎo)致RAM空間的大量占用,,有可能直接導(dǎo)致RAM空間不夠用。尤其是在一些小容量的單片機(jī)內(nèi),,一般只有128或256字節(jié),,大量的靜態(tài)變量定義,在如此小的RAM資源狀況下顯然就不太合適了,。由此而有了另一種的解決方法,,如下代碼所示:

  void TaskC(void){

  UINT8 x,v;

  whlk(1){

  OS_ENTER_CRITICAL();

  x=GetX(); (1)

  y=GetY(); (2)

 ?。蝿?wù)的其他代碼

  OS_EXIT_CRITICAL(); (3)

  0SSleep(100); (4)

  }

  }

  以上代碼TaskC中使用了臨界保護(hù)的方法來保護(hù)代碼不被中斷占先,,確實(shí)有效地解決了RAM空間太小,不宜大量定義靜態(tài)變量的問題,。然而如果每個(gè)任務(wù)都采用此種結(jié)構(gòu),,任務(wù)一開始,就關(guān)閉中斷,,將使實(shí)時(shí)性得不到保證,。事實(shí)證明,這種延時(shí)是相當(dāng)可觀的,。用一個(gè)實(shí)例來說明,,如果想在系統(tǒng)中使用一個(gè)動(dòng)態(tài)刷新的LED顯示器,就難以保證顯示的穩(wěn)定與連續(xù),,哪怕在系統(tǒng)中是使用一個(gè)單獨(dú)的定時(shí)器來做這一工作(進(jìn)入臨界區(qū)后,,EA=0)。其次,,這種結(jié)構(gòu)事實(shí)上將占先的任務(wù)調(diào)度轉(zhuǎn)化為非占先的任務(wù)調(diào)度,。實(shí)際上如果在(3)與(4)之間沒有碰巧發(fā)生中斷并導(dǎo)致一個(gè)任務(wù)調(diào)度,那就可以理解為是任務(wù)主動(dòng)放棄CPU的控制,。如果在(3)和(4)之間碰巧產(chǎn)生了一個(gè)中斷并導(dǎo)致了一個(gè)任務(wù)調(diào)度,,只是執(zhí)行了一次多余的任務(wù)調(diào)度而已,而且并不希望在(3)之后發(fā)生2次甚至多次的任務(wù)調(diào)度,相信讀者也有這一愿望,。

  除此之外,,還可以發(fā)現(xiàn)任務(wù)的一個(gè)特點(diǎn):當(dāng)任務(wù)從(1)重新開始時(shí),局部變量x和y是一個(gè)什么值并不在乎,,即x和y即使在(3)之后改變了,,也已經(jīng)不再重要,不會(huì)影響程序的正確性,。其實(shí)這一特點(diǎn)也是大部分任務(wù),,至少是太部分任務(wù)的大部分局部變量的一個(gè)共性——如果任務(wù)在整個(gè)執(zhí)行過程中,不會(huì)(被占先)放棄CPU控制權(quán),,則其局部變量大多數(shù)并不需要進(jìn)行特別的保護(hù),,即其作用域只是任務(wù)的當(dāng)次執(zhí)行,針對上面的代碼,,就是臨界保護(hù)區(qū)內(nèi)的代碼區(qū)域,。

 

  2 實(shí)時(shí)操作系統(tǒng)要不要占先

 

  由上面的分析,如果要保持一個(gè)函數(shù)可重人,,就得使用靜態(tài)變量,,系統(tǒng)的RAM資源將是一個(gè)嚴(yán)峻的考驗(yàn);如果使用臨界區(qū)來保護(hù)運(yùn)行環(huán)境,系統(tǒng)的實(shí)時(shí)性又得不到保證,,而且有將占先式任務(wù)調(diào)度轉(zhuǎn)為非占先任務(wù)調(diào)度之虞,。顯然,使用靜態(tài)變量簡單,,但有更多的不適用性,,對將來功能的調(diào)整也是一個(gè)阻礙,一般不被采用,。那么,,就只能從環(huán)境保護(hù)上來下功夫了,但是果真只能以進(jìn)入臨界區(qū)犧牲系統(tǒng)的實(shí)時(shí)性來保證任務(wù)不被占先,?下面看看臨界保護(hù)這一方法的基本思路:

 ?、僭谝粋€(gè)任務(wù)中,如果局部變量在其作用域內(nèi)不被占先切換,,則這些變量在任務(wù)被剝奪了CPU控制權(quán)后,,不關(guān)心其值也不會(huì)影響任務(wù)的正確執(zhí)行;

  ②使用臨界區(qū)保護(hù),,可以達(dá)到上面所提到的要求;

 ?、塾纱藢?dǎo)致的實(shí)時(shí)性能與占先切換的減弱可以接受。由此可知,,不被占先是任務(wù)保護(hù)局部變量的關(guān)鍵,。既然如此,,何不舍棄占先式的任務(wù)調(diào)度?這不失為一個(gè)好的出發(fā)點(diǎn),。針對Keil C51,非占先式任務(wù)調(diào)度,,可能是一種更好的方法,,更能協(xié)調(diào)51系列單片機(jī)的既定資源。下面編寫這樣一個(gè)系統(tǒng):

 ?、偈褂梅钦枷仁饺蝿?wù)調(diào)度;

 ?、诳梢栽谛∪萘康男酒惺褂茫_發(fā)目標(biāo)是,,即使是8051這樣小的芯片,,也可使用這個(gè)實(shí)時(shí)操作系統(tǒng);

  ③支持優(yōu)先級調(diào)度,,盡可能保證其實(shí)時(shí)性,。

 

  3 實(shí)時(shí)操作系統(tǒng)的實(shí)現(xiàn)

 

  基于以上的分析與目的,近日完成了這個(gè)操作系統(tǒng),。在堆棧上借用RTx的管理方法,,即當(dāng)前任務(wù)使用全部的堆空間,如圖1所示,。

 

  

 

  3.1 堆棧的初始化與任務(wù)的創(chuàng)建

  堆棧的初始化實(shí)際是初始化0STaskStackBotton數(shù)組,,并將當(dāng)前任務(wù)指定為空閑任務(wù),下一個(gè)運(yùn)行任務(wù)指定為最高優(yōu)先級任務(wù),,即優(yōu)先級為零的任務(wù),。初始化時(shí),將SP的值存人OSTaslkStackBotton[O],,SP+2的值存入OSTaskStacKBotton[1],,依此類推。而任務(wù)是調(diào)用0STa-skCreate函數(shù)建立的,。實(shí)際上只是將任務(wù)(假設(shè)為n號任務(wù))的地址填人到對應(yīng)OSTaskStackBotton[n]所指向的位置,,并將SP向后移動(dòng)2個(gè)字節(jié),如圖2所示,。

 

  

 

  為什么要以這樣一種規(guī)律而不是其他的方式呢,?這是由于在任務(wù)建立后,還未進(jìn)行任務(wù)調(diào)度之前,,各任務(wù)的堆棧實(shí)際上是它們自身的地址,,因而其堆棧深度為2,為了程序的簡便而直接填入,。

  void main(void){

  OSInit(); /*初始化OSTaskStackBcBotton隊(duì)列*/

  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();

  上面這段代碼中,所有任務(wù)建立后,,便調(diào)用OSStart()開始任務(wù)調(diào)度,。OSStart()是一個(gè)宏定義,如下所示:

  #deflne OSStart() d0{\

  OSTaskCreate(TaskIdle,,NULL,,OS_MAX_TASKS);\

  EA=l:\

  return;\

  }while(O)

  首先,它創(chuàng)建了一個(gè)空閑任務(wù)并打開中斷,,然后便返回,。返回到哪里了呢?我們知道,,空閑任務(wù)是優(yōu)先級最低的任務(wù),,當(dāng)調(diào)OSTaskCreate建立時(shí),會(huì)將其地址填人到SP的位置,,并把SP向后移動(dòng)2個(gè)字節(jié)(見圖2及說明),,因而此時(shí)處在堆棧頂端的,一定是空閑任務(wù)Taslddle,。這就使得這里的return一定會(huì)返回到空閑任務(wù),。至此,系統(tǒng)進(jìn)入正常運(yùn)行狀態(tài),。

  3.2 任務(wù)的切換

  任務(wù)的切換分兩種情況,,在當(dāng)前任務(wù)優(yōu)先級低于下一個(gè)取得CPU控制權(quán)的任務(wù)時(shí),將下一個(gè)取得CPU控制權(quán)的任務(wù)的棧頂?shù)疆?dāng)前任務(wù)的棧頂之間的內(nèi)容向RAM空間的高端搬移,,以空出全部的RAM空間作下一個(gè)任務(wù)的堆空間,,同時(shí)更新對應(yīng)的OSTaskStackBotton,使其指向新的正確任務(wù)的堆棧棧底,。如果當(dāng)前任務(wù)的優(yōu)先級高于下一個(gè)任務(wù)的優(yōu)先級,,則作相反的搬移,如圖3與圖4所示,。

 

  

 

  所有任務(wù)必須主動(dòng)調(diào)用OSSleep,,放棄CPU的控制權(quán)。任務(wù)調(diào)用OSSleep后,,將選擇優(yōu)先級最高的就緒任務(wù)運(yùn)行,。

 

  結(jié) 語

 

  系統(tǒng)完成后,內(nèi)核的代碼量在400多個(gè)字節(jié)左右,,占用1個(gè)定時(shí)器中斷及小量的內(nèi)存空間,。系統(tǒng)設(shè)置容量為8個(gè)任務(wù),,用戶實(shí)際可用任務(wù)為7個(gè),能夠滿足一般需求,,也達(dá)到了在小容量芯片中應(yīng)用的開發(fā)要求,。由于沒有采用占先式的任務(wù)調(diào)度,除開全程相關(guān)的個(gè)別任務(wù)的一些局部變量外,,其他局部變量已經(jīng)不存在覆蓋關(guān)系,,由于是任務(wù)主動(dòng)放棄CPU控制權(quán),對于個(gè)別需要保護(hù)的變量單獨(dú)進(jìn)行處理也變得容易,。在系統(tǒng)中,全程不需要反復(fù)地開關(guān)中斷,,實(shí)時(shí)性能也很好,。對個(gè)別時(shí)序要求嚴(yán)格的外設(shè)(如DSl8820)除外。

此內(nèi)容為AET網(wǎng)站原創(chuàng),,未經(jīng)授權(quán)禁止轉(zhuǎn)載,。