1. 程式人生 > >uC/OS-II學習筆記(1)

uC/OS-II學習筆記(1)

                                                                               by WC 7.9.2015

本文假設讀者對uC/OS-II和多工知之甚少,但有一定的C語言和微控制器基礎。為的是為了從0開始學習uC/OS-II。流程圖如下圖示:
這裡寫圖片描述

1## 範例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      */8for (;;)                                                                                 【9】
     {   
        TaskStartDisp();                   /* Update the display                   */10if (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》,邵貝貝譯。