Freertos定時器

說明

這節講講freertos內核組件之定時器。

從這節基本上可以看出來,內核為了使其應用性更優,而加入定時器。所以,為了應用需求,嵌入式開發者完全可以自己在freertos基礎上繼續添加組件。


分析


先講講內核定時器是怎麼回事。

內核定時器目的主要是為了解決硬件定時器不夠用而加入的。


內核定時器怎麼創建?

可以調用函數xTimerCreate(),函數內容為:

<code>TimerHandle_t xTimerCreate(\tconst char * const pcTimerName,\t\t\t/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
\t\t\t\t\t\t\t\tconst TickType_t xTimerPeriodInTicks,
\t\t\t\t\t\t\t\tconst UBaseType_t uxAutoReload,
\t\t\t\t\t\t\t\tvoid * const pvTimerID,
\t\t\t\t\t\t\t\tTimerCallbackFunction_t pxCallbackFunction )
\t{
\tTimer_t *pxNewTimer;

\t\tpxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of Timer_t is always a pointer to the timer's mame. */

\t\tif( pxNewTimer != NULL )

\t\t{
\t\t\t/* Status is thus far zero as the timer is not created statically
\t\t\tand has not been started. The auto-reload bit may get set in
\t\t\tprvInitialiseNewTimer. */
\t\t\tpxNewTimer->ucStatus = 0x00;
\t\t\tprvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );
\t\t}

\t\treturn pxNewTimer;
\t}/<code>

在用戶調用該函數的時候,指定定時器名稱,定時時間到執行的函數等。初始化定時器主要使用函數prvInitialiseNewTimer(),在第一次調用這個函數的時候,會初始化定時器鏈表

xActiveTimerList1和xActiveTimerList2。然後使用xQueueCreate()創建一個消息隊列。

創建timer完成之後,使用xTimerStart()來啟動定時器。其實調用的是xTimerGenericCommand()。

#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )


在這個函數中,會組裝一個消息,然後調用xQueueSendToBack()函數將消息發送到隊列的尾部,


如果想停止定時器,調用的是xTimerStop(),其實實際使用的還是xTimerGenericCommand()。

所以,看完以上函數,還是沒發現定時器的運行原理。


在系統啟動的時候,內核會調用vTaskStartScheduler()啟動調度器,其中會調用xTimerCreateTimerTask(),其內容為:


<code>BaseType_t xTimerCreateTimerTask( void )
{
BaseType_t xReturn = pdFAIL;

\t/* This function is called when the scheduler is started if
\tconfigUSE_TIMERS is set to 1. Check that the infrastructure used by the
\ttimer service task has been created/initialised. If timers have already
\tbeen created then the initialisation will already have been performed. */
\tprvCheckForValidListAndQueue();

\tif( xTimerQueue != NULL )
\t{
\t\t#if( configSUPPORT_STATIC_ALLOCATION == 1 )
\t\t{
\t\t\tStaticTask_t *pxTimerTaskTCBBuffer = NULL;
\t\t\tStackType_t *pxTimerTaskStackBuffer = NULL;
\t\t\tuint32_t ulTimerTaskStackSize;

\t\t\tvApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
\t\t\txTimerTaskHandle = xTaskCreateStatic(\tprvTimerTask,
\t\t\t\t\t\t\t\t\t\t\t\t\tconfigTIMER_SERVICE_TASK_NAME,
\t\t\t\t\t\t\t\t\t\t\t\t\tulTimerTaskStackSize,
\t\t\t\t\t\t\t\t\t\t\t\t\tNULL,
\t\t\t\t\t\t\t\t\t\t\t\t\t( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
\t\t\t\t\t\t\t\t\t\t\t\t\tpxTimerTaskStackBuffer,
\t\t\t\t\t\t\t\t\t\t\t\t\tpxTimerTaskTCBBuffer );

\t\t\tif( xTimerTaskHandle != NULL )
\t\t\t{
\t\t\t\txReturn = pdPASS;
\t\t\t}
\t\t}
\t\t#else
\t\t{

\t\t\txReturn = xTaskCreate(\tprvTimerTask,
\t\t\t\t\t\t\t\t\tconfigTIMER_SERVICE_TASK_NAME,
\t\t\t\t\t\t\t\t\tconfigTIMER_TASK_STACK_DEPTH,
\t\t\t\t\t\t\t\t\tNULL,
\t\t\t\t\t\t\t\t\t( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
\t\t\t\t\t\t\t\t\t&xTimerTaskHandle );
\t\t}
\t\t#endif /* configSUPPORT_STATIC_ALLOCATION */
\t}
\telse
\t{
\t\tmtCOVERAGE_TEST_MARKER();
\t}

\tconfigASSERT( xReturn );
\treturn xReturn;
}/<code>


也就是說,系統會創建好一個任務,專門供定時器運行。

之後會首先調用prvCheckForValidListAndQueue()將定時器鏈表建立好。之後調用xTaskCreate()創建任務prvTimerTask。

在啟動工作起來後,會指定static portTASK_FUNCTION( prvTimerTask, pvParameters ),這個函數內容為:

<code>static portTASK_FUNCTION( prvTimerTask, pvParameters )
{
TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;

\t/* Just to avoid compiler warnings. */
\t( void ) pvParameters;

\t#if( configUSE_DAEMON_TASK_STARTUP_HOOK == 1 )
\t{
\t\textern void vApplicationDaemonTaskStartupHook( void );

\t\t/* Allow the application writer to execute some code in the context of
\t\tthis task at the point the task starts executing. This is useful if the
\t\tapplication includes initialisation code that would benefit from
\t\texecuting after the scheduler has been started. */
\t\tvApplicationDaemonTaskStartupHook();
\t}
\t#endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */

\tfor( ;; )
\t{
\t\t/* Query the timers list to see if it contains any timers, and if so,
\t\tobtain the time at which the next timer will expire. */
\t\txNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );

\t\t/* If a timer has expired, process it. Otherwise, block this task
\t\tuntil either a timer does expire, or a command is received. */
\t\tprvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );

\t\t/* Empty the command queue. */
\t\tprvProcessReceivedCommands();
\t}
}/<code>

可以看出,該函數循環執行3個函數。

假設定時器任務為timerA。

如果任務B使用xTimerCreate()創建了一個定時器,之後調用xTimerStart()啟動定時器,則該函數會使用xQueueSendToBack()給消息隊列發送一條消息,設啟動消息為tmrCOMMAND_START。這個時候定時器任務timerA會觸發prvProcessReceivedCommands()的執行,其實就是調用到xQueueReceive(),然後prvProcessReceivedCommands()繼續執行,會將定時器狀態設置為激活,然後將該定時器添加到list鏈表中,如pxOverflowTimerList

pxCurrentTimerList。如果任務B的定時器任務是定時執行(多次執行),則需要再給隊列發送消息。

之後定時器任務timerA執行到prvGetNextExpireTime(),查看到鏈表pxCurrentTimerList不為空,就會獲取到任務的超時時間,如果超時間未到,就會調用vTaskPlaceOnEventListRestricted()將定時器任務timerA添加到延時表中,以致可以在定時時間到定時器timerA能喚醒。

接著再執行時,說明timerA已經被喚醒了,那隊列在上次創建的時候已經是存在消息的,所以會再次走上次的流程,只是消息類型改變為tmrCOMMAND_START_DONT_TRACE而已,同時,定時執行的函數也在這個位置。


稍微總結一下內核定時器是怎麼實現的。

首先創建一個定時器任務,然後這個任務一直查看是否有超時定時器,如果有則執行定時器中指定的函數。

再細化一下,定時器任務需要定義一個消息隊列。

定時器任務一直執行,如果再另外一個任務A創建了定時器,之後使用xTimerStart()啟動定時器的時候,會給消息隊列發送一個消息,定時器任務在自己這個循環中一直收消息,發消息,執行任務A指定的回調函數。


分享到:


相關文章: