uC/OS-II在ARM系統(tǒng)上的移植與實現(xiàn)
摘要:使用ARM公司提供的ADS 開發(fā)工具,將uC/ OS - II 移植到ARM 處理器上,并將移植結(jié)果應(yīng)用在跑馬燈和數(shù)碼管的實現(xiàn)上,運行正常,表明移植成功. 關(guān)鍵詞:uC/ OS - II ;ARM;移植 0 引言 在開發(fā)嵌入式系統(tǒng)時,一般選擇基于ARM 和uC/ OS - II 的嵌入式開發(fā)平臺,因為ARM 微處理器具有處理速度快、超低功耗、價格低廉、應(yīng)用前景廣泛等優(yōu)點[1 ] . 將uC/ OS - II 移植到ARM 系統(tǒng)之后,可以充分結(jié)合兩者的優(yōu)勢. 如果一個程序在一個環(huán)境里能工作,我們經(jīng)常希望能將它移植到另一個編譯系統(tǒng)、處理器或者操作系統(tǒng)上,這就是移植技術(shù).移植技術(shù)可以使一種特定的技術(shù)在更加廣泛的范圍使用,使軟件使用更加靈活,不局限于某一條件.uC/OS - II 是由Jean J . Labrosse 先生編寫的完整的可移植、固化、裁剪的占先式實時多任務(wù)內(nèi)核.uC/ OS - II 的源代碼完全開放,這是其他商業(yè)實時內(nèi)核無法比擬的[2 ] . 它是針對嵌入式應(yīng)用設(shè)計的,在設(shè)計之初就充分考慮了可移植性,它的大部分源代碼都是用高可移植性的ANSIC 編寫的[3 ] . uC/ OS - II可以移植到從8 位到64 位的不同類型、不同規(guī)模的嵌入式系統(tǒng),并能在大部分的8 位、16 位、32 位、甚至64 位的微處理器和DSP 上運行. 由于uC/ OS - II是一個實時操作系統(tǒng),所以如果將它嵌入到ARM處理器上,就能夠進一步簡化ARM系統(tǒng)的開發(fā).
圖1 uC/ OS - II 文件體系結(jié)構(gòu) [b]1 uC/ OS - II 的移植 [/b] uC/OS - II 的文件系統(tǒng)結(jié)構(gòu)包括核心代碼部分、設(shè)置代碼部分、與處理器相關(guān)的移植代碼部分[4 ] . 結(jié)構(gòu)如圖1 所示.其中最上邊的軟件應(yīng)用層是uC/ OS - II 上的代碼. 核心代碼部分包括7 個源代碼文件和1 個頭文件. 功能分別是內(nèi)核管理、事件管理、消息隊列管理、存儲管理、消息管理、信號量處理、任務(wù)調(diào)度和定時管理. 設(shè)置代碼部分包括2 個頭文件,用來配置事件控制塊的數(shù)目以及是否包含消息管理相關(guān)代碼. 而與處理器相關(guān)的移植代碼部分則是進行移植過程中需要更改的部分,包括1 個頭文件OS CPU. H ,1 個匯編文件OS CPU A. S 和1 個C 代碼文件.實際上將uC/ OS - II 移植到ARM 處理器上,需要完成的工作主要是以下三個與體系結(jié)構(gòu)相關(guān)的文件:OS CPU. H ,OS CPU. C 以及OS CPU A. S[5 ] . 1. 1 OS CPU. H 的移植 文件OS CPU. H 中包括了用# define 語句定義的與處理器相關(guān)的常數(shù)、宏以及類型. 移植時主要修改的內(nèi)容有:與編譯器相關(guān)的數(shù)據(jù)類型的設(shè)定;用#define 語句定義2 個宏開關(guān)中斷;根據(jù)堆棧的方向定義OS STK GROWTH等. 在將uC/ OS - II 移植到ARM 處理器上時,首先進行基本配置和數(shù)據(jù)類型定義. 重新定義數(shù)據(jù)類型是為了增加代碼的可移植性,因為不同的編譯器所提供的同一數(shù)據(jù)類型的數(shù)據(jù)長度并不相同,例如int型,在有的編譯器中是16 位,而在另外一些編譯器中則是32 位. 所以,為了便于移植,需要重新定義數(shù)據(jù)類型,如INT32U 代表無符號32 位整型. typedefunsigned int INT8U ,就是定義一個8 位的無符號整型數(shù)據(jù)類型. 其次就是對ARM 處理器相關(guān)宏進行定義,如ARM處理器中的退出臨界區(qū)和進入臨界區(qū)的宏定義,退出臨界區(qū)宏定義[5 ] : # define OS EXITCRITICAL () ARMDisable Int ( ) / / 關(guān)中斷,進入臨界區(qū)宏定義# define OS ENTER CRITICAL ( ) AR2MEnableInt () / / 開中斷. 最后就是堆棧增長方向的設(shè)定. 當進行函數(shù)調(diào)用時,入口參數(shù)和返回地址一般都會保存在當前任務(wù)的堆棧中,編譯器的編譯選項和由此生成的堆棧指令就會決定堆棧的增長方向[6 ] ,定義為# define OS STK GROWTH 1.
圖2 堆棧增長方向 1. 2 OS CPU. C 的移植 OS CPU. C 的移植包括任務(wù)堆棧初始化和相應(yīng)函數(shù)的實現(xiàn). 在這里,共有6 個函數(shù):OSTaskStkInit( ) , OSSTaskCreateHook ( ) , OSTaskDelHook ( ) , OS2TaskSwHook( ) ,OSTaskStatHook ( ) , OSTimeTickHook () . 其中后面的5 個HOOK函數(shù)又稱為鉤子函數(shù),主要是用來對uC/ OS - II 進行功能擴展. 這些函數(shù)為用戶定義函數(shù),由操作系統(tǒng)調(diào)用相應(yīng)的HOOK函數(shù)去執(zhí)行,在一般情況下,他們都沒有代碼,所以實現(xiàn)為空函數(shù)即可. 而函數(shù)OSTaskStkInit ( ) 對堆棧進行初始化,在ARM 系統(tǒng)中,任務(wù)堆??臻g由高到低依次為PC ,LR ,R12 ,R11 , %26;#8943;,R1 ,R0 ,CPSR ,SPSR. 在進行堆棧初始化以后,OSTaskStkInit ( ) 返回新的堆棧棧頂指針. 1. 3 OS CPU A. S 的移植 OS CPU A. S 文件的移植需要對處理器的寄存器進行操作,所以必須用匯編語言來編寫. 這個文件的實現(xiàn)集中體現(xiàn)了所要移植到處理器的體系結(jié)構(gòu)和uC/ OS - II 的移植原理[6 ] . 它包括4 個子函數(shù):OSStartHighRdy() , OSCtxSw() , OSIntCtxSw() ,OSTick2ISR() . 其中難點在于OSIntCtxSw() 和OSTickISR() 函數(shù)的實現(xiàn),因為這兩個函數(shù)的實現(xiàn)與移植者的移植思路以及相關(guān)硬件定時器、中斷寄存器的設(shè)置有關(guān).在實際的移植工作中,這兩處也是比較容易出錯的地方. OSIntCtxSw( ) 函數(shù)由OSIntExit ( ) 函數(shù)調(diào)用,而OSIntExit () 函數(shù)又由OSTickISR() 調(diào)用. OSIntCtxSw()函數(shù)最重要的作用就是它完成在中斷ISR 中直接進行任務(wù)切換,從而提高了實時響應(yīng)的速度. 它發(fā)生的時機是在ISR 執(zhí)行到OSIntExit ( ) 時,如果發(fā)現(xiàn)有高優(yōu)先級的任務(wù)因為等待time tick 的到來獲得了執(zhí)行%26;#8226; 7 2 %26;#8226; 第4 期李學(xué)橋等:uC/ OS - II 在ARM系統(tǒng)上的移植與實現(xiàn)的條件,就可以馬上被調(diào)度執(zhí)行,而不用返回被中斷的那個任務(wù)之后再進行任務(wù)切換. 實現(xiàn)OSIntCtxSw() 的方法大致也有兩種情況[7 ] :一是通過調(diào)整SP 堆棧指針的方法,根據(jù)所用的編譯器對于函數(shù)嵌套的處理,通過精確計算出所需要調(diào)整的SP 位置來使得進入中斷時所作的保護現(xiàn)場的工作可以被重用. 二是設(shè)置需要切換標志位的方法,在OSIntCtxSw( ) 里面不發(fā)生切換,而是設(shè)置一個需要切換的標志,等函數(shù)嵌套從進入OSIntExit ( ) = > OS ENTER CRITI2CAL() = > OSIntCtxSw( ) = > OS EXIT CRITICAL() = > OSIntExit ( ) 退出后,再根據(jù)標志位來判斷是否需要進行中斷級的任務(wù)切換. 其次是對OSTickISR() 修改.OSTickISR() 首先在被中斷任務(wù)堆棧中保存CPU 寄存器的值,然后調(diào)用OSIntEnter () . 隨后調(diào)用OSTimeTick() ,檢查所有處于延時等待狀態(tài)的任務(wù),判斷是否有延時結(jié)束就緒的任務(wù). 最后調(diào)用OSIntExit ( ) . 如果在中斷中(或其他嵌套的中斷) 有更高優(yōu)先級的任務(wù)就緒,并且當前中斷為中斷嵌套的最后一層,OSIntExit ( ) 將進行任務(wù)調(diào)度. 如果進行了任務(wù)調(diào)度,OSIntExit () 將不再返回調(diào)用者,而是用新任務(wù)的堆棧中的寄存器數(shù)值恢復(fù)CPU 現(xiàn)場,然后實現(xiàn)任務(wù)切換. 如果當前中斷不是中斷嵌套的最后一層,或中斷中沒有改變?nèi)蝿?wù)的就緒狀態(tài), OSIntExit ( ) 將返回調(diào)用者OSTickISR ( ) ,OSTickISR() 返回被中斷的任務(wù). 最后就是退出臨界區(qū)和進入臨界區(qū)函數(shù). 進入臨界區(qū)時,必須關(guān)閉中斷,用ARMDisableInt () 函數(shù)實現(xiàn). 在退出臨界區(qū)的時候恢復(fù)原來的中斷狀態(tài),通過ARMEnableInt ( ) 函數(shù)來實現(xiàn)[7 ] . 至于進行任務(wù)級上下文切換,則是由匯編子程序OSCtxSw 實現(xiàn). [b] 2 在ARM系統(tǒng)上的實現(xiàn) [/b] 以跑馬燈和數(shù)碼管為例,說明uC/ OS - II 的移植過程:跑馬燈是4 個小燈輪流變明變暗,很方便看出效果. 跑馬燈在日常中使用很多,比如狀態(tài)欄跑馬燈、文字跑馬燈、圖片跑馬燈、單片機跑馬燈等[8 ] . 本文采用的是單片機跑馬燈. 實現(xiàn)單片機跑馬燈的程序中,只有地址口為低電平(接地) 時,發(fā)光管才會亮. 所以只要循環(huán)控制地址口的各個引腳的電平高低變化就可使LED 循環(huán)點亮:首先是全不亮,接著第1 個燈亮,第2 個燈亮,第3 個燈亮,第4 個燈亮,第5 個燈亮,最后所有的燈一起亮. 筆者使用6 個共陽極LED 數(shù)碼管來實現(xiàn)在7 段數(shù)碼管上循環(huán)顯示0~9 ,A~F 字符. 每個顯示位的段選線與一個8 位并行口線對應(yīng)相連,只要在顯示位上的段選線上保持段碼電平不變,則該位就能保持相應(yīng)的顯示字符. 這里的8 位并行口可以直接采用并行I/ O 口,也可以采用串入/ 并出的移位寄存器或是其他具有三態(tài)功能的鎖存器等. 當采用動態(tài)顯示接口時,在多位LED 顯示時,為了簡化電路,降低成本,將所有位的段選線并聯(lián)在一起,由一個8 位I/ O口控制. 而共陰(或共陽) 極公共端分別由相應(yīng)的I/ O 線控制,實現(xiàn)各位的分時選通. 由于各個數(shù)碼管是共用同一個段碼輸出口分時輪流通電的,從而大大簡化了硬件線路,降低了成本. 對于數(shù)碼管的實現(xiàn)分為3 個步驟: 1) 制作LED 字符與碼段對應(yīng)表 2) 掃描控制 3 ( (U8 3 ) 0x02000006) = 0x3E; / 3 使能第一個數(shù)碼管 3 / 3) 段碼輸出 ( (U8 3 ) 0x02000004) = seg7table[0 ] ; 根據(jù)上面的LED 字符與碼段對應(yīng)表,控制相應(yīng)的數(shù)字進行輸出. 數(shù)碼管掃描控制地址為0x02000006 ,8 位訪問,比如Bit0 控制數(shù)碼管0 ,并且低電平有效,Bit5 控制數(shù)碼管5 ,低電平有效,數(shù)碼管顯示試驗系統(tǒng)中采用的是動態(tài)顯示接口,其中數(shù)碼管掃描控制地址為0x02000006 ,位0 —5 分別對應(yīng)一個數(shù)碼管,將其中每位清0 來選擇相應(yīng)的數(shù)碼管;地址0x02000004 為數(shù)碼管的數(shù)據(jù)寄存器,控制數(shù)碼管的段碼輸出. [b] 3 多任務(wù)應(yīng)用程序 [/b] uC/OS - II 的移植及跑馬燈和數(shù)碼管的實現(xiàn)如下[9 ] :首先是C 語言入口函數(shù)Main (所有C 程序的入口) . 它里面包括調(diào)用函數(shù)ARMTargetInit () 初始化ARM處理器,調(diào)用OSInit ( ) 進行uC/ OS - II 操作系統(tǒng)初始化,然后調(diào)用OSTaskCreate ( ) 函數(shù)創(chuàng)建任務(wù)TaskLED 和TaskSEG,最后調(diào)用ARMTargetStart () 函數(shù)啟動時鐘節(jié)拍中斷,并且調(diào)用OSStart ( ) 啟動系統(tǒng)任務(wù)調(diào)度,由于在程序當中使用for ( ; ;) ,這是一個永無止境的回路,所以裝置可以一直進行下去,直到關(guān)閉裝置. void Main(void) {ARMTargetInit () ; uHALr printf (″uC/ OS - II # n″) ; OSInit () ; Sem1 = OSSemCreate(0) ; Sem2 = OSSemCreate(1) ; OSTaskCreate(TaskLED , (void 3 ) %26;amp;IdLED , (OS STK 3 ) %26;amp;StackLED[ STACKSIZE - 1 ] , 5) ; OSTaskCreate(TaskSEG, (void 3 ) %26;amp;IdSEG, (OS STK 3 ) %26;amp;StackSEG[ STACKSIZE - 1 ] , 6) ; ARMTargetStart () ;OSStart () ; return ;} 4 結(jié)語 使用創(chuàng)建好的模板Temp 新建一個工程Temp ,并將模板中的Core 和Assemble 文件夾中的文件加入到工程Temp 中. 1) 新建一個文件Temp. c ,并將其添加到Temp 工程的App 文件夾中. 2) 打開Temp. c文件,添加兩個任務(wù),它們的任務(wù)處理函數(shù)分別為TaskLED() 和TaskSEG() . 3) 在TaskLED( ) 函數(shù)中每隔50 個時鐘節(jié)拍使所有跑馬燈閃爍一次(即按順序亮,然后全亮,最后全滅,順序循環(huán)) . 4) 在TaskSEG() 函數(shù)中每隔50 個時鐘節(jié)拍切換一次數(shù)碼管顯示(循環(huán)從0~F 顯示) . 5) 編譯工程Temp ,如果出錯,則進行修改后再編譯. 6) 將Temp 下載并運行,看結(jié)果. 正確的結(jié)果是將每隔1/ 2 s 切換一次數(shù)碼管顯示,每隔1/ 2 s使所有跑馬燈閃爍一次. 經(jīng)持續(xù)了2 h試驗,沒有出現(xiàn)錯誤,跑馬燈和數(shù)碼管正常運轉(zhuǎn),結(jié)果證明移植成功. [b]參考文獻: [/b][1 ] 雷必成, 吳高標, 吳永良. 嵌入式實時操作系統(tǒng)uC/ OS - II 的移植探討[J ] . 自動化技術(shù)與應(yīng)用,2003 , (5) :1 —3. [2 ] 邵貝貝. 嵌入式實時操作系統(tǒng)uC/ OS - II[M] . 第2 版. 北京:北京航空航天大學(xué)出版社,2003. 2 —30. [3 ] 葉豐橋,黃海. uC/ OS - II 在51XA 上的移植應(yīng)用[J ] . 工業(yè)控制計算機,2002 , (10) :1 —2. [4 ] 田澤. 嵌入式開發(fā)與應(yīng)用實驗教程[M] . 北京: 北京航空航天大學(xué)出版社,2004. 264 —270. [5 ] 陳賾. ARM嵌入式技術(shù)實踐教程[M] . 北京:北京航空航天大學(xué)出版社,2005. 189 —203. [6 ] 王田苗. 嵌入式系統(tǒng)設(shè)計與實例開發(fā)[M] . 北京: 清華大學(xué)出版社,2003. 62 —89. [7 ] 朱華軍. uC/ OS - II 操作系統(tǒng)在ARM處理器上的技巧 [J ] . 計算機工程,2004 , (S1) :2 —3. [8 ] 蘇中義,楊宇. 嵌入式系統(tǒng)[J ] . 嵌入系統(tǒng),2004 , (3) :11 [9 ] 曾鳴. uC/ OS - II 實時操作系統(tǒng)在嵌入式平臺上進行移植的一般方法和技巧[ J ] . 今日電子, 2004 , (11) :2 —3.