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.07例1
第一個範例可以在\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();每個任務在螢幕上隨機的位置顯示一個0到9的數位。
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_ExitFlag爲FALSE[L1.6(1)],說明用戶不是要返回DOS,然後初始化OSTickDOSCtr爲1[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_ExitFlag爲TRUE,並且執行longjmp()語句[L1.7(2)],這時處理器將跳回 PC_DOSSaveReturn()[在調用 setjmp()之後] [L1.6(5)],此時PC_ExitFlag爲TRUE,故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),因爲它未被DOS和BIOS使用。
這裏用了一個信號量來保護Borland C/C++庫中的産生亂數的函數[L1.5(5)],之所以使用信號量保護一下,是因爲筆者不知道這個函數是否具備可重入性,筆者假設其不具備,初始化將信號量設置爲1,意思是在某一時刻只有一個任務可以調用亂數産生函數。
在開始多工之前,筆者建立了一個叫做TaskStart()的任務[L1.5(6)],在啓動多工OSStart()之前用戶至少要先建立一個任務,這一點非常重要[L1.5(7)]。不這樣做用戶的應用程式將會崩潰。實際上,如果用戶要計算CPU的利用率時,也需要先建立一個任務。µCOS-II的統計任務要求在整個一秒鐘內沒有任何其他任務運行。如果用戶在啓動多工之前要建立其他任務,必須保證用戶的任務代碼監控總體變數OSStatRdy和延時程式 [即調用 OSTimeDly()]的執行,直到這個變數變成TRUE。這表明µC/OS-II的CPU利用率統計函數已經採集到了資料。
1.07.02 TaskStart()
例1中的主要工作由TaskStart()來完成。TaskStart()函數的示意代碼如程式清單 L 1.8所示。TaskStart()首先在螢幕頂端顯示一個標識,說明這是例1 [L1.8(1)]。然後關中斷,以改變中斷向量,讓其指向µC/OS-II的時鐘節拍處理,而後,改變時鐘節拍率,從DOS的 18.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)。所以OSIdleMax是OSIdleCtr所能達到的最大值。而當用戶再增加其他應用代碼時,空閒任務就不會佔用那樣多的CPU時間。OSIdleCtr不可能達到那樣多的記數,(如果擁護程式每秒重定一次OSIdleCtr)CPU利用率的計算由µ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個任務將輪流獲得信號量,並調用該函數。當計算出x和y座標後[程式清單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) |
} |
} |
留言列表