uC/OS-II學習筆記(1)
by WC 7.9.2015
本文假設讀者對uC/OS-II和多工知之甚少,但有一定的C語言和微控制器基礎。為的是為了從0開始學習uC/OS-II。流程圖如下圖示:
1## 範例1 ##
範例一演示uC/OC-II的多工處理能力。共有10個任務在螢幕上面隨機的位置顯示一個0~9的數字。每個任務只顯示同一個數字。其包含了13個任務,在執行視窗的左下角增加了兩個內部任務。注意:context switch指CPU暫存器內容的切換,其實就是任務切換。
LI.1 TEXT.C
#include "includes.h" 【1】
#define TASK_STK_SIZE 512 /* Size of each task's stacks (# of WORDs) */ 【2】
#define N_TASKS 10 /* Number of identical tasks */ 【3】
OS_STK TaskStk[N_TASKS][TASK_STK_SIZE]; /* Tasks stacks */ 【4】
OS_STK TaskStartStk[TASK_STK_SIZE]; 【5】
char TaskData[N_TASKS]; /* Parameters to pass to each task */ 【6】
OS_EVENT *RandomSem;
說明:在程式的最後加了索引【?】,L1.1【1】表示程式L.1.1標號1地方的程式碼.
L1.1【1】:所有的標頭檔案都放在了主控標頭檔案includes.h中,這樣寫結構簡單。後面需要時會繼續講解LI.1中的內容,先看main函式。
L1.1【2】:任務堆疊長度。
L1.1【3】:該程式任務的個數。
L1.1【4】:堆疊空間。
L1.1【5】:棧頂。
L1.1【6】:定義一個數組,存放0~9的ASCII字元。
LI.2 TEXT.C,main()
void main (void)
{
PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); /* Clear the screen */ 【1】
OSInit(); /* Initialize uC/OS-II */ 【2】
PC_DOSSaveReturn(); /* Save environment to return to DOS*/ 【3】
PC_VectSet(uCOS, OSCtxSw); /* Install uC/OS-II's context switch vector*/ 【4】
RandomSem = OSSemCreate(1); /* Random number semaphore */ 【5】
OSTaskCreate(TaskStart, (void *)0, &TaskStartStk[TASK_STK_SIZE - 1], 0); 【6】
OSStart(); /* Start multitasking */ 【7】
}
L1.2【1】:清空螢幕,PC_DispClrScr(INT8U colur),也可以通過定義DISP_FGND_X來指定任意一種顏色.
L1.2【2】:在使用uC/OS-II提供的功能之前,必須呼叫OSInint()函式。該函式建立了兩個任務,空閒任務——在其他任 務未就緒時執行;統計任務——計算CPU的利用率。
L1.2【3】:PC_DOSSaveReturn()允許程式開始多工前,儲存重要暫存器的值,以保證uC/OS-II能夠正常返回沒有執行之前的DOS環境。必須在設定uC/OS-II的環境切換中斷向量之前呼叫。
L1.2【4】:PC_VectSet( INT8U vect,void *(pisr)(void) )用於設定中斷向量表的內容,vect:中斷向量值,介於0~255的數值。pisr:中斷/異常處理程式地址。eg:PC_VectSet(64,InterruptHandler)
L1.2【5】:呼叫OSSemCreate(INT16U cnt)函式建立一個訊號量,用於保護產生的隨機數。cnt為訊號量的初始值,當cnt=0,該訊號量表示等待一個事件或者多個事件的發生。當cnt=1時,兩個任務Task1和Task2對一個公共資源進 行互斥訪問。該程式中,訊號量初值設定為1.
L1.2【6】:建立了一個叫做TaskStart的任務。這個任務有4個引數:第1個引數是指向該任務程式碼的指標,在這裡指的是 TaskStart(),第2個引數是一個指向任務初始化資料的指標,這裡不需要任何初始化資料,所以傳遞一個空(NULL)指標,第3個引數是任務的堆疊棧頂TOS(Top-of-Stack),在這個例子中,堆疊空間被定義為TaskSt- artStk。第4個引數為優先順序,0為最高優先順序。
L1.2【7】:呼叫OSStart()函式,將控制權給uC/OS-II核心,開始執行多工。在啟動OSStart()函式之前,至少要先建立一個任務。OSStart()函式將判斷哪一個任務的優先順序最高,最先執行優先順序最高的任務。
LI.3 TEXT.C,TaskStart()
void TaskStart (void *pdata)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */ 【0】
OS_CPU_SR cpu_sr;
#endif
char s[100];
INT16S key;
pdata = pdata; /* Prevent compiler warning */ 【1】
TaskStartDispInit(); /* Initialize the display */ 【2】
OS_ENTER_CRITICAL(); 【3】
PC_VectSet(0x08, OSTickISR); /* Install uC/OS-II's clock tick ISR */ 【4】
PC_SetTickRate(OS_TICKS_PER_SEC); /* Reprogram tick rate */ 【5】
OS_EXIT_CRITICAL(); 【6】
OSStatInit(); /* Initialize uC/OS-II's statistics */ 【7】
TaskStartCreateTasks(); /* Create all the application tasks */ 【8】
for (;;) 【9】
{
TaskStartDisp(); /* Update the display */ 【10】
if (PC_GetKey(&key) == TRUE) /* See if key has been pressed */ 【11】
{
if (key == 0x1B) /* Yes, see if it's the ESCAPE key */ 【12】
{
PC_DOSReturn(); /* Return to DOS */ 【13】
}
}
OSCtxSwCtr = 0; /* Clear context switch counter */ 【14】
OSTimeDlyHMSM(0, 0, 1, 0); /* Wait one second */ 【15】
}
}
L1.3【1】:假裝使用了pdata,以避免某些編譯器的警告,該程式沒有實際意思。並且pdata是當任務建立傳遞過來的一個指標,這個例子指向空指標。
L1.3【2】:初始化螢幕顯示。
L1.3【3】:關中斷。uC/OS-II需要先關中斷,在放置程式碼段,在重新開中斷。在這裡解釋一個名詞:臨界段,就是不可被中斷的程式碼段,例如常見的入棧出棧等操作就不能被中斷。uC/OS-II是一個實時核心,需要關閉中斷進入和開中斷退出臨界段。
L1.3【4】:設定中斷向量
L1.3【5】:PC_SetTickRate(INT16U freq)將PC的時鐘節拍從標準值18.20648改變為freq,OS_TICKS_PER_SEC為200
L1.3【6】:開中斷,必須和關中斷成對使用。
L1.3【7】:計算處理器在執行所用任務時實際CPU使用率。
L1.3【8】:來建立更多的任務,在這裡即N_TASKS個任務,先分析TaskStartCreateTasks()的程式碼,如下所示。
LI.4 TEST.C,TaskStartCreateTasks()
/*
CREATE TASKS
*/
static void TaskStartCreateTasks (void)
{
INT8U i;
for (i = 0; i < N_TASKS; i++) /* Create N_TASKS identical tasks */ 【1】
{
TaskData[i] = '0' + i; /* Each task will display its own letter */ 【2】
OSTaskCreate(Task, (void *)&TaskData[i], &TaskStk[i][TASK_STK_SIZE - 1], i + 1); 【3】
}
}
L1.4【1】:迴圈建立了N_TASKS(即10)個相同的任務Task()。
L1.4【2】:初始化一個數組,包含0~9的ASCII字元 [見L1.1【5】]。
L1.4【3】:建立了一個叫做Task的任務。這個任務有4個引數:第1個引數是指向該任務程式碼的指標,在這裡指的是 Task(),第2個引數是一個指向任務初始化資料的指標,第3個引數是任務的堆疊棧頂TOS(Top-of-Stack),在這個例子中棧頂為TaskStk[N_TASKS][TASK_STK_SIZE] [見L1.1【5】]。第4個引數為優先順序,因為優先順序0已經被任務TaskStart()佔用了,新建立的任務將使用1~10的優先順序。
注:在每個任務建立的時候,uC/OS-II判斷新建立的任務是否比建立它們的任務優先順序高。如果高,則這個新建立的任務將立即開始執行。這這個範例中,建立新任務的TaskStart()任務具有最高的優先順序0,所以這些新建立的任務暫時不能執行。
繼續分析L1.3
L1.3【9】:每一個任務都是無限的迴圈。
L1.3【10】:在DOS視窗的底部,顯示建立任務的個數、當前CPU利用率、任務切換次數等相關資訊。
L1.3【11】:檢測是否有按鍵按下。
L1.3【13】:判斷是否按下Esc鍵,如果按下,則返回DOS環境。
L1.3【14】:如果沒有按下,記錄任務切換次數的全域性變數OSCtxSwCtr被清零,以便更新為下一個1S內發生的任務切換次數。
L1.3【15】:通過呼叫OSTimeDlyHMSM(),TaskStrat()任務將自身掛起1S。HMSM代表時(hour),分(miunte),秒(second)及毫秒(millisecond),這是傳遞給OSTimeDlyHMSM()的引數。當TaskStrat()任務將自身掛起1S,uC/OC-II開始排程,找到下一個最高優先順序的就緒任務,在本例中就是優先順序為1的Task()任務。注意:若沒有OSTimeDlyHMSM()或其他類似的函式,TaskStrat()將是一個真正的無限迴圈,其他任務沒有執行的機會。
LI.5 TEST.C,Task()
void Task (void *pdata)
{
INT8U x;
INT8U y;
INT8U err;
for (;;) 【1】
{
OSSemPend(RandomSem, 0, &err); /* Acquire semaphore to perform random numbers */ 【2】
x = random(80); /* Find X position where task number will appear */ 【3】
y = random(16); /* Find Y position where task number will appear */ 【4】
OSSemPost(RandomSem); /* Release semaphore */ 【5】
/* Display the task number on the screen */
PC_DispChar(x, y + 5, *(char *)pdata, DISP_FGND_BLACK + DISP_BGND_LIGHT_GRAY); 【6】
OSTimeDly(1); /* Delay 1 clock tick */ 【7】
}
}
L1.5【1】:每一個任務是一個無限迴圈。
L1.5【2】:Task()任務首先檢查RandomSem訊號量,這個訊號量用來防止多個任務同時訪問產生隨機數的函式。OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err),等待一個訊號量函式(訊號量指標、允許等待的時鐘節拍、程式碼錯誤指標)。pevent:指向事件控制塊結合目標訊號量的指標;timeout:定時超時選項(以時鐘節拍為單位),如果這個值為0,代表任務將無限期地等待訊號量。在此程式中,訊號量在建立時被初始化為1,並且沒有其他的任務使用該訊號量,所有Task()任務立刻得到該訊號量並繼續執行。如果該訊號量被其他任務佔用,uC/OS-II將掛起這個任務並開始排程,找到下一個就緒任務中優先順序最高的;err:指向錯誤程式碼的訊息指標。
L1.5【3】:產生一個0~79(含79)的數值。這個值用來確定在螢幕上面顯示字元的X方向的位置。
L1.5【4】:產生一個0~75(含75)的數值。這個值用來確定在螢幕上面顯示字元的Y方向的位置。
L1.5【5】:釋放訊號量。
L1.5【6】:PC_DispChar(INT8U x,INT8U y,INT8U c,INT8U color)用來表示在(x,y)座標處顯示色彩組合為color的字元c,為了避免影響底部的其他資訊,y加了5行的偏移量。
L1.5【7】:通過呼叫OSTimeDly(),通知uC/OS-II該任務本次執行已經結束,可以讓下一個最高優先順序的就緒任務執行。引數1表示延時一個時鐘節拍,在200Hz情況下就是5ms。
注:本人這個系列的學習筆記參考於《嵌入式實時作業系統uC/OS-II》,邵貝貝譯。