close
 

1.06 應用 µC/OS-II 的範例


本章中的例子都用Borland C/C++編譯器編譯通過,是在Windows95 DOS窗口下編譯的。可執行代碼可以在每個範例的OBJ子目錄下找到。實際上這些代碼是在Borland IDE (Integrated Development Environment)下編譯的,編譯時的選項如表1.1所示:














































































T1.1 IDE中編譯選項。



Code generation






Model



: Large



Options



: Treat enums as ints



Assume SS Equals DS



: Default for memory model



Advanced code generation






Floating point



: Emulation



Instruction set



: 80186



Options



: Generate underbars






Debug info in OBJs






Fast floating point



Optimizations






Optimizations



Global register allocation






Invariant code motion






Induction variables






Loop optimization






Suppress redundant loads






Copy propagation






Dead code elimination






Jump optimization






In-line intrinsic functions



Register variables



Automatic



Common subexpressions



Optimize globally



Optimize for



Speed





筆者的Borland C/C++編譯器安裝在C:\CPP目錄下,如果用戶的編譯器是在不同的目錄下,可以在Options/Directories的提示下改變IDE的路徑。


µC/OS-II是一個可裁剪的作業系統,這意味著用戶可以去掉不需要的服務。代碼的削減可以通過設置OS_CFG.H中的#defines OS_???_EN 0來實現。用戶不需要的服務代碼就不生成。本章的範例就用這種功能,所以每個例子都定義了不同的OS_???_EN


1.071


第一個範例可以在\SOFTWARE\uCOS_II\EX1_x86L目錄下找到,它有13個任務(包括 µC/OS-II 的空閒任務)µC/OS-II 增加了兩個內部任務:空閒任務和一個計算CPU利用率的任務。例1建立了11個其他任務。TaskStart()任務是在函數main()中建立的;它的功能是建立其他任務並且在螢幕上顯示如下統計資訊:






  • 每秒鐘任務切換次數;



  • CPU利用百分率;



  • 寄存器切換次數;



  • 目前日期和時間;



  • µC/OS-II的版本號;





TaskStart()還檢查是否按下ESC鍵,以決定是否返回到DOS


其餘10個任務基於相同的代碼——Task();每個任務在螢幕上隨機的位置顯示一個09的數位。


1.07.01 main()


1基本上和最初µC/OS中的第一個例子做一樣的事,但是筆者整理了其中的代碼,並且在螢幕上加了彩色顯示。同時筆者使用原來的資料類型(UBYTE, UWORD等)來說明µC/OS-II向下相容。


main()程式從清整個螢幕開始,爲的是保證螢幕上不留有以前的DOS下的顯示[L1.5(1)]。注意,筆者定義了白色的字元和黑色的背景色。既然要請螢幕,所以可以只定義背景色而不定義前景色,但是這樣在退回DOS之後,用戶就什麽也看不見了。這也是爲什麽總要定義一個可見的前景色。


µC/OS-II要用戶在使用任何服務之前先調用OSInit() [L1.5(2)]。它會建立兩個任務:空閒任務和統計任務,前者在沒有其他任務處於就緒態時運行;後者計算CPU的利用率。


































程式清單 L 1.5 main().



void main (void)



{



PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); (1)



OSInit(); (2)



PC_DOSSaveReturn(); (3)



PC_VectSet(uCOS, OSCtxSw); (4)



RandomSem = OSSemCreate(1); (5)



OSTaskCreate(TaskStart, (6)



(void *)0,



(void *)&TaskStartStk[TASK_STK_SIZE-1],



0);



OSStart(); (7)



}





當前DOS環境是通過調用PC_DOSSaveReturn()[L1.5(3)]來保存的。這使得用戶可以返回到沒有運行µC/OS-II以前的DOS環境。跟隨清單L1.6中的程式可以看到PC_DOSSaveReturn()做了很多事情。PC_DOSSaveReturn()首先設置PC_ExitFlagFALSE[L1.6(1)],說明用戶不是要返回DOS,然後初始化OSTickDOSCtr1[L1.6(2)],因爲這個變數將在OSTickISR()中遞減,而0將使得這個變數在OSTickISR()中減1後變爲255。然後,PC_DOSSaveReturn()DOS 的時鐘節拍處理(tick handler)存入一個自由向量表入口中[L1.6(3)-(4)],以便爲µC/OS-II的時鐘節拍處理所調用。接著PC_DOSSaveReturn()調用jmp()[L1.6(5)],它將處理器狀態(即所有寄存器的值)存入被稱爲PC_JumpBuf的結構之中。保存處理器的全部寄存器使得程式返回到PC_DOSSaveReturn()並且在調用setjmp()之後立即執行。因爲PC_ExitFlag被初始化爲FALSE[L1.6(1)]PC_DOSSaveReturn()跳過if狀態語句 [L1.6(6)–(9)] 回到main()函數。如果用戶想要返回到DOS,可以調用 PC_DOSReturn()(程式清單 L 1.7),它設置PC_ExitFlagTRUE,並且執行longjmp()語句[L1.7(2)],這時處理器將跳回 PC_DOSSaveReturn()[在調用 setjmp()之後] [L1.6(5)],此時PC_ExitFlagTRUE,故if語句以後的代碼將得以執行。 PC_DOSSaveReturn()將時鐘節拍改爲 18.2Hz[L1.6(6)],恢復PC 時鐘節拍中斷服務[L1.6(7)],清螢幕[L1.6(8)],通過exit(0)返回DOS [L1.6(9)]
















































程式清單 L 1.6 保存DOS環境。.



void PC_DOSSaveReturn (void)



{



PC_ExitFlag = FALSE; (1)



OSTickDOSCtr = 8; (2)



PC_TickISR = PC_VectGet(VECT_TICK); (3)






OS_ENTER_CRITICAL();



PC_VectSet(VECT_DOS_CHAIN, PC_TickISR); (4)



OS_EXIT_CRITICAL();






Setjmp(PC_JumpBuf); (5)



if (PC_ExitFlag == TRUE) {



OS_ENTER_CRITICAL();



PC_SetTickRate(18); (6)



PC_VectSet(VECT_TICK, PC_TickISR); (7)



OS_EXIT_CRITICAL();



PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); (8)



exit(0); (9)



}



}



















程式清單 L 1.7 設置返回DOS



void PC_DOSReturn (void)



{



PC_ExitFlag = TRUE; (1)



longjmp(PC_JumpBuf, 1); (2)



}





現在回到main()這個函數,在程式清單 L 1.5中,main()調用PC_VectSet()來設置µCOS-II中的 CPU寄存器切換。任務級的CPU寄存器切換由80x86 INT指令來分配向量位址。筆者使用向量0x80(即128),因爲它未被DOSBIOS使用。


這裏用了一個信號量來保護Borland C/C++庫中的産生亂數的函數[L1.5(5)],之所以使用信號量保護一下,是因爲筆者不知道這個函數是否具備可重入性,筆者假設其不具備,初始化將信號量設置爲1,意思是在某一時刻只有一個任務可以調用亂數産生函數。


在開始多工之前,筆者建立了一個叫做TaskStart()的任務[L1.5(6)],在啓動多工OSStart()之前用戶至少要先建立一個任務,這一點非常重要[L1.5(7)]。不這樣做用戶的應用程式將會崩潰。實際上,如果用戶要計算CPU的利用率時,也需要先建立一個任務。µCOS-II的統計任務要求在整個一秒鐘內沒有任何其他任務運行。如果用戶在啓動多工之前要建立其他任務,必須保證用戶的任務代碼監控總體變數OSStatRdy和延時程式 [即調用 OSTimeDly()]的執行,直到這個變數變成TRUE。這表明µC/OS-IICPU利用率統計函數已經採集到了資料。


1.07.02 TaskStart()


1中的主要工作由TaskStart()來完成。TaskStart()函數的示意代碼如程式清單 L 1.8所示。TaskStart()首先在螢幕頂端顯示一個標識,說明這是例1 [L1.8(1)]。然後關中斷,以改變中斷向量,讓其指向µC/OS-II的時鐘節拍處理,而後,改變時鐘節拍率,從DOS18.2Hz 變爲 200Hz [L1.8(3)]。在處理器改變中斷向量時以及系統沒有完全初始化前,當然不希望有中斷打入!注意main()這個函數(見程式清單 L 1.5)在系統初始化的時候並沒有將中斷向量設置成µC/OS-II的時鐘節拍處理程式,做嵌入式應用時,用戶必須在第一個任務中打開時鐘節拍中斷。






























































程式清單 L 1.8 建立其他任務的任務。



void TaskStart (void *data)



{



Prevent compiler warning by assigning ‘data’ to itself;



Display banner identifying this as EXAMPLE #1; (1)





OS_ENTER_CRITICAL();



PC_VectSet(0x08, OSTickISR); (2)



PC_SetTickRate(200); (3)



OS_EXIT_CRITICAL();






Initialize the statistic task by calling ‘OSStatInit()’; (4)






Create 10 identical tasks; (5)






for (;;) {



Display the number of tasks created;



Display the % of CPU used;



Display the number of task switches in 1 second;



Display uC/OS-II’s version number



If (key was pressed) {



if (key pressed was the ESCAPE key) {



PC_DOSReturn();



}



}



Delay for 1 Second;



}



}





在建立其他任務之前,必須調用OSStatInit()[L1.8(4)]來確定用戶的PC有多快,如程式清單L1.9所示。在一開始,OSStatInit()就將自身延時了兩個時鐘節拍,這樣它就可以與時鐘節拍中斷同步[L1.9(1)]。因此,OSStatInit()必須在時鐘節拍啓動之後調用;否則,用戶的應用程式就會崩潰。當µC/OS-II調用OSStatInit()時,一個32位的計數器OSIdleCtr被清爲0 [L1.9(2)],並産生另一個延時,這個延時使OSStatInit()挂起。此時,uCOS-II沒有別的任務可以執行,它只能執行空閒任務(µC/OS-II的內部任務)。空閒任務是一個無線的迴圈,它不斷的遞增OSIdleCtr[L1.9(3)]1秒以後,uCOS-II重新開始OSStatInit(),並且將OSIdleCtr保存在OSIdleMax[L1.9(4)。所以OSIdleMaxOSIdleCtr所能達到的最大值。而當用戶再增加其他應用代碼時,空閒任務就不會佔用那樣多的CPU時間。OSIdleCtr不可能達到那樣多的記數,(如果擁護程式每秒重定一次OSIdleCtrCPU利用率的計算由µC/OS-II 中的OSStatTask()函數來完成,這個任務每秒執行一次。而當OSStatRdy置爲TRUE[L1.9(5)],表示µC/OS-II將統計CPU的利用率。
































程式清單 L 1.9 測試CPU速度。



void OSStatInit (void)



{



OSTimeDly(2); (1)



OS_ENTER_CRITICAL();



OSIdleCtr = 0L; (2)



OS_EXIT_CRITICAL();



OSTimeDly(OS_TICKS_PER_SEC); (3)



OS_ENTER_CRITICAL();



OSIdleCtrMax = OSIdleCtr; (4)



OSStatRdy = TRUE; (5)



OS_EXIT_CRITICAL();



}





1.07.03 TaskN()


OSStatInit()將返回到TaskStart()。現在,用戶可以建立10個同樣的任務(所有任務共用同一段代碼)。所有任務都由TaskStart()中建立,由於TaskStart()的優先順序爲0(最高),新任務建立後不進行任務調度。當所有任務都建立完成後,TaskStart()將進入無限迴圈之中,在螢幕上顯示統計資訊,並檢測是否有ESC鍵按下,如果沒有按鍵輸入,則延時一秒開始下一次迴圈;如果在這期間用戶按下了ESC鍵,TaskStart()將調用PC_DOSReturn()返回DOS系統。


程式清單L1.10給出了任務的代碼。任務一開始,調用OSSemPend()獲取信號量RandomSem [程式清單L1.10(1)](也就是禁止其他任務運行這段代碼—譯者注),然後調用Borland C/C++的庫函數random()來獲得一個亂數[程式清單L1.10(2)],此處設random()函數是不可重入的,所以10個任務將輪流獲得信號量,並調用該函數。當計算出xy座標後[程式清單L1.10(3)],任務釋放信號量。隨後任務在計算的座標處顯示其任務號(0-9,任務建立時的標識)[程式清單L1.10(4)]。最後,任務延時一個時鐘節拍[程式清單L1.10(5)],等待進入下一次迴圈。系統中每個任務每秒執行200次,10個任務每秒鐘將切換2000次。










































程式清單 L 1.10 在螢幕上顯示隨機位置顯示數位的任務。



void Task (void *data)



{



UBYTE x;



UBYTE y;



UBYTE err;









for (;;) {



OSSemPend(RandomSem, 0, &err); (1)



x = random(80); (2)



y = random(16);



OSSemPost(RandomSem); (3)



PC_DispChar(x, y + 5, *(char *)data, DISP_FGND_LIGHT_GRAY); (4)



OSTimeDly(1); (5)



}



}




arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()