13、軟件定時器


1、軟件定時器的基本概念

  定時器,是指從指定的時刻開始,經過一個指定時間,然后觸發一個超時事件,用戶可以自定義定時器的周期與頻率。

  定時器有硬件定時器和軟件定時器之分:

  硬件定時器是芯片本身提供的定時功能。一般是由外部晶振提供給芯片輸入時鍾,芯片向軟件模塊提供一組配置寄存器,接受控制輸入,到達設定時間值后芯片中斷控制器產生時鍾中斷。硬件定時器的精度一般很高,可以達到納秒級別,並且是中斷觸發方式。

  軟件定時器,軟件定時器是由操作系統提供的一類系統接口,它構建在硬件定時器基礎之上,使系統能夠提供不受硬件定時器資源限制的定時器服務,它實現的功能與硬件定時器也是類似的。

  使用硬件定時器時,每次在定時時間到達之后就會自動觸發一個中斷,用戶在中斷中處理信息;而使用軟件定時器時,需要我們在創建軟件定時器的時候指定時間到達后要去調用的函數(也稱超時函數/回調函數,為了統一,下文均用回調函數描述),在回調函數中處理信息。

  注意:軟件定時器回調函數的上下文是任務, 下文所說的定時器均為軟件定時器。

  軟件定時器在被創建之后,當經過設定的時鍾計數值后會觸發用戶定義的回調函數。

  定時精度與系統時鍾的周期有關。一般系統利用 SysTick 作為軟件定時器的基礎時鍾, 軟件定時器的回調函數類似硬件的中斷服務函數,所以, 回調函數也要快進快出,而且回調函數中不能有任何阻塞任務運行的情況(軟件定時器回調函數的上下文環境是任務) ,比如 vTaskDelay()以及其它能阻塞任務運行的函數 ,兩次觸發回調函數的時間間隔xTimerPeriodInTicks 叫定時器的定時周期。

  FreeRTOS 操作系統提供軟件定時器功能,軟件定時器的使用相當於擴展了定時器的數量,允許創建更多的定時業務。 FreeRTOS 軟件定時器功能上支持:

  • 裁剪:能通過宏關閉軟件定時器功能。
  • 軟件定時器創建。
  • 軟件定時器啟動。
  • 軟件定時器停止。
  • 軟件定時器復位。
  • 軟件定時器刪除。

  FreeRTOS 提供的軟件定時器支持單次模式和周期模式, 單次模式和周期模式的定時時間到之后都會調用軟件定時器的回調函數,用戶可以在回調函數中加入要執行的工程代碼。

  單次模式:當用戶創建了定時器並啟動了定時器后,定時時間到了,只執行一次回調函數之后就將該定時器刪除,不再重新執行。

  周期模式:這個定時器會按照設置的定時時間循環執行回調函數,直到用戶將定時器刪除,具體見下圖:

  FreeRTOS 通過一個 prvTimerTask 任務(也叫守護任務 Daemon)管理軟定時器,它是在啟動調度器時自動創建的, 為了滿足用戶定時需求。 prvTimerTask 任務會在其執行期間檢查用戶啟動的時間周期溢出的定時器,並調用其回調函數。只有設置 FreeRTOSConfig.h中的宏定義 configUSE_TIMERS 設置為 1 ,將相關代碼編譯進來,才能正常使用軟件定時器相關功能。

2、 軟件定時器應用場景

  在很多應用中,我們需要一些定時器任務,硬件定時器受硬件的限制,數量上不足以滿足用戶的實際需求,無法提供更多的定時器,那么可以采用軟件定時器來完成,由軟件定時器代替硬件定時器任務。

  但需要注意的是軟件定時器的精度是無法和硬件定時器相比的,因為在軟件定時器的定時過程中是極有可能被其它中斷所打斷,因為軟件定時器的執行上下文環境是任務。所以,軟件定時器更適用於對時間精度要求不高的任務,一些輔助型的任務。

3、 軟件定時器的精度

  在操作系統中,通常軟件定時器以系統節拍周期為計時單位。系統節拍是系統的心跳節拍,表示系統時鍾的頻率,就類似人的心跳, 1s 能跳動多少下,系統節拍配置為configTICK_RATE_HZ,該宏在 FreeRTOSConfig.h 中有定義,默認是 1000。那么系統的時鍾節拍周期就為 1ms(1s 跳動 1000 下,每一下就為 1ms)。軟件定時器的所定時數值必須是這個節拍周期的整數倍,例如節拍周期是 10ms,那么上層軟件定時器定時數值只能是10ms, 20ms, 100ms 等,而不能取值為 15ms。由於節拍定義了系統中定時器能夠分辨的精確度,系統可以根據實際系統 CPU 的處理能力和實時性需求設置合適的數值,系統節拍周期的值越小,精度越高,但是系統開銷也將越大,因為這代表在 1 秒中系統進入時鍾中斷的次數也就越多。

4、 軟件定時器的運作機制

  軟件定時器是可選的系統資源,在創建定時器的時候會分配一塊內存空間。當用戶創建並啟動一個軟件定時器時, FreeRTOS 會根據當前系統時間及用戶設置的定時確定該定時器喚醒時間,並將該定時器控制塊掛入軟件定時器列表, FreeRTOS 中采用兩個定時器列表維護軟件定時器, pxCurrentTimerList 與 pxOverflowTimerList 是列表指針, 在初始化的時候分別指向 xActiveTimerList1 與 xActiveTimerList2,具體見下面代碼清單。

//軟件定時器用到的列表 
PRIVILEGED_DATA static List_t xActiveTimerList1;
PRIVILEGED_DATA static List_t xActiveTimerList2;
PRIVILEGED_DATA static List_t *pxCurrentTimerList;
PRIVILEGED_DATA static List_t *pxOverflowTimerList;

  pxCurrentTimerList: 系統新創建並激活的定時器都會以超時時間升序的方式插入到pxCurrentTimerList 列表中。系統在定時器任務中掃描 pxCurrentTimerList 中的第一個定時器,看是否已超時,若已經超時了則調用軟件定時器回調函數。否則將定時器任務掛起,因為定時時間是升序插入軟件定時器列表的,列表中第一個定時器的定時時間都還沒到的話,那后面的定時器定時時間自然沒到。

  pxOverflowTimerList 列表是在軟件定時器溢出的時候使用, 作用與 pxCurrentTimerList一致。

  同時, FreeRTOS 的軟件定時器還有采用消息隊列進行通信, 利用“定時器命令隊列”向軟件定時器任務發送一些命令,任務在接收到命令就會去處理命令對應的程序,比如啟動定時器,停止定時器等。假如定時器任務處於阻塞狀態,我們又需要馬上再添加一個軟件定時器的話,就是采用這種消息隊列命令的方式進行添加,才能喚醒處於等待狀態的定時器任務,並且在任務中將新添加的軟件定時器添加到軟件定時器列表中,所以,在定時器啟動函數中, FreeRTOS 是采用隊列的方式發送一個消息給軟件定時器任務,任務被喚醒從而執行接收到的命令。

  例如:系統當前時間 xTimeNow 值為 0,注意: xTimeNow 其實是一個局部變量, 是根據 xTaskGetTickCount()函數獲取的,實際它的值就是全局變量 xTickCount 的值,下文都采用它表示當前系統時間。 在當前系統中已經創建並啟動了 1 個定時器 Timer1;系統繼續運行,當系統的時間 xTimeNow 為 20 的時候,用戶創建並且啟動一個定時時間為 100 的定時器 Timer2 , 此時 Timer2 的溢出時 間 xTicksToWait 就為定 時時間 +系統當前時間(100+20=120),然后將 Timer2 按 xTicksToWait 升序插入軟件定時器列表中;假設當前系統時間 xTimeNow 為 40 的時候,用戶創建並且啟動了一個定時時間為 50 的定時器Timer3 ,那么此時Timer3 的 溢 出 時 間 xTicksToWait 就 為 40+50=90 , 同 樣 安 裝xTicksToWait 的數值升序插入軟件定時器列表中,在定時器鏈表中插入過程具體見下圖。同理創建並且啟動在已有的兩個定時器中間的定時器也是一樣的,具體見下圖:

 

  那么系統如何處理軟件定時器列表?系統在不斷運行,而 xTimeNow( xTickCount)隨着 SysTick 的觸發一直在增長(每一次硬件定時器中斷來臨, xTimeNow 變量會加 1),在軟件定時器任務運行的時候會獲取下一個要喚醒的定時器,比較當前系統時間xTimeNow 是否大於或等於下一個定時器喚醒時間 xTicksToWait,若大於則表示已經超時,定時器任務將會調用對應定時器的回調函數,否則將軟件定時器任務掛起,直至下一個要喚醒的軟件定時器時間到來或者接收到命令消息。以上圖為例,講解軟件定時器調用回調函數的過程,在創建定 Timer1 並且啟動后,假如系統經過了 50 個 tick, xTimeNow 從 0增長到 50,與 Timer1 的 xTicksToWait 值相等, 這時會觸發與 Timer1 對應的回調函數,從而轉到回調函數中執行用戶代碼,同時將 Timer1 從軟件定時器列表刪除,如果軟件定時器是周期性的,那么系統會根據 Timer1 下一次喚醒時間重新將 Timer1 添加到軟件定時器列表中,按照 xTicksToWait 的升序進行排列。同理,在 xTimeNow=40 的時候創建的 Timer3,在經過 130 個 tick 后(此時系統時間 xTimeNow 是 40, 130 個 tick 就是系統時間xTimeNow 為 170 的時候),與 Timer3 定時器對應的回調函數會被觸發,接着將 Timer3 從軟件定時器列表中刪除,如果是周期性的定時器,還會按照 xTicksToWait 升序重新添加到軟件定時器列表中。

  使用軟件定時器時候要注意以下幾點:

  • 軟件定時器的回調函數中應快進快出,絕對不允許使用任何可能引軟件定時器起任務掛起或者阻塞的 API 接口,在回調函數中也絕對不允許出現死循環。
  • 軟件定時器使用了系統的一個隊列和一個任務資源,軟件定時器任務的優先級默認為 configTIMER_TASK_PRIORITY,為了更好響應,該優先級應設置為所有任務中最高的優先級。
  • 創建單次軟件定時器,該定時器超時執行完回調函數后,系統會自動刪除該軟件定時器,並回收資源。
  • 定時器任務的堆棧大小默認為 configTIMER_TASK_STACK_DEPTH 個字節。

5、 軟件定時器控制塊

  軟件定時器雖然不屬於內核資源,但是也是 FreeRTOS 核心組成部分,是一個可以裁剪的功能模塊,同樣在系統中由一個控制塊管理其相關信息,具體見下面代碼清單。

//軟件定時器控制塊
typedef struct tmrTimerControl 
{
  const char *pcTimerName; (1)
  ListItem_t xTimerListItem; (2)
  TickType_t xTimerPeriodInTicks; (3)
  UBaseType_t uxAutoReload; (4)
  void *pvTimerID; (5)
  TimerCallbackFunction_t pxCallbackFunction; (6)

  #if( configUSE_TRACE_FACILITY == 1 )
      UBaseType_t uxTimerNumber;
  #endif

  #if( ( configSUPPORT_STATIC_ALLOCATION == 1 )&& ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
    uint8_t ucStaticallyAllocated; (7) 
  #endif 
} xTIMER;

typedef xTIMER Timer_t;
代碼清單 21-2(1):軟件定時器名字,這個名字一般用於調試的, RTOS 使用定時器是通過其句柄,並不是使用其名字。
代碼清單 21-2(2):軟件定時器列表項,用於插入定時器列表。
代碼清單 21-2(3): 軟件定時器的周期, 單位為系統節拍周期 (即 tick) ,pdMS_TO_TICKS()可以把時間單位從 ms 轉換為系統節拍周期。
代碼清單 21-2(4): 軟件定時器是否自動重置, 如果該值為 pdFalse,那么創建的軟件定時器工作模式是單次模式,否則為周期模式。
代碼清單 21-2(5): 軟件定時器 ID, 數字形式。 該 ID 典型的用法是當一個回調函數分配給一個或者多個軟件定時器時,在回調函數里面根據 ID 號來處理不同的軟件定時器。
代碼清單 21-2(6): 軟件定時器的回調函數, 當定時時間到達的時候就會調用這個函數。
代碼清單 21-2(7): 標記定時器使用的內存,刪除時判斷是否需要釋放內存。

6、 軟件定時器函數接口講解

  軟件定時器的功能是在定時器任務(或者叫定時器守護任務) 中實現的。 軟件定時器的很多 API 函數通過一個名字叫“定時器命令隊列” 的隊列來給定時器守護任務發送命令。該定時器命令隊列由 RTOS 內核提供,且應用程序不能夠直接訪問, 其消息隊列的長度由宏 configTIMER_QUEUE_LENGTH 定義,下面就講解一些常用的軟件定時器函數接口。

  6.1 軟件定時器創建函數 xTimerCreate()

  軟件定時器與 FreeRTOS 內核其他資源一樣,需要創建才允許使用的, FreeRTOS 為我們提供了兩種創建方式,一種是動態創建軟件定時器 xTimerCreate(), 另一種是靜態創建方式 xTimerCreateStatic(),因為創建過程基本差不多,所以在這里我們只講解動態創建方式。

  xTimerCreate()用於創建一個軟件定時器,並返回一個句柄。 要想使用該函數函數必須在 頭 文 件 FreeRTOSConfig.h 中 把 宏 configUSE_TIMERS 和configSUPPORT_DYNAMIC_ALLOCATION 均 定 義 為 1(configSUPPORT_DYNAMIC_ALLOCATION 在 FreeRTOS.h 中默認定義為 1), 並且需要把 FreeRTOS/source/times.c 這個 C 文件添加到工程中。

  每一個軟件定時器只需要很少的 RAM 空間來保存其的狀態。如果使用函數xTimeCreate()來創建一個軟件定時器,那么需要的 RAM 是動態分配的。 如果使用函數xTimeCreateStatic()來創建一個事件組,那么需要的 RAM 是靜態分配的軟件定時器在創建成功后是處於休眠狀態的, 可以使用 xTimerStart()、 xTimerReset()、xTimerStartFromISR() 、 xTimerResetFromISR() 、 xTimerChangePeriod() 和xTimerChangePeriodFromISR()這些函數將其狀態轉換為活躍態。

//xTimerCreate()源碼

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

TimerHandle_t xTimerCreate(const char * const pcTimerName, (1)
const TickType_t xTimerPeriodInTicks, (2)
const UBaseType_t uxAutoReload, (3)
void * const pvTimerID, (4)
TimerCallbackFunction_t pxCallbackFunction ) (5)
{
    Timer_t *pxNewTimer;

    /* 為這個軟件定時器申請一塊內存 */
    pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) ); (6)

    if ( pxNewTimer != NULL ) 
    {
        /* 內存申請成功,進行初始化軟件定時器 */
        prvInitialiseNewTimer( pcTimerName,
        xTimerPeriodInTicks,
        uxAutoReload,
        pvTimerID,
        pxCallbackFunction,
        pxNewTimer ); (7)

        #if( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            pxNewTimer->ucStaticallyAllocated = pdFALSE;
        }
        #endif
    }

    return pxNewTimer;
}
代碼清單 21-3(1):軟件定時器名字, 文本形式,純粹是為了調試, FreeRTOS 使用定時器是通過其句柄, 而不是使用其名字。
代碼清單 21-3(2):軟件定時器的周期,單位為系統節拍周期(即 tick)。 使用pdMS_TO_TICKS()可以把時間單位從 ms 轉換為系統節拍周期。 如果軟件定時器的周期為100 個 tick, 那么只需要簡單的設置 xTimerPeriod 的值為 100 即可。如果軟件定時器的周期為 500ms, 那么 xTimerPeriod 應設置為 pdMS_TO_TICKS(500)。 宏 pdMS_TO_TICKS()只有當 configTICK_RATE_HZ 配置成小於或者等於 1000HZ 時才可以使用。
代碼清單 21-3(3): 如果 uxAutoReload 設置為 pdTRUE, 那么軟件定時器的工作模式就 是 周 期 模 式 , 一 直 會 以 用 戶 指 定 的 xTimerPeriod 周 期 去 執 行 回 調 函 數 。 如 果uxAutoReload 設置為 pdFALSE, 那么軟件定時器就在用戶指定的 xTimerPeriod 周期下運行一次后就進入休眠態。
代碼清單 21-3(4): 軟件定時器 ID, 數字形式。 該 ID 典型的用法是當一個回調函數分配給一個或者多個軟件定時器時,在回調函數里面根據 ID 號來處理不同的軟件定時器。
代碼清單 21-3(5): 軟件定時器的回調函數, 當定時時間到達的時候就會調用這個函數, 該函數需要用戶自己實現。
代碼清單 21-3(6): 為這個軟件定時器申請一塊內存,大小為軟件定時器控制塊大小,用於保存該定時器的基本信息。
代碼清單 21-3(7): 調用 prvInitialiseNewTimer()函數初始化一個新的軟件定時器,該函數的源碼具體見代碼清單 21-4(3): 。

 

//prvInitialiseNewTimer()源碼
static void prvInitialiseNewTimer(const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
Timer_t *pxNewTimer )
{
    /* 斷言,判斷定時器的周期是否大於 0 */
    configASSERT( ( xTimerPeriodInTicks > 0 ) ); (1)

    if ( pxNewTimer != NULL ) 
    {
        /* 初始化軟件定時器列表與創建軟件定時器消息隊列 */
        prvCheckForValidListAndQueue(); (2)

        /* 初始化軟件定時信息,這些信息保存在軟件定時器控制塊中 */ (3)
        pxNewTimer->pcTimerName = pcTimerName;
        pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
        pxNewTimer->uxAutoReload = uxAutoReload;
        pxNewTimer->pvTimerID = pvTimerID;
        pxNewTimer->pxCallbackFunction = pxCallbackFunction;
        vListInitialiseItem( &( pxNewTimer->xTimerListItem ) ); (4)
        traceTIMER_CREATE( pxNewTimer );
    }
}
代碼清單 21-4(1): 斷言,判斷軟件定時器的周期是否大於 0,否則的話其他任務是沒辦法執行的,因為系統會一直執行軟件定時器回調函數。
代碼清單 21-4(2):在 prvCheckForValidListAndQueue()函數中系統將初始化軟件定時器列表與創建軟件定時器消息隊列,也叫“定時器命令隊列” ,因為在使用軟件定時器的時候,用戶是無法直接控制軟件定時器的,必須通過“ 定時器命令隊列”向軟件定時器發送一個命令,軟件定時器任務被喚醒就去執行對應的命令操作。
代碼清單 21-4(3): 初始化軟件定時基本信息,如定時器名稱、回調周期、 定時器 ID與定時器回調函數等,這些信息保存在軟件定時器控制塊中, 在操作軟件定時器的時候,就需要用到這些信息。
代碼清單 21-4(4): 初始化定時器列表項。
軟件定時器的創建很簡單,需要用戶根據自己需求指定相關信息即可,下面來看看xTimerCreate()函數使用實例,具體見代碼清單 21-5 加粗部分。

 

//xTimerCreate()使用實例
static TimerHandle_t Swtmr1_Handle = NULL; /* 軟件定時器句柄 */
static TimerHandle_t Swtmr2_Handle = NULL; /* 軟件定時器句柄 */
//周期模式的軟件定時器 1,定時器周期 1000(tick) Swtmr1_Handle = xTimerCreate((const char*)"AutoReloadTimer", (TickType_t)1000,/* 定時器周期 1000(tick) */ (UBaseType_t)pdTRUE,/* 周期模式 */ (void* )1,/* 為每個計時器分配一個索引的唯一 ID */ (TimerCallbackFunction_t)Swtmr1_Callback); /* 回調函數 */ if (Swtmr1_Handle != NULL) {   /********************************************************************   * xTicksToWait:如果在調用 xTimerStart()時隊列已滿,則以 tick 為單位指定調用任務應保持   * 在 Blocked(阻塞)狀態以等待 start 命令成功發送到 timer 命令隊列的時間。   * 如果在啟動調度程序之前調用 xTimerStart(),則忽略 xTicksToWait。在這里設置等待時間為 0.   **********************************************************************/   xTimerStart(Swtmr1_Handle,0); //開啟周期定時器 } //單次模式的軟件定時器 2,定時器周期 5000(tick) Swtmr2_Handle = xTimerCreate( (const char* )"OneShotTimer", (TickType_t)5000,/* 定時器周期 5000(tick) */ (UBaseType_t )pdFALSE,/* 單次模式 */ (void*)2,/* 為每個計時器分配一個索引的唯一 ID */ (TimerCallbackFunction_t)Swtmr2_Callback); if (Swtmr2_Handle != NULL) {   xTimerStart(Swtmr2_Handle,0); //開啟單次定時器 } static void Swtmr1_Callback(void* parameter) {   /* 軟件定時器的回調函數,用戶自己實現 */ } static void Swtmr2_Callback(void* parameter) {   /* 軟件定時器的回調函數,用戶自己實現 */ }

  6.2 、軟件定時器啟動函數

  (1)、xTimerStart()

  軟件定時器在創建完成的時候是處於休眠狀態的,需要用 FreeRTOS 的相關函數將軟件定時器活動起來,而 xTimerStart()函數就是可以讓處於休眠的定時器開始工作。

  我們知道,在系統開始運行的時候,系統會幫我們自動創建一個軟件定時器任務( prvTimerTask),在這個任務中,如果暫時沒有運行中的定時器,任務會進入阻塞態等待命令, 而我們的啟動函數就是通過“定時器命令隊列” 向定時器任務發送一個啟動命令,定時器任務獲得命令就解除阻塞,然后執行啟動軟件定時器命令。 下面來看看 xTimerStart()是怎么讓定時器工作的吧。

#define xTimerStart( xTimer, xTicksToWait ) \
xTimerGenericCommand( ( xTimer ), \(1)
tmrCOMMAND_START, \(2)
( xTaskGetTickCount() ), \(3)
NULL, \(4)
( xTicksToWait ) ) (5)

 

代碼清單 21-6(1):要操作的軟件定時器句柄。xTimerStart()函數就是一個宏定義,真正起作用的是 xTimerGenericCommand()函數。
代碼清單 21-6(2): tmrCOMMAND_START 是軟件定時器啟動命令,因為現在是要將軟件定時器啟動,該命令在 timers.h 中有定義。 xCommandID 參數可以指定多個命令,軟件定時器操作支持的命令具體見代碼清單 21-7//軟件定時器支持的命令
#define tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR ( ( BaseType_t ) -2 )
#define tmrCOMMAND_EXECUTE_CALLBACK ( ( BaseType_t ) -1 )
#define tmrCOMMAND_START_DONT_TRACE ( ( BaseType_t ) 0 )
#define tmrCOMMAND_START ( ( BaseType_t ) 1 )
#define tmrCOMMAND_RESET ( ( BaseType_t ) 2 )
#define tmrCOMMAND_STOP ( ( BaseType_t ) 3 )
#define tmrCOMMAND_CHANGE_PERIOD ( ( BaseType_t ) 4 )
#define tmrCOMMAND_DELETE ( ( BaseType_t ) 5 )

#define tmrFIRST_FROM_ISR_COMMAND ( ( BaseType_t ) 6 )
#define tmrCOMMAND_START_FROM_ISR ( ( BaseType_t ) 6 )
#define tmrCOMMAND_RESET_FROM_ISR ( ( BaseType_t ) 7 )
#define tmrCOMMAND_STOP_FROM_ISR ( ( BaseType_t ) 8 )
#define tmrCOMMAND_CHANGE_PERIOD_FROM_ISR ( ( BaseType_t ) 9 )

代碼清單 21-6(3): 獲取當前系統時間。
代碼清單 21-6(4): pxHigherPriorityTaskWoken 為 NULL,該參數在中斷中發送命令才起作用。
代碼清單 21-6(5): 用戶指定超時阻塞時間, 單位為系統節拍周期(即 tick)。調用xTimerStart()的任務將被鎖定在阻塞態, 在軟件定時器把啟動的命令成功發送到定時器命令隊列之前。如果在 FreeRTOS 調度器開啟之前調用 xTimerStart(),形參將不起作用。
BaseType_t xTimerGenericCommand( TimerHandle_t xTimer,
const BaseType_t xCommandID,
const TickType_t xOptionalValue,
BaseType_t * const pxHigherPriorityTaskWoken,
const TickType_t xTicksToWait )
{
  BaseType_t xReturn = pdFAIL;
  DaemonTaskMessage_t xMessage;

  configASSERT( xTimer );

  /* 發送命令給定時器任務 */
  if ( xTimerQueue != NULL ) (1)
  {
    /* 要發送的命令信息,包含命令、命令的數值(比如可以表示當前系統時間、要修改的定時器周期等)以及要處理的軟件定時器句柄 */
    xMessage.xMessageID = xCommandID; (2)
    xMessage.u.xTimerParameters.xMessageValue = xOptionalValue;
    xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer;

    /* 命令是在任務中發出的 */
    if ( xCommandID < tmrFIRST_FROM_ISR_COMMAND )  (3)
        {

            /* 如果調度器已經運行了,就根據用戶指定超時時間發送 */
            if ( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ) 
            {
                xReturn = xQueueSendToBack( xTimerQueue,&xMessage,xTicksToWait ); (4)
            } 
            else 
            {
                /* 如果調度器還未運行,發送就行了,不需要阻塞 */
                xReturn = xQueueSendToBack( xTimerQueue,xMessage,tmrNO_DELAY ); (5)
            }
        }
        /* 命令是在中斷中發出的 */
        else 
        {
            /* 調用從中斷向消息隊列發送消息的函數 */
            xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage,pxHigherPriorityTaskWoken );(6)
        }

        traceTIMER_COMMAND_SEND( xTimer,xCommandID,xOptionalValue,xReturn );
    } 
    else 
    {
        mtCOVERAGE_TEST_MARKER();
    }

    return xReturn;
}
代碼清單 21-8(1):系統打算通過“定時器命令隊列” 發送命令給定時器任務, 需要先判斷一下“定時器命令隊列”是否存在,只有存在隊列才允許發送命令。
代碼清單 21-8(2): 要發送的命令基本信息,包括命令、命令的數值(比如可以表示當前系統時間、要修改的定時器周期等)以及要處理的軟件定時器句柄等。
代碼清單 21-8(3): 根據用戶指定的 xCommandID 參數,判斷命令是在哪個上下文環境發出的,如果是在任務中發出的,則執行(4) 、 (5) 代碼,否則就執行(6)。
代碼清單 21-8(4): 如果系統調度器已經運行了,就根據用戶指定超時時間向“定時器命令隊列” 發送命令。
代碼清單 21-8(5): 如果調度器還未運行,用戶指定的超時時間是無效的,發送就行了,不需要阻塞, tmrNO_DELAY 的值為 0。
代碼清單 21-8(6): 命令是在中斷中發出的, 調用從中斷向消息隊列發送消息的函數xQueueSendToBackFromISR()就行了。
軟件定時器啟動函數的使用很簡單, 在創建一個軟件定時器完成后, 就可以調用該函數啟動定時器了

  (2)、xTimerStartFromISR()

  當 然 除 在 任 務 啟 動 軟 件 定 時 器 之 外 , 還 有 在 中 斷 中 啟 動 軟 件 定 時 器 的 函 數xTimerStartFromISR()。 xTimerStartFromISR()是函數 xTimerStart()的中斷版本, 用於啟動一個先前由函數 xTimerCreate() / xTimerCreateStatic()創建的軟件定時器。該函數的具體說明見表格 21-1, 使用實例具體見代碼清單 21-9。

/*******************************************************************************************************
  *@ 函數功能:在中斷中啟動一個軟件定時器。
  *@ 函數參數:xTimer:軟件定時器句柄  
               pxHigherPriorityTaskWoken:定時器守護任務的大部分時間都在阻塞態等待定時器命令隊列的命令。 調用函數 xTimerStartFromISR()將會往定時器的命令隊列發送一個啟動命令,這很有可能會將定時 器 任 務 從 阻 塞 態 移 除 。 如 果 調 用 函 數xTimerStartFromISR()讓定時器任務脫離阻塞態, 且定時器守護任務的優先級大於或者等於當前被中斷的任務的優先級,那么 pxHigherPriorityTaskWoken 的值會在函數xTimerStartFromISR()內部設置為 pdTRUE, 然后在中斷退出之前執行一次上下文切換。
               pxHigherPriorityTaskWoken:pxHigherPriorityTaskWoken 在使用之前必須初始化成pdFALSE。調用 xEventGroupSetBitsFromISR()會給守護任務發送一個消息, 如果守護任務的優先級高於當前被中斷的任務的優先級的話(一般情況下都需要將守護任務 的 優 先 級 設 置 為 所 有 任 務 中 最 高 優 先 級 ) ,pxHigherPriorityTaskWoken 會被置為 pdTRUE, 然后在中斷退出前執行一次上下文切換。
  *@ 返回值:如果啟動命令無法成功地發送到定時器命令隊列則返回 pdFAILE, 成功發送則返回pdPASS。 軟件定時器成功發送的命令是否真正的被執行也還要看定時器守護任務的優先級,其優先級由宏 configTIMER_TASK_PRIORITY 定義。
*******************************************************************************************************/
#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken )    xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR,( xTaskGetTickCountFromISR() ),( pxHigherPriorityTaskWoken ), 0U )

 

/* 這個方案假定軟件定時器 xBacklightTimer 已經創建,定時周期為 5s,執行次數為一次,即定時時間到了之后就進入休眠態。
程序說明:當按鍵按下,打開液晶背光,啟動軟件定時器,5s 時間到,關掉液晶背光*/

/* 軟件定時器回調函數 */
void vBacklightTimerCallback( TimerHandle_t pxTimer )
{
  /* 關掉液晶背光 */
  vSetBacklightState( BACKLIGHT_OFF );
}

/* 按鍵中斷服務程序 */
void vKeyPressEventInterruptHandler( void )
{
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;

  /* 確保液晶背光已經打開 */
  vSetBacklightState( BACKLIGHT_ON );

  /* 啟動軟件定時器 */
  if ( xTimerStartFromISR( xBacklightTimer,&xHigherPriorityTaskWoken ) != pdPASS ) 
  {     
/* 軟件定時器開啟命令沒有成功執行 */   }     /* ...執行其他的按鍵相關的功能代碼 */   if ( xHigherPriorityTaskWoken != pdFALSE )
  {     
/* 執行上下文切換 */   } }

  6.3、軟件定時器停止函數

  (1)、xTimerStop()

  xTimerStop() 用於停止一個已經啟動的軟件定時器, 該函數的實現也是通過“定時器命令隊列”發送一個停止命令給軟件定時器任務,從而喚醒軟件定時器任務去將定時器停止。 要想使函數 xTimerStop()必須在頭文件 FreeRTOSConfig.h 中把宏 configUSE_TIMERS定義為 1。 

/*******************************************************************************************************
  *@ 函數功能:停止一個軟件定時器, 讓其進入休眠態。
  *@ 函數參數:xTimer:軟件定時器句柄
               xBlockTime:用戶指定超時時間, 單位為系統節拍周期(即 tick)。 如果在 FreeRTOS 調度器開啟之前調用 xTimerStart(),形參將不起作用。
  *@ 返回值:如果啟動命令在超時時間之前無法成功地發送到定時器命令隊列則返回 pdFAILE, 成功發送則返回 pdPASS。 軟件定時器成功發送的命令是否真正的被執行也還要看定時器守護任務的優先級,其優先級由宏 configTIMER_TASK_PRIORITY 定義。
*******************************************************************************************************/
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xBlockTime );

  (2)、xTimerStopFromISR()

  xTimerStopFromISR()是函數 xTimerStop()的中斷版本, 用於停止一個正在運行的軟件定時器, 讓其進入休眠態, 實現過程也是通過“定時器命令隊列”向軟件定時器任務發送停止命令。

/*******************************************************************************************************
  *@ 函數功能:在中斷中停止一個軟件定時器, 讓其進入休眠態。
  *@ 函數參數:xTimer:軟件定時器句柄
               pxHigherPriorityTaskWoken:定時器守護任務的大部分時間都在阻塞態等待定時器命令隊列的命令。調用函數 xTimerStopFromISR()將會往定時器的命令隊列發送一個
                                          停止命令,這很有可能會將定時 器 任 務 從 阻 塞 態 移 除 。 如 果 調 用 函 數xTimerStopFromISR()讓定時器任務脫離阻塞態,且
                                          定時器守護任務的優先級大於或者等於當前被中斷的任務的優先級,那么 pxHigherPriorityTaskWoken 的值會在函數xTimerStopFromISR()
                                          內部設置為 pdTRUE, 然后在中斷退出之前執行一次上下文切換。
  *@ 返回值:如果停止命令在超時時間之前無法成功地發送到定時器命令隊列則返回 pdFAILE, 成功發送則返回 pdPASS。 軟件定時器成功發送的命令是否真正的被執行也還要看定時
             器守護任務的優先級,其優先級由宏 configTIMER_TASK_PRIORITY 定義。
*******************************************************************************************************/
BaseType_t xTimerStopFromISR(TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken);

  6.4、軟件定時器任務

  我們知道,軟件定時器回調函數運行的上下文環境是任務,那么軟件定時器任務是在干什么的呢?如何創建的呢?下面跟我一步步來分析軟件定時器的工作過程。軟件定時器任務是在系統開始調度( vTaskStartScheduler()函數) 的時候就被創建的,前提是將宏定義 configUSE_TIMERS 開啟,具體見代碼清單 21-12 加粗部分, 在xTimerCreateTimerTask()函數里面就是創建了一個軟件定時器任務,就跟我們創建任務一樣,支持動態與靜態創建,我們暫時看動態創建的即可,具體見代碼清單 21-13 加粗部分。

void vTaskStartScheduler( void )
{
    #if ( configUSE_TIMERS == 1 )
    {
        if ( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask();
        } 
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_TIMERS */
}
BaseType_t xTimerCreateTimerTask( void )
{
  BaseType_t xReturn = pdFAIL;

  prvCheckForValidListAndQueue();

  if ( xTimerQueue != NULL )
  {     
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) /* 靜態創建任務 */     {       StaticTask_t *pxTimerTaskTCBBuffer = NULL;       StackType_t *pxTimerTaskStackBuffer = NULL;       uint32_t ulTimerTaskStackSize;       vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer,&pxTimerTaskStackBuffer,&ulTimerTaskStackSize );       xTimerTaskHandle = xTaskCreateStatic(prvTimerTask,"Tmr Svc",ulTimerTaskStackSize,NULL,( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,pxTimerTaskStackBuffer,pxTimerTaskTCBBuffer );       if ( xTimerTaskHandle != NULL )       {         xReturn = pdPASS;       }     }     #else /* 動態創建任務 */     {       xReturn = xTaskCreate(prvTimerTask, "Tmr Svc",configTIMER_TASK_STACK_DEPTH,NULL,( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,&xTimerTaskHandle ); (1)     }     #endif   }   else   {     mtCOVERAGE_TEST_MARKER();   }   configASSERT( xReturn );   return xReturn; }

  6.5、 軟件定時器刪除函數 xTimerDelete()

  xTimerDelete()用於刪除一個已經被創建成功的軟件定時器, 刪除之后就無法使用該定時器, 並且定時器相應的資源也會被系統回收釋放。 要想使函數 xTimerStop()必須在頭文件 FreeRTOSConfig.h 中把宏 configUSE_TIMERS 定義為 1, 該函數的具體說明見下。

/*******************************************************************************************************
  *@ 函數功能:刪除一個已經被創建成功的軟件定時器
  *@ 函數參數:xTimer:軟件定時器句柄
              xBlockTime:用戶指定的超時時間, 單位為系統節拍周期(即 tick), 如果在 FreeRTOS調度器開啟之前調用 xTimerStart(), 該形參將不起作用。
  *@ 返回值:如果刪除命令在超時時間之前無法成功地發送到定時器命令隊列則返回 pdFAILE, 成功發送則返回 pdPASS。
*******************************************************************************************************/
#define xTimerDelete( xTimer, xTicksToWait )    xTimerGenericCommand( ( xTimer ),tmrCOMMAND_DELETE,0U, NULL, ( xTicksToWait ) )

  從軟件定時器刪除函數 xTimerDelete()的原型可以看出, 刪除一個軟件定時器也是在軟件定時器任務中刪除, 調用 xTimerDelete()將刪除軟件定時器的命令發送給軟件定時器任務,軟件定時器任務在接收到刪除的命令之后就進行刪除操作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM