

软件定时器基于硬件定时器实现。
软件定时器允许设置一段时间,当设置的时间到达之后就执行指定的功能函数,被定时器
调用的这个功能函数叫做定时器的回调函数。回调函数的两次执行间隔叫做定时器的定时周期,
简而言之,软件定时器的定时周期到了以后就会执行回调函数。
Free RT OS基于systick中断产生计数值。软件定时其开始计数,当计数值到了所设置的时间时执行回调函数。
在RT-Thread中 软件定时器的定时回调函数直接放到了systick的中断回调函数里进行处理,但Free-RT-OS并没有这样做,主要原因是在于不知道定时处理回调函数需要花费多少时间,使得systick的节拍受到影响。
那么软件定时器是如何处理回调函数的?
答:Free RT OS创建了一个管理软件定时器的任务,叫做DaemonTask 也被称为守护任务,由它处理软件定时器的定时回调函数。
Free RT OS允许用户自己选择是否使用软件定时器。
当选择使用软件定时器时,Free RTOS内部的具体流程:
Free RT OS为管理软件定时器创建的 守护任务,定时器链表,超时链表,定时器命令队列。对应下面的代码。

具体过程:
1 : 当使用了软件定时器,则在开启调度器时,创建Daemon Task(守护任务)
osKernelStart() ->vTaskStartScheduler()(启动调度器)-> xTimerCreateTimerTask(); (创建守护任务)
创建守护任务

2:守护任务内部定时任务的函数入口 prvTimerTask

3: 同时在创建内部调用 prvCheckForValidListAndQueue
prvCheckForValidListAndQueue (创建定时器命令队列,创建当前定时器链表和超时定时器链表。

4:守护任务的函数入口 prvTimerTask
- 查询计时器列表,看看它是否包含任何计时器,如果包含,获取下一个计时器将过期的时间。
- 如果计时器已过期,则处理它。否则,阻塞此任务。直到计时器过期或收到命令为止。
- 处理软件定时器的命令接受。(开启,关闭,调整周期,复位)
到这,已经能摸清除软件定时器工作流程的大概了。
以上都是RTOS内部为管理软件定时器所做的辅助工作。
再看看创建软件定时器:
软件定时器刚创建处于休眠态。

调用开启软件定时器的API(xTimerStart)后则把封装一个message对象 ,写入定时器命令队列。(这个定时器命令队列就是在守护函数中所创建的那个)
#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )
//这个开启软件定时器的函数向定时器服务任务发送相应命令
调用宏 xTimerGenericCommand( ( xTimer ) 发message到定时器命令队列

同理,停止某个软件定时器,调整软件定时器的定时周期也是通过写命令队列的方式实现。
不能在软件定时器的回调函数中调用会阻塞当前任务的API.(如不能调用 vTaskDelay()、vTaskDelayUnti(),还有一些访问队列或者信号量的非零阻塞时间的 API 函数也不能调用。)
为什么?因为软件定时器的回调函数都依赖于守护任务去处理实现其回调函数,在软件定时器的回调函数中阻塞则意味在守护任务中阻塞,守护任务将无法继续管理所有的软件定时器。
再剖析守护任务的回调函数。

prvGetNextExpireTime 获取定时器下一次超时时间。
定时器列表(pxCurrentTimerList)按定时器的溢出时间按升序排列,最前面的任务首先超时。
- static TickType_t prvGetNextExpireTime( BaseType_t * const pxListWasEmpty )
- {
- TickType_t xNextExpireTime;
-
- /* Timers are listed in expiry time order, with the head of the list
- referencing the task that will expire first. Obtain the time at which
- the timer with the nearest expiry time will expire. If there are no
- active timers then just set the next expire time to 0. That will cause
- this task to unblock when the tick count overflows, at which point the
- timer lists will be switched and the next expiry time can be
- re-assessed. */
- *pxListWasEmpty = listLIST_IS_EMPTY( pxCurrentTimerList );
- if( *pxListWasEmpty == pdFALSE )
- {
- xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
- }
- else
- {
- /* Ensure the task unblocks when the tick count rolls over. */
- xNextExpireTime = ( TickType_t ) 0U;
- }
-
- return xNextExpireTime;
- }
prvProcessTimerOrBlock() //处理软件定时器任务(溢出超时) | 让软件定时器阻塞(未溢出)
过程:
取出当前systick的计数值,stm32默认以systick作为os的时基,而systick是24位计数器。
把获取到的计数值与上一次进行比较,正常情况是现在的计数值>上一次计数值。若小于则计数值发生了溢出。
- static void prvProcessTimerOrBlockTask( const TickType_t xNextExpireTime, BaseType_t xListWasEmpty )
- {
- TickType_t xTimeNow;
- BaseType_t xTimerListsWereSwitched;
-
- vTaskSuspendAll();
- {
- /* Obtain the time now to make an assessment as to whether the timer
- has expired or not. If obtaining the time causes the lists to switch
- then don't process this timer as any timers that remained in the list
- when the lists were switched will have been processed within the
- prvSampleTimeNow() function. */
- xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
- if( xTimerListsWereSwitched == pdFALSE )
- {
- /* The tick count has not overflowed, has the timer expired? */
- if( ( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow ) )
- {
- ( void ) xTaskResumeAll();
- prvProcessExpiredTimer( xNextExpireTime, xTimeNow );
- }
- else
- {
- /* The tick count has not overflowed, and the next expire
- time has not been reached yet. This task should therefore
- block to wait for the next expire time or a command to be
- received - whichever comes first. The following line cannot
- be reached unless xNextExpireTime > xTimeNow, except in the
- case when the current timer list is empty. */
- if( xListWasEmpty != pdFALSE )
- {
- /* The current timer list is empty - is the overflow list
- also empty? */
- xListWasEmpty = listLIST_IS_EMPTY( pxOverflowTimerList );
- }
-
- vQueueWaitForMessageRestricted( xTimerQueue, ( xNextExpireTime - xTimeNow ), xListWasEmpty );
-
- if( xTaskResumeAll() == pdFALSE )
- {
- /* Yield to wait for either a command to arrive, or the
- block time to expire. If a command arrived between the
- critical section being exited and this yield then the yield
- will not cause the task to block. */
- portYIELD_WITHIN_API();
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- }
- }
- else
- {
- ( void ) xTaskResumeAll();
- }
- }
- }
处理软件定时器溢出任务(这个函数是最为关键的函数,更新下一次溢出值,并插入正确的链表中)
- static void prvProcessExpiredTimer( const TickType_t xNextExpireTime, const TickType_t xTimeNow )
- {
- BaseType_t xResult;
- Timer_t * const pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );
-
- /* Remove the timer from the list of active timers. A check has already
- been performed to ensure the list is not empty. */
- ( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
- traceTIMER_EXPIRED( pxTimer );
-
- /* If the timer is an auto reload timer then calculate the next
- expiry time and re-insert the timer in the list of active timers. */
- if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
- {
- /* The timer is inserted into a list using a time relative to anything
- other than the current time. It will therefore be inserted into the
- correct list relative to the time this task thinks it is now. */
- if( prvInsertTimerInActiveList( pxTimer, ( xNextExpireTime + pxTimer->xTimerPeriodInTicks ), xTimeNow, xNextExpireTime ) != pdFALSE )
- {
- /* The timer expired before it was added to the active timer
- list. Reload it now. */
- xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xNextExpireTime, NULL, tmrNO_DELAY );
- configASSERT( xResult );
- ( void ) xResult;
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
-
- /* Call the timer callback. */
- pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
- }
prvProcessReceiveCommands() //对定时器命令队列中的数据进行处理。
遍历并处理定时器命令队列的所有message。
- static void prvProcessReceivedCommands( void )
- {
- DaemonTaskMessage_t xMessage;
- Timer_t *pxTimer;
- BaseType_t xTimerListsWereSwitched, xResult;
- TickType_t xTimeNow;
-
- while( xQueueReceive( xTimerQueue, &xMessage, tmrNO_DELAY ) != pdFAIL ) /*lint !e603 xMessage does not have to be initialised as it is passed out, not in, and it is not used unless xQueueReceive() returns pdTRUE. */
- {
- #if ( INCLUDE_xTimerPendFunctionCall == 1 )
- {
- /* Negative commands are pended function calls rather than timer
- commands. */
- if( xMessage.xMessageID < ( BaseType_t ) 0 )
- {
- const CallbackParameters_t * const pxCallback = &( xMessage.u.xCallbackParameters );
-
- /* The timer uses the xCallbackParameters member to request a
- callback be executed. Check the callback is not NULL. */
- configASSERT( pxCallback );
-
- /* Call the function. */
- pxCallback->pxCallbackFunction( pxCallback->pvParameter1, pxCallback->ulParameter2 );
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- }
- #endif /* INCLUDE_xTimerPendFunctionCall */
-
- /* Commands that are positive are timer commands rather than pended
- function calls. */
- if( xMessage.xMessageID >= ( BaseType_t ) 0 )
- {
- /* The messages uses the xTimerParameters member to work on a
- software timer. */
- pxTimer = xMessage.u.xTimerParameters.pxTimer;
-
- if( listIS_CONTAINED_WITHIN( NULL, &( pxTimer->xTimerListItem ) ) == pdFALSE ) /*lint !e961. The cast is only redundant when NULL is passed into the macro. */
- {
- /* The timer is in a list, remove it. */
- ( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
-
- traceTIMER_COMMAND_RECEIVED( pxTimer, xMessage.xMessageID, xMessage.u.xTimerParameters.xMessageValue );
-
- /* In this case the xTimerListsWereSwitched parameter is not used, but
- it must be present in the function call. prvSampleTimeNow() must be
- called after the message is received from xTimerQueue so there is no
- possibility of a higher priority task adding a message to the message
- queue with a time that is ahead of the timer daemon task (because it
- pre-empted the timer daemon task after the xTimeNow value was set). */
- xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
-
- switch( xMessage.xMessageID )
- {
- case tmrCOMMAND_START :
- case tmrCOMMAND_START_FROM_ISR :
- case tmrCOMMAND_RESET :
- case tmrCOMMAND_RESET_FROM_ISR :
- case tmrCOMMAND_START_DONT_TRACE :
- /* Start or restart a timer. */
- if( prvInsertTimerInActiveList( pxTimer, xMessage.u.xTimerParameters.xMessageValue + pxTimer->xTimerPeriodInTicks, xTimeNow, xMessage.u.xTimerParameters.xMessageValue ) != pdFALSE )
- {
- /* The timer expired before it was added to the active
- timer list. Process it now. */
- pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
- traceTIMER_EXPIRED( pxTimer );
-
- if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
- {
- xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xMessage.u.xTimerParameters.xMessageValue + pxTimer->xTimerPeriodInTicks, NULL, tmrNO_DELAY );
- configASSERT( xResult );
- ( void ) xResult;
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- break;
-
- case tmrCOMMAND_STOP :
- case tmrCOMMAND_STOP_FROM_ISR :
- /* The timer has already been removed from the active list.
- There is nothing to do here. */
- break;
-
- case tmrCOMMAND_CHANGE_PERIOD :
- case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
- pxTimer->xTimerPeriodInTicks = xMessage.u.xTimerParameters.xMessageValue;
- configASSERT( ( pxTimer->xTimerPeriodInTicks > 0 ) );
-
- /* The new period does not really have a reference, and can
- be longer or shorter than the old one. The command time is
- therefore set to the current time, and as the period cannot
- be zero the next expiry time can only be in the future,
- meaning (unlike for the xTimerStart() case above) there is
- no fail case that needs to be handled here. */
- ( void ) prvInsertTimerInActiveList( pxTimer, ( xTimeNow + pxTimer->xTimerPeriodInTicks ), xTimeNow, xTimeNow );
- break;
-
- case tmrCOMMAND_DELETE :
- /* The timer has already been removed from the active list,
- just free up the memory if the memory was dynamically
- allocated. */
- #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 0 ) )
- {
- /* The timer can only have been allocated dynamically -
- free it again. */
- vPortFree( pxTimer );
- }
- #elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configSUPPORT_STATIC_ALLOCATION == 1 ) )
- {
- /* The timer could have been allocated statically or
- dynamically, so check before attempting to free the
- memory. */
- if( pxTimer->ucStaticallyAllocated == ( uint8_t ) pdFALSE )
- {
- vPortFree( pxTimer );
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- }
- #endif /* configSUPPORT_DYNAMIC_ALLOCATION */
- break;
-
- default :
- /* Don't expect to get here. */
- break;
- }
- }
- }
- }
参考: