蘇州永節電子科技有限公司
設為首頁 | 收藏本站
新聞詳情

嵌入式開發時間管理

瀏覽數:2
文章附圖

第5
時間管理

在3.10節時鐘節拍中曾提到,μC/OS-Ⅱ(其它內核也一樣)要求用戶提供定時中斷來實現延時與超時控制等功能。這個定時中斷叫做時鐘節拍,它應該每秒發生10至100次。時鐘節拍的實際頻率是由用戶的應用程序決定的。時鐘節拍的頻率越高,系統的負荷就越重。

3.10節討論了時鐘的中斷服務子程序和節時鐘節函數OSTimeTick——該函數用于通知μC/OS-Ⅱ發生了時鐘節拍中斷。本章主要講述五個與時鐘節拍有關的系統服務:

?OSTimeDly()

?OSTimeDlyHMSM()

?OSTimeDlyResume()

?OSTimeGet()

?OSTimeSet()

本章所提到的函數可以在OS_TIME.C文件中找到。

6.
任務延時函數OSTimeDly()

μC/OS-Ⅱ提供了這樣一個系統服務:申請該服務的任務可以延時一段時間,這段時間的長短是用時鐘節拍的數目來確定的。實現這個系統服務的函數叫做OSTimeDly()。調用該函數會使μC/OS-Ⅱ進行一次任務調度,并且執行下一個優先級最高的就緒態任務。任務調用OSTimeDly()后,一旦規定的時間期滿或者有其它的任務通過調用OSTimeDlyResume()取消了延時,它就會馬上進入就緒狀態。注意,只有當該任務在所有就緒任務中具有最高的優先級時,它才會立即運行。

程序清單 L5.1所示的是任務延時函數OSTimeDly()的代碼。用戶的應用程序是通過提供延時的時鐘節拍數——一個1 到65535之間的數,來調用該函數的。如果用戶指定0值[L5.1(1)],則表明用戶不想延時任務,函數會立即返回到調用者。非0值會使得任務延時函數OSTimeDly()將當前任務從就緒表中移除[L5.1(2)]。接著,這個延時節拍數會被保存在當前任務的OS_TCB中[L5.1(3)],并且通過OSTimeTick()每隔一個時鐘節拍就減少一個延時節拍數。最后,既然任務已經不再處于就緒狀態,任務調度程序會執行下一個優先級最高的就緒任務。


程序清單 L 5.1 OSTimeDly().

void OSTimeDly (INT16U ticks)

{

    if (ticks > 0) {                                                         (1)

        OS_ENTER_CRITICAL();

        if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {     (2)

            OSRdyGrp &= ~OSTCBCur->OSTCBBitY;

        }

        OSTCBCur->OSTCBDly = ticks;                                        (3)

        OS_EXIT_CRITICAL();

        OSSched();                                                            (4)

    }

}


清楚地認識0到一個節拍之間的延時過程是非常重要的。換句話說,如果用戶只想延時一個時鐘節拍,而實際上是在0到一個節拍之間結束延時。即使用戶的處理器的負荷不是很重,這種情況依然是存在的。圖F5.1詳細說明了整個過程。系統每隔10ms發生一次時鐘節拍中斷[F5.1(1)]。假如用戶沒有執行其它的中斷并且此時的中斷是開著的,時鐘節拍中斷服務就會發生[F5.1(2)]。也許用戶有好幾個高優先級的任務(HPT)在等待延時期滿,它們會接著執行[F5.1(3)]。接下來,圖5.1中所示的低優先級任務(LPT)會得到執行的機會,該任務在執行完后馬上調用[F5.1(4)]所示的OSTimeDly(1)。μC/OS-Ⅱ會使該任務處于休眠狀態直至下一個節拍的到來。當下一個節拍到來后,時鐘節拍中斷服務子程序會執行[F5.1(5)],但是這一次由于沒有高優先級的任務被執行,μC/OS-Ⅱ會立即執行申請延時一個時鐘節拍的任務[F5.1(6)]。正如用戶所看到的,該任務實際的延時少于一個節拍!在負荷很重的系統中,任務甚至有可能會在時鐘中斷即將發生時調用OSTimeDly(1),在這種情況下,任務幾乎就沒有得到任何延時,因為任務馬上又被重新調度了。如果用戶的應用程序至少得延時一個節拍,必須要調用OSTimeDly(2),指定延時兩個節拍!

Figure 5.1 Delay resolution.



6.
按時分秒延時函數 OSTimeDlyHMSM()

OSTimeDly()雖然是一個非常有用的函數,但用戶的應用程序需要知道延時時間對應的時鐘節拍的數目。用戶可以使用定義全局常數OS_TICKS_PER_SEC(參看OS_CFG.H)的方法將時間轉換成時鐘段,但這種方法有時顯得比較愚笨。筆者增加了OSTimeDlyHMSM()函數后,用戶就可以按小時(H)、分(M)、秒(S)和毫秒(m)來定義時間了,這樣會顯得更自然些。與OSTimeDly()一樣,調用OSTimeDlyHMSM()函數也會使μC/OS-Ⅱ進行一次任務調度,并且執行下一個優先級最高的就緒態任務。任務調用OSTimeDlyHMSM()后,一旦規定的時間期滿或者有其它的任務通過調用OSTimeDlyResume()取消了延時(參看5.02,恢復延時的任務OSTimeDlyResume()),它就會馬上處于就緒態。同樣,只有當該任務在所有就緒態任務中具有最高的優先級時,它才會立即運行。

程序清單 L5.2所示的是OSTimeDlyHMSM()的代碼。從中可以看出,應用程序是通過用小時、分、秒和毫秒指定延時來調用該函數的。在實際應用中,用戶應避免使任務延時過長的時間,因為從任務中獲得一些反饋行為(如減少計數器,清除LED等等)經常是很不錯的事。但是,如果用戶確實需要延時長時間的話,μC/OS-Ⅱ可以將任務延時長達256個小時(接近11天)。

OSTimeDlyHMSM()一開始先要檢驗用戶是否為參數定義了有效的值[L5.2(1)]。與OSTimeDly()一樣,即使用戶沒有定義延時,OSTimeDlyHMSM()也是存在的[L5.2(9)]。因為μC/OS-Ⅱ只知道節拍,所以節拍總數是從指定的時間中計算出來的[L5.2(3)]。很明顯,程序清單 L5.2中的程序并不是十分有效的。筆者只是用這種方法告訴大家一個公式,這樣用戶就可以知道怎樣計算總的節拍數了。真正有意義的只是OS_TICKS_PER_SEC。[L5.2(3)]決定了最接近需要延遲的時間的時鐘節拍總數。500/OS_TICKS_PER_SECOND的值基本上與0.5個節拍對應的毫秒數相同。例如,若將時鐘頻率(OS_TICKS_PER_SEC)設置成100Hz(10ms),4ms的延時不會產生任何延時!而5ms的延時就等于延時10ms。

μC/OS-Ⅱ支持的延時最長為65,535個節拍。要想支持更長時間的延時,如L5.2(2)所示,OSTimeDlyHMSM()確定了用戶想延時多少次超過65,535個節拍的數目[L5.2(4)]和剩下的節拍數[L5.2(5)]。例如,若OS_TICKS_PER_SEC的值為100,用戶想延時15分鐘,則OSTimeDlyHMSM()會延時15x60x100=90,000個時鐘。這個延時會被分割成兩次32,768個節拍的延時(因為用戶只能延時65,535個節拍而不是65536個節拍)和一次24,464個節拍的延時。在這種情況下,OSTimeDlyHMSM()首先考慮剩下的節拍,然后是超過65,535的節拍數[L5.2(7)和(8)](即兩個32,768個節拍延時)。


程序清單 L 5.2 OSTimeDlyHMSM().

INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli)

{

    INT32U ticks;

    INT16U loops;



    if (hours > 0 || minutes > 0 || seconds > 0 || milli > 0) {         (1)

        if (minutes > 59) {

            return (OS_TIME_INVALID_MINUTES);

        }

        if (seconds > 59) {

            return (OS_TIME_INVALID_SECONDS);

        }

        If (milli > 999) {

            return (OS_TIME_INVALID_MILLI);

        }

        ticks = (INT32U)hours    * 3600L * OS_TICKS_PER_SEC                 (2)

              + (INT32U)minutes   *   60L * OS_TICKS_PER_SEC

              + (INT32U)seconds   *         OS_TICKS_PER_SEC

              + OS_TICKS_PER_SEC * ((INT32U)milli

              + 500L/OS_TICKS_PER_SEC) / 1000L; (3)

        loops = ticks / 65536L;                                     (4)

        ticks = ticks % 65536L;                                          (5)

        OSTimeDly(ticks);                                                (6)

        while (loops > 0) {                                              (7)

            OSTimeDly(32768);                                           (8)

            OSTimeDly(32768);

            loops--;

        }

        return (OS_NO_ERR);

    } else {

        return (OS_TIME_ZERO_DLY);                                        (9)

    }

}


由于OSTimeDlyHMSM()的具體實現方法,用戶不能結束延時調用OSTimeDlyHMSM()要求延時超過65535個節拍的任務。換句話說,如果時鐘節拍的頻率是100Hz,用戶不能讓調用OSTimeDlyHMSM(0,10,55,350)或更長延遲時間的任務結束延時。

6.
讓處在延時期的任務結束延時OSTimeDlyResume()

μC/OS-Ⅱ允許用戶結束延時正處于延時期的任務。延時的任務可以不等待延時期滿,而是通過其它任務取消延時來使自己處于就緒態。這可以通過調用OSTimeDlyResume()和指定要恢復的任務的優先級來完成。實際上,OSTimeDlyResume()也可以喚醒正在等待事件(參看第六章——任務間的通訊和同步)的任務,雖然這一點并沒有提到過。在這種情況下,等待事件發生的任務會考慮是否終止等待事件。

OSTimeDlyResume()的代碼如程序清單 L5.3所示,它首先要確保指定的任務優先級有效 [L5.3(1)]。接著,OSTimeDlyResume()要確認要結束延時的任務是確實存在的[L5.3(2)]。如果任務存在,OSTimeDlyResume()會檢驗任務是否在等待延時期滿[L5.3(3)]。只要OS_TCB域中的OSTCBDly包含非0值就表明任務正在等待延時期滿,因為任務調用了OSTimeDly(),OSTimeDlyHMSM()或其它在第六章中所描述的PEND函數。然后延時就可以通過強制命令OSTCBDly為0來取消[L5.3(4)]。延時的任務有可能已被掛起了,這樣的話,任務只有在沒有被掛起的情況下才能處于就緒狀態[L5.3(5)]。當上面的條件都滿足后,任務就會被放在就緒表中[L5.3(6)]。這時,OSTimeDlyResume()會調用任務調度程序來看被恢復的任務是否擁有比當前任務更高的優先級[L5.3(7)]。這會導致任務的切換。



程序清單 L 5.3 恢復正在延時的任務

INT8U OSTimeDlyResume (INT8U prio)

{

    OS_TCB *ptcb;


    if (prio >= OS_LOWEST_PRIO) {                                      (1)

        return (OS_PRIO_INVALID);

    }

    OS_ENTER_CRITICAL();

    ptcb = (OS_TCB *)OSTCBPrioTbl[prio];

    if (ptcb != (OS_TCB *)0) {                                         (2)

        if (ptcb->OSTCBDly != 0) {                                     (3)

            ptcb->OSTCBDly   = 0;                                       (4)

            if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) {                (5)

                OSRdyGrp               |= ptcb->OSTCBBitY;             (6)

                OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

                OS_EXIT_CRITICAL();

                OSSched();                                             (7)

            } else {

                OS_EXIT_CRITICAL();

            }

            return (OS_NO_ERR);

        } else {

            OS_EXIT_CRITICAL();

            return (OS_TIME_NOT_DLY);

        }

    } else {

        OS_EXIT_CRITICAL();

        return (OS_TASK_NOT_EXIST);

    }

}


注意,用戶的任務有可能是通過暫時等待信號量、郵箱或消息隊列來延時自己的(參看第六章)。可以簡單地通過控制信號量、郵箱或消息隊列來恢復這樣的任務。這種情況存在的唯一問題是它要求用戶分配事件控制塊(參看6.00),因此用戶的應用程序會多占用一些RAM。

6.
系統時間,OSTimeGet()OSTimeSet()

無論時鐘節拍何時發生,μC/OS-Ⅱ都會將一個32位的計數器加1。這個計數器在用戶調用OSStart()初始化多任務和4,294,967,295個節拍執行完一遍的時候從0開始計數。在時鐘節拍的頻率等于100Hz的時候,這個32位的計數器每隔497天就重新開始計數。用戶可以通過調用OSTimeGet()來獲得該計數器的當前值。也可以通過調用OSTimeSet()來改變該計數器的值。OSTimeGet()和OSTimeSet()兩個函數的代碼如程序清單 L5.4所示。注意,在訪問OSTime的時候中斷是關掉的。這是因為在大多數8位處理器上增加和拷貝一個32位的數都需要數條指令,這些指令一般都需要一次執行完畢,而不能被中斷等因素打斷。


程序清單 L 5.4 得到和改變系統時間

INT32U OSTimeGet (void)

{

    INT32U ticks;


    OS_ENTER_CRITICAL();

    ticks = OSTime;

    OS_EXIT_CRITICAL();

    return (ticks);

}


void OSTimeSet (INT32U ticks)

{

    OS_ENTER_CRITICAL();

    OSTime = ticks;

    OS_EXIT_CRITICAL();

}