<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > 構造一個51單片機的實時操作系統(tǒng)

          構造一個51單片機的實時操作系統(tǒng)

          ——
          作者:長沙市希麥特電子科技有限公司 彭光紅 時間:2007-01-26 來源:《單片機與嵌入式系統(tǒng)應用》 收藏

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

          多年來我們一直想找一個合適的實時操作系統(tǒng),作為自己的開發(fā)基礎。根據開發(fā)需求,整合一些常用的嵌入式構件,以節(jié)約開發(fā)時間,盡最大可能地減少開發(fā)工作量;另外,要求這個實時操作系統(tǒng)能非常容易地嵌入到小容量的芯片中。畢竟,大系統(tǒng)是少數的,而小應用是多數而廣泛的。顯而易見,μc/os-ⅱ是不太適合于以上要求的,而keil c所帶的rtx tiny不帶源代碼,不具透明性,至于其full版本就更不用說了。

          1 keil c51與重入問題

          說到實時操作系統(tǒng),就不能不考慮重入問題。對于pc機這樣的大內存處理器而言,這似乎并不是一個很麻煩的問題,借用μc/os-ⅱ rtos的說法,即要求在重入的函數內,使用局部變量。但51系列單片機堆??臻g很小,僅局限在256字節(jié)之內,無法為每個函數都分配一個局部堆空間,正是由于這個原因,keil c51使用了所謂的可覆蓋技術:

          (1)局部變量存儲在全局ram空間(不考慮擴展外部存儲器的情況);
          (2)在編譯鏈接時,即已經完成局部變量的定位;
          (3)如果各函數之間沒有直接或間接的調用關系,則其局部變量空間便可覆蓋。

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

          void taska(void*ptr){
          uint8 val_a;
          //其他一些變量定義
          do{
          //實際的用戶任務處理代碼
          }while(1);
          }
          void taskb(void*ptr){
          uint8 val_b;
          //其他一些變量定義
          do{
          funcl();
          //其他實際的用戶任務處理代碼
          }while(1);
          }
          void funcl(){
          uint8 val_fa;
          //其他變量的定義
          //函數的處理代碼
          }

          在上面的代碼中,taska與taskb并不存在直接或間接的調用關系,因而其局部變量val_a與val_b便是可以被互相覆蓋的,即其可能都被定位于某一個相同的ram空間。這樣,當taska運行一段時間,改變了val_a后,taskb取得cpu控制權并運行時,便可能會改變val_b。由于其指向相同的ram空間,導致taska重新取得cpu控制權時,val_a的值已經改變,從而導致程序運行不正確,反過來亦然。另一方面,funch()與taskb有直接的調用關系,因而其局部變量val_fa與val_b不會被互相覆蓋,但也不能保證其局部變量val_fa不會與taska或其他任務的局部變量形成可覆蓋關系。

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

          void taskc(void){
          uint8 x,y;
          while(1){
          os_enter_critical();
          x=getx(); (1)
          y=gety(); (2)
          //任務的其他代碼
          os_exit_critical(); (3)
          ossleep(100); (4)
          }
          }

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

          除此之外,還可以發(fā)現任務的一個特點:當任務從(1)重新開始時,局部變量x和y是一個什么值并不在乎,即x和y即使在(3)之后改變了,也已經不再重要,不會影響程序的正確性。其實這一特點也是大部分任務,至少是大部分任務的大部分局部變量的一個共性--如果任務在整個執(zhí)行過程中,不會(被占先)放棄cpu控制權,則其局部變量大多數并不需要進行特別的保護,即其作用域只是任務的當次執(zhí)行,針對上面的代碼,就是臨界保護區(qū)內的代碼區(qū)域。
          2 實時操作系統(tǒng)要不要占先

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

          (1)在一個任務中,如果局部變量在其作用域內不被占先切換,則這些變量在任務被剝奪了cpu控制權后,不關心其值也不會影響任務的正確執(zhí)行;
          (2)使用臨界區(qū)保護,可以達到上面所提到的要求;
          (3)由此導致的實時性能與占先切換的減弱可以接受。

          由此可知,不被占先的是任務保護局部變量的關鍵。既然如此,何不舍棄占先式的任務調度?這不失為一個好的出發(fā)點。針對keil c51,非占先式任務調度,可能是一種更好的方法,更能協(xié)調51系列的單片機的既定資源,下面編寫這樣一個系統(tǒng):

          (1)使用非占先式任務調度;
          (2)可以在小容量的芯片中使用,開發(fā)目標是,即使是8051這樣小的芯片,也可使用這個實時操作系統(tǒng);
          (3)支持優(yōu)先級調度,盡可能保證其實時性。

          3 實時操作系統(tǒng)的實現

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

          本文引用地址:http://www.ex-cimer.com/article/20782.htm

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

          堆棧的初始化實際是初始化ostaskstackbotton數組,并將當前任務指定為空閑任務,下一個運行任務指定為最高優(yōu)先級任務,即優(yōu)先級為零的任務。初始化時,將sp的值存入ostaskstackbotton[0],sp+2的值存入ostaskstackbotton[1],依此類推。而任務是調用ostaskcreate函數建立的,實際上只是將任務(假設為n號任務)的地址填入到對應ostaskstackbotton[n]所指向的位置,并將sp向后移動2個字節(jié),如圖2所示。

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

          void main(void){
          osinit(); /*初始化ostaskstackbotton隊列*/
          tmod=(tmod&0xf0)|0x01;
          tl0=0xbf;
          th0=0xfc;
          tr0=1;
          et0=1;
          tf0=0;
          ostaskcreate(taska,null,0);
          ostaskcreate(taskb,null,1);
          ostaskcreate(taskc,null,2);
          osstart();
          }
          上面這段代碼中,所有任務建立后,便調用osstart()開始任務調度。osstart()是一個宏定義,如下所示:
          #define osstart() do{
          ostaskcreate(taskidle,null,os_max_tasks);
          ea=1;
          return;
          }while(0)

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

          3.2 任務的切換

          任務的切換分兩種情況,在當前任務優(yōu)先級低于下一個取得cpu的控制權的任務時,將下一個取得cpu控制權的任務的棧頂到當前任務的棧頂之間的內容向ram空間的高端搬移,以空出全部的ram空間作下一個任務的堆空間,同時更新對應的ostaskstackbotton,使其指向新的正確任務的堆棧棧底。如果當前任務的優(yōu)先級高于下一個任務的優(yōu)先級,則作相反的搬移,如圖3與圖4所示。



          所有任務必須主動調用ossleep,放棄cpu的控制權。任務調用ossleep后,將選擇優(yōu)先級最高的就緒任務運行。

          (編者注:實時操作系統(tǒng)源代碼見本刊網站www.mesnet.com.cn。)

          結語

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

          注:本文所說的重入不一定是嚴格意義上的重入,很大程度上僅指函數被打斷后再次進入時,程序能正確運行,并不是說其環(huán)境變量沒有改變。



          關鍵詞:

          評論


          相關推薦

          技術專區(qū)

          關閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();