本文主要是關于ucos-II的相關介紹,并著重對ucos-II移植到51單片機進行了詳盡的闡述。
ucos-II
μC/OS-II由Micrium公司提供,是一個可移植、可固化的、可裁剪的、占先式多任務實時內核,它適用于多種微處理器,微控制器和數字處理芯片(已經移植到超過100種以上的微處理器應用中)。同時,該系統源代碼開放、整潔、一致,注釋詳盡,適合系統開發。 μC/OS-II已經通過聯邦航空局(FAA)商用航行器認證,符合航空無線電技術委員會(RTCA)DO-178B標準。
μC/OS-II被廣泛應用于微處理器、微控制器和數字信號處理器。
μC/OS-II 的前身是μC/OS,最早出自于1992 年美國嵌入式系統專家Jean J.Labrosse 在《嵌入式系統編程》雜志的5 月和6 月刊上刊登的文章連載,并把μC/OS 的源碼發布在該雜志的B B S 上。
μC/OS 和μC/OS-II 是專門為計算機的嵌入式應用設計的, 絕大部分代碼是用C語言編寫的。CPU 硬件相關部分是用匯編語言編寫的、總量約200行的匯編語言部分被壓縮到最低限度,為的是便于移植到任何一種其它的CPU 上。用戶只要有標準的ANSI 的C交叉編譯器,有匯編器、連接器等軟件工具,就可以將μC/OS-II嵌入到開發的產品中。μC/OS-II 具有執行效率高、占用空間小、實時性能優良和可擴展性強等特點, 最小內核可編譯至 2KB 。μC/OS-II 已經移植到了幾乎所有知名的CPU 上。
嚴格地說uC/OS-II只是一個實時操作系統內核,它僅僅包含了任務調度,任務管理,時間管理,內存管理和任務間的通信和同步等基本功能。沒有提供輸入輸出管理,文件系統,網絡等額外的服務。但由于uC/OS-II良好的可擴展性和源碼開放,這些非必須的功能完全可以由用戶自己根據需要分別實現。
uC/OS-II目標是實現一個基于優先級調度的搶占式的實時內核,并在這個內核之上提供最基本的系統服務,如信號量,郵箱,消息隊列,內存管理,中斷管理等。
uC/OS-II以源代碼的形式發布,是開源軟件, 但并不意味著它是免費軟件。你可以將其用于教學和私下研究(peaceful research);但是如果你將其用于商業用途,那么你必須通過Micrium獲得商用許可。
uCOSII移植的一點心得
uCOS-II是一種十分優秀實時操作系統,其在NASA的認證通過直接說明了其優秀及穩健的性能,同時由于其完全open,所以受到廣大開源愛好者的喜愛。uCOS-II簡單明了,同時絕大部分代碼都采用ANSI C編寫(除了與CPU相關代碼外),所以學習起來十分容易,是嵌入式學習乃至操作系統學習最好的入門OS之一。
我主要想講一下自己最近移植uCOS-II的心得,因為最近也在學習操作系統,所以這段日子對于uCOS-II的學習的確也讓我對于操作系統有了一個實際深刻的認識。
uCOS-II移植其實十分簡單。對于一個處理器,需要做的工作只有:修改三個文件――os_cpu_c.c、os_cpu.h、os_cpu_a.asm(ASM文件根據編譯器不同而又有一些不同)。
用另一種方式說,需要做的工作就是修改五個函數:
1、os_cpu_c.c:OSTaskStkInit;
2、os_cpu_a.asm:OSStartHighRdy、OSCtxSw、OSIntCtxSw、OSTickISR;
OSTaskStkInit函數是針對CPU壓棧的函數,需要模仿出CPU初始化后的寄存器狀況。也使需要修改的唯一一個C語言函數。其他的都是匯編函數。
如果我們可以從uCOS-II官方網站上找到相同CPU或是相似的同一家族的CPU移植代碼,那么我們的移植工作將會簡單得多。因為至少我們可以只用了解這個處理器的內部結構,而不用細致的了解其匯編指令等很多繁瑣而沒有意義的事情(有的處理器你可能一輩子不再用它,了解得太細致只是在浪費時間)。
譬如,此次我要做的是將uCOS-II移植到瑞薩M16C/62A上,而官方網站上只有其62P的移植代碼,于是乎我就將二者的datasheet在CPU的寄存器、中斷部分仔細比對,發現二者區別很小。最大的區別恐怕就在CPU內部寄存器中的INTBL和PC寄存器二者順序相反吧,這只要在相關部分注意就可以了,所以很容易就搞定了CPU相關代碼部分。
總結一下移植中浪費我時間的幾個小錯誤吧,這完全是個人粗心導致的失誤:
1、在os_cpu.h文件中需要用宏定義將OS_TASK_SW指向OSCtxSw函數,而我開始像以前一樣直接將OSCtxSw函數與0號軟終端鏈接起來,結果發現函數調用不成功。后來直接用宏定義將OS_TASK_SW define為OSCtxSw函數,初步調試通過,即驗證OSCtxSw函數正確,但是到后來調用任務的時候卻發現任務切換不正常,不得不重新將函數與中斷結合起來,當然就不能還是0號中斷了,而是改為一個比較保險的軟終端,這個問題糾結了很長時間。
2、在看書的時候不仔細直接導致我犯了一個大錯誤。起初以為task只要是能夠達到功能的死循環即可。所以每個task函數都是while(1)或者for(;;;),但是我沒有注意到一點就是每個task里面都應該有OSTimeDly()函數,否則將導致任務之間不能跳轉。所以最初的實驗現象是永遠只有一個任務在運行,但是任務不能切換……
3、未注意到版本之間的區別。我們知道在新版本的uCOS-II中,添加了一個文件os_tmr.c,主要是在timer上面做了很大的調整,但是我沒有注意到這一點,仍舊按照老版本的方法調試,導致函數調用讓我完全不知所措。最后注意到os的源代碼的不同,仔細閱讀源代碼之后知道了其用法,其實如果不需要timer太強大的功能,只要在os_cfg.h文件中將OS_TMR_EN設置為0即可。這在習慣老版本調試方法的同學而言是很好的方法。
ucos-II移植到51單片機的解決辦法
先來了解和51移植相關的三個概念:
第一,移植UCOS必須要了解編譯器,我們一般使用的51編譯器都是KEIL。值得一提的是KEIL對可重入函數的處理。由于51單片機的堆棧指針是8位的,所以硬件堆棧只能設置在內部RAM的DATA區和IDATA區(DATA、IDATA、PDATA、XDATA、CODE這些概念相關資料很多,我不想在此處滋述),所以51的堆棧是很緊張的。于是,KEIL將函數內的動態變量和函數傳遞的參數(當然有一部分參數是用寄存器直接傳送的),放在分配的固定數據段中,函數執行時在固定的數據段中去取得相關的數據,而不是像傳統的CPU都用堆棧來處理,這就導致了函數不可重入,因為當一個函數沒執行完成時再次執行會把數據段里的內容覆蓋掉。為了使函數可重入KEIL引入了仿真堆棧的概念(重入函數需在函數定義后面加上reentrant關鍵字),用仿真堆棧來傳遞參數及分配動態變量,就好像傳統堆棧的入棧、出棧操作一般,如此函數第二次進入執行時,就不會覆蓋掉上一次的變量和參數,仿真堆棧實現原理詳見http://hi.baidu.com/lyb1900/blog/item/99b6313defc2b40abaa167fe.html 。但是,KEIL的這一機制會給我們移植造成了麻煩,任務切換時不僅要保存好硬件堆棧內容,還要保存好仿真堆棧的內容。(建議先理解仿真堆棧的概念)
第二,其他類型的CPU可以在任務切換時先將SP指針保存到被中斷任務的OSTCBCur-》OSTCBStkPtr中,再將高優先級任務的OSTCBCur-》OSTCBStkPtr恢復到SP中就可以了,各個任務使用各自的堆棧空間,互不干擾,切換也很方便。而51的堆棧指針是8位的,SP只能指向內部RAM空間,但是內部RAM很小,根本不可能將所有任務堆棧都設置在內部RAM中(DATA和IDATA區)。所以,51只能設置一個固定的硬件堆棧,每個任務可以在外部RAM中設置各自的任務堆棧,任務切換時,將本任務所使用到的硬件堆棧的長度和內容保存到任務堆棧中,然后將高優先級任務的用戶堆棧里的內容恢復到硬件堆棧中。所以51切換任務會比較慢。
第三,在KEIL的工程配置Target選項中會有一個Memory Model選項。用鼠標點擊Memory Model的下拉箭頭,會有3個選項。
Small:變量存儲在內部ram里。
Compact:變量存儲在外部ram里,使用頁8位間接尋址
Large:變量存儲在外部Ram里,使用16位間接尋址。
這三個變量決定了定義的變量在不加存儲類型關鍵字時,變量存放的位置。這一點很多網站、資料都說的很明白。但是其實還有一點很多資料都是沒說的。它還默認決定了上述仿真堆棧的位置。這一點在51的啟動代碼STARTUP.asm中能體現出來。其中有一段如下:
; Stack Space for reentrant functions in the SMALL model.
IBPSTACK EQU 1 ; set to 1 if small reentrant is used.
IBPSTACKTOP EQU 0FFH+1 ; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the LARGE model.
XBPSTACK EQU 0 ; set to 1 if large reentrant is used.
XBPSTACKTOP EQU 7FFFH+1; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the COMPACT model.
PBPSTACK EQU 0 ; set to 1 if compact reentrant is used.
PBPSTACKTOP EQU 7FFFH+1; set top of stack to highest location+1.
IF IBPSTACK 《》 0
EXTRN DATA (?C_IBP)
MOV ?C_IBP,#LOW IBPSTACKTOP
ENDIF
IF XBPSTACK 《》 0
EXTRN DATA (?C_XBP)
MOV ?C_XBP,#HIGH XBPSTACKTOP
MOV ?C_XBP+1,#LOW XBPSTACKTOP
ENDIF
IF PBPSTACK 《》 0
EXTRN DATA (?C_PBP)
MOV ?C_PBP,#LOW PBPSTACKTOP
ENDIF
注釋講的很清楚,根據所選模式,編譯器會將IBPSTACK、PBPSTACK或者XBPSTACK設置為1,就決定了仿真堆棧在IDATA區、PDAIA區還是XDATA區。對應的,KEIL會自動分配一個仿真堆棧指針,分別是?C_IBP、?C_PBP和(?C_XBP、?C_XBP+1),由于尋址XDATA區需要16位地址,所以需要兩個字節。這三個指針是KEIL根據選擇的Memory Model選項自動分配的。
注意:不要試圖在選擇好模式后將仿真堆棧設置在另一模式的空間中。比如,我用的小模式編譯,仿真堆棧在IDATA區,用的仿真堆棧指針是?C_IBP,但是我現在在啟動代碼中將IBPSTACK定義為0,將XBPSTACK設置為1,看起來我們先把仿真堆棧設置在XDATA區了,但實際上其它代碼段中使用的仿真堆棧指針任然是?C_IBP。有趣的是,KEIL還為我們的啟動代碼做了一個很友好的列表框選擇界面。但實際上選擇好編譯模式后,仿真堆棧使用空間是不能更改的,不知道KEIL為什么這么做?但是我們有時候要根據單片機的型號選擇仿真堆棧的起始地址。
講了那么多,應該來看看關于堆棧的組織了,首先是不知道哪位前輩移植的,用的小模式編譯的堆棧結構:
每個任務分都需要配一個任務堆棧,OSTCBCur-》OSTCBStkPtr指向任務堆棧的棧底,任務堆棧的首字節是仿真堆棧指針?C_IBP(由于是小模式編譯,所以使用的仿真堆棧設置在IDAIA區)。用戶堆棧中緊接著存放的是該任務的仿真堆棧中的內容。再接著是系統堆棧(就是SP指針所指的堆棧)的長度,最后是系統堆棧的內容。
任務在切換時,首先將當前的?C_IBP的值保存到本任務堆棧的首地址中,然后將仿真堆棧的全部內容復制到任務堆棧中(仿真堆棧棧底固定在IDATA區的最高字節0xff,可以根據(0xff-?C_IBP+1)的值來確定所使用的仿真堆棧的長度),接著保存系統堆棧的長度(系統堆棧設置在DATA或IDATA區中,系統堆棧的棧底的地址我們可以在啟動代碼中設置,長度可以用(SP-Stack+1)來計算得到)最后將所用的系統堆棧中的內容復制到任務堆棧中。
然后得到高優先級的任務堆棧,首先恢復高優先級任務的?C_IBP,然后計算出高優先級任務所用仿真堆棧的長度,將保存的仿真堆棧的內容一一恢復到仿真堆棧中,然后得到系統棧的長度,再將保存的系統堆棧的內容恢復到系統堆棧中,最后恢復SP指針并執行RETI返回指令,便實現了任務切換。
任務被打斷時將仿真堆棧和系統堆棧的內容全都備份到任務堆棧中,在恢復運行時將相應的內容還原到系統堆棧和仿真堆棧中。
這種方法的缺點是,任務切換將會變的很慢,因為要分別拷貝和恢復仿真堆棧和系統堆棧的全部內容。完全可以將仿真堆棧設置在XDATA區中,任務切換時,只需保存和恢復?C_XBP指針就行了,而不必每次都拷貝和恢復仿真堆棧的全部內容。由于SP指針只有8位,系統堆棧只能設置在內部RAM中。
再來看看楊屹大俠大模式編譯下的堆棧結構:
同樣,每個任務分配一個任務堆棧,OSTCBCur-》OSTCBStkPtr指向任務堆棧的棧底,任務堆棧的首字節是系統堆棧的長度,接著是系統堆棧的全部內容。再接著是仿真堆棧指針?C_XBP的高低字節(因為是大模式編譯,所以仿真堆棧在XDATA區),任務堆棧再高的字節是作為仿真堆棧用的,用戶堆棧的棧頂就是仿真堆棧的棧底。
任務切換時,首先計算任務使用的系統堆棧的長度,將長度保存在任務堆棧棧底,然后將使用的系統堆棧的內容全部復制到任務堆棧中,最后保存當前的?C_XBP仿真堆棧指針的高低字節。
接著恢復高優先級任務的信息,先得到堆棧長度,將備份的堆棧內容恢復到系統堆棧中,并恢復SP指針(根據長度和系統堆棧的棧底可以計算出SP指針的值)。最后恢復?C_XBP的高低字節。便實現了任務的切換。
任務切換時將系統堆棧的內容和仿真堆棧指針保存起來,再將高優先級任務的仿真堆棧指針和系統堆棧的內容恢復。
和上述的小模式下的切換過程相比,仿真堆棧的內容在任務切換時不需要保存和恢復了,任務切換速度會提高不少。但是讀過楊屹大俠代碼的朋友肯定知道,每個任務堆棧的大小都要設置成相同。這對于有些堆棧使用很少的任務來說是很浪費的,而且51的RAM本來就那么緊張?仿真堆棧被設置在任務堆棧的最高地址處,細心的朋友會發現,堆棧檢測函數肯定是無法運行了。
正是意識到這些缺陷,我對楊屹大俠移植的代碼進行了一些改動,堆棧結構也有較大改變,使用的也是大模式編譯:
先來了解和51移植相關的三個概念:
第一,移植UCOS必須要了解編譯器,我們一般使用的51編譯器都是KEIL。值得一提的是KEIL對可重入函數的處理。由于51單片機的堆棧指針是8位的,所以硬件堆棧只能設置在內部RAM的DATA區和IDATA區(DATA、IDATA、PDATA、XDATA、CODE這些概念相關資料很多,我不想在此處滋述),所以51的堆棧是很緊張的。于是,KEIL將函數內的動態變量和函數傳遞的參數(當然有一部分參數是用寄存器直接傳送的),放在分配的固定數據段中,函數執行時在固定的數據段中去取得相關的數據,而不是像傳統的CPU都用堆棧來處理,這就導致了函數不可重入,因為當一個函數沒執行完成時再次執行會把數據段里的內容覆蓋掉。為了使函數可重入KEIL引入了仿真堆棧的概念(重入函數需在函數定義后面加上reentrant關鍵字),用仿真堆棧來傳遞參數及分配動態變量,就好像傳統堆棧的入棧、出棧操作一般,如此函數第二次進入執行時,就不會覆蓋掉上一次的變量和參數,仿真堆棧實現原理詳見http://hi.baidu.com/lyb1900/blog/item/99b6313defc2b40abaa167fe.html 。但是,KEIL的這一機制會給我們移植造成了麻煩,任務切換時不僅要保存好硬件堆棧內容,還要保存好仿真堆棧的內容。(建議先理解仿真堆棧的概念)
第二,其他類型的CPU可以在任務切換時先將SP指針保存到被中斷任務的OSTCBCur-》OSTCBStkPtr中,再將高優先級任務的OSTCBCur-》OSTCBStkPtr恢復到SP中就可以了,各個任務使用各自的堆棧空間,互不干擾,切換也很方便。而51的堆棧指針是8位的,SP只能指向內部RAM空間,但是內部RAM很小,根本不可能將所有任務堆棧都設置在內部RAM中(DATA和IDATA區)。所以,51只能設置一個固定的硬件堆棧,每個任務可以在外部RAM中設置各自的任務堆棧,任務切換時,將本任務所使用到的硬件堆棧的長度和內容保存到任務堆棧中,然后將高優先級任務的用戶堆棧里的內容恢復到硬件堆棧中。所以51切換任務會比較慢。
第三,在KEIL的工程配置Target選項中會有一個Memory Model選項。用鼠標點擊Memory Model的下拉箭頭,會有3個選項。
Small:變量存儲在內部ram里。
Compact:變量存儲在外部ram里,使用頁8位間接尋址
Large:變量存儲在外部Ram里,使用16位間接尋址。
這三個變量決定了定義的變量在不加存儲類型關鍵字時,變量存放的位置。這一點很多網站、資料都說的很明白。但是其實還有一點很多資料都是沒說的。它還默認決定了上述仿真堆棧的位置。這一點在51的啟動代碼STARTUP.asm中能體現出來。其中有一段如下:
; Stack Space for reentrant functions in the SMALL model.
IBPSTACK EQU 1 ; set to 1 if small reentrant is used.
IBPSTACKTOP EQU 0FFH+1 ; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the LARGE model.
XBPSTACK EQU 0 ; set to 1 if large reentrant is used.
XBPSTACKTOP EQU 7FFFH+1; set top of stack to highest location+1.
;
; Stack Space for reentrant functions in the COMPACT model.
PBPSTACK EQU 0 ; set to 1 if compact reentrant is used.
PBPSTACKTOP EQU 7FFFH+1; set top of stack to highest location+1.
IF IBPSTACK 《》 0
EXTRN DATA (?C_IBP)
MOV ?C_IBP,#LOW IBPSTACKTOP
ENDIF
IF XBPSTACK 《》 0
EXTRN DATA (?C_XBP)
MOV ?C_XBP,#HIGH XBPSTACKTOP
MOV ?C_XBP+1,#LOW XBPSTACKTOP
ENDIF
IF PBPSTACK 《》 0
EXTRN DATA (?C_PBP)
MOV ?C_PBP,#LOW PBPSTACKTOP
ENDIF
注釋講的很清楚,根據所選模式,編譯器會將IBPSTACK、PBPSTACK或者XBPSTACK設置為1,就決定了仿真堆棧在IDATA區、PDAIA區還是XDATA區。對應的,KEIL會自動分配一個仿真堆棧指針,分別是?C_IBP、?C_PBP和(?C_XBP、?C_XBP+1),由于尋址XDATA區需要16位地址,所以需要兩個字節。這三個指針是KEIL根據選擇的Memory Model選項自動分配的。
注意:不要試圖在選擇好模式后將仿真堆棧設置在另一模式的空間中。比如,我用的小模式編譯,仿真堆棧在IDATA區,用的仿真堆棧指針是?C_IBP,但是我現在在啟動代碼中將IBPSTACK定義為0,將XBPSTACK設置為1,看起來我們先把仿真堆棧設置在XDATA區了,但實際上其它代碼段中使用的仿真堆棧指針任然是?C_IBP。有趣的是,KEIL還為我們的啟動代碼做了一個很友好的列表框選擇界面。但實際上選擇好編譯模式后,仿真堆棧使用空間是不能更改的,不知道KEIL為什么這么做?但是我們有時候要根據單片機的型號選擇仿真堆棧的起始地址。
結語
關于ucos-II的相關介紹就到這了,如有不足之處歡迎指正。
-
處理器
+關注
關注
68文章
19825瀏覽量
233776 -
單片機
+關注
關注
6065文章
44934瀏覽量
647653
發布評論請先 登錄
在51系列單片機上移植uCOS-II

評論