定时器的使用与定时器任务(Tmr Svc)服务的理解.
freertos的定时器功能是用task实现的,但是其命令却是用队列queue实现的.
一旦别的task中发送xMessage,可以导致prvTimerTask立刻从pxDelayedTaskList(等待列表)中加入pxReadyTasksLists(就绪列表).
由于Daemon优先级最高,因此可以立刻执行到prvTimerTask,通过接收xMessage的函数,根据xMessage.xMessageID,具体来处理命令。
这是定时器的数据结构
定时器接收命令的格式:
包含两种格式,两种格式是以union的形式组织的,一种是对定时器开启、停止等操作,一种是执行回调函数
BaseType_t xTimerCreateTimerTask( void )
{
BaseType_t xReturn = pdFAIL;
//初始化Time任务需要的信息
prvCheckForValidListAndQueue();
if( xTimerQueue != NULL )
{
StaticTask_t *pxTimerTaskTCBBuffer = NULL;
StackType_t *pxTimerTaskStackBuffer = NULL;
uint32_t ulTimerTaskStackSize;
vApplicationGetGeneralTaskMemory(configTIMER_SERVICE_TASK_NAME,&pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,
configTIMER_SERVICE_TASK_NAME,
ulTimerTaskStackSize,
NULL,
( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
pxTimerTaskStackBuffer,
pxTimerTaskTCBBuffer );
if( xTimerTaskHandle != NULL )
{
xReturn = pdPASS;
}
}
configASSERT( xReturn );
return xReturn;
}
static void prvCheckForValidListAndQueue( void )
{
taskENTER_CRITICAL();
{
if( xTimerQueue == NULL )
{
//定时器管理列表
vListInitialise( &xActiveTimerList1 );
vListInitialise( &xActiveTimerList2 );
// 当前节拍计数器对应的定时器管理链表指针
pxCurrentTimerList = &xActiveTimerList1;
// 溢出时间到了下一个节拍计数阶段(当前节拍计数器溢出后)的定时器管理链表指针
pxOverflowTimerList = &xActiveTimerList2;
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
// 创建定时器命令队列 */
static StaticQueue_t xStaticTimerQueue; /*lint !e956 Ok to declare in this manner to prevent additional conditional compilation guards in other locations. */
static uint8_t ucStaticTimerQueueStorage[ ( size_t ) configTIMER_QUEUE_LENGTH * sizeof( DaemonTaskMessage_t ) ]; /*lint !e956 Ok to declare in this manner to prevent additional conditional compilation guards in other locations. */
xTimerQueue = xQueueCreateStatic( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, ( UBaseType_t ) sizeof( DaemonTaskMessage_t ), &( ucStaticTimerQueueStorage[ 0 ] ), &xStaticTimerQueue );
}
#else
#endif
#if ( configQUEUE_REGISTRY_SIZE > 0 )
{
if( xTimerQueue != NULL )
{
vQueueAddToRegistry( xTimerQueue, "TmrQ" );
}
}
#endif /* configQUEUE_REGISTRY_SIZE */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
}
//中断函数调用
#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 )
命令的发送如下 等等
#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )
#define xTimerStop( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )
#define xTimerReset( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )
#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )
#define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP_FROM_ISR, 0, ( pxHigherPriorityTaskWoken ), 0U )
// xTaskGetTickCount函数返回当前系统时间
#define xTimerStart( xTimer, xTicksToWait ) \
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, \
( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )
#define tmrNO_DELAY ( TickType_t ) 0U
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 )
{
// 组装定时器命令
xMessage.xMessageID = xCommandID; //指令
xMessage.u.xTimerParameters.xMessageValue = xOptionalValue; //传递的数据
xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer; //句柄
if( xCommandID < tmrFIRST_FROM_ISR_COMMAND )
{
// 任务级发送消息
// 只有在调度器运行期间,发送消息才能带延时(因为可能导致阻塞)
if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING )
{
xReturn = xQueueSendToBack( xTimerQueue, &xMessage,
xTicksToWait );
}
else
{
// 如果调度器尚未开启,或者调度器被挂起,
// 则只能以非阻塞的方式调用
xReturn = xQueueSendToBack( xTimerQueue, &xMessage,
tmrNO_DELAY );
}
}
else
{
// 中断级发送消息
// 中断级发送消息不能阻塞,同时返回时要判断是否需要触发任务调度
xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage,
pxHigherPriorityTaskWoken );
}
}
return xReturn;
}
说明1:FreeRTOS软件定时器API分析的重点,就是传递的xCommandID和xOptionalValue
启动定时器传递的xOptionalValue为发送定时器命令时的系统时间,也就是计算定时器到期绝对时间的基准
说明2:异步处理定时器命令
① 用户可以在任务或ISR中发送定时器命令到定时器命令消息队列,之后由定时器任务接收并处理该命令,因此是一个异步处理流程
② 因为是异步处理,所以需要考虑实际处理定时器命令与发送定时器命令之间的时延
与任务级调用相比,
① 不能带发送延时
② 需要通过pxHigherPriorityTaskWoken返回是否需要触发任务调度
#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) \
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, \
( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )
//修改定时值传递的xOptionalValue为新的定时值
// 任务级
#define xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait ) \
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD, \
( xNewPeriod ), NULL, ( xTicksToWait ) )
// 中断级
#define xTimerChangePeriodFromISR( xTimer, xNewPeriod, \
pxHigherPriorityTaskWoken ) \
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD_FROM_ISR, \
( xNewPeriod ), ( pxHigherPriorityTaskWoken ), 0U )
//删除定时器
// 任务级 删除定时值传递的xOptionalValue为0
#define xTimerDelete( xTimer, xTicksToWait ) \
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_DELETE, 0U, NULL, \
( xTicksToWait ) )
整体认知:调用关系
prvTimerTask // 定时器任务函数
--> prvGetNextExpireTime // 获取pxCurrentTimerList队首定时器到期绝对时间
--> prvProcessTimerOrBlockTask // 处理到期定时器并阻塞在定时器命令队列上
--> prvSampleTimeNow // 获取当前时间,如果溢出则交换定时器列表
--> prvSwitchTimerLists // 交换定时器列表,并处理pxCurrentTimerList
--> prvProcessExpiredTimer // 处理到期的定时器
--> prvInsertTimerInActiveList // 将定时器加入定时器列表,已超时则直接返回
--> vQueueWaitForMessageRestricted // 将任务加入定时器命令队列等待接收列表
--> vTaskPlaceOnEventListRestricted
--> prvProcessReceivedCommands // 接收定时器命令并处理
static void prvTimerTask( void *pvParameters )
{
TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;
/* Just to avoid compiler warnings. */
( void ) pvParameters;
for( ;; )
{
/* 返回获取下一个定时器的时间,用参数返回列表中是否有定时器数据*/
xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
/* /* 如果计时器已过期,请处理它。否则,阻塞此任务直到计时器过期或接收到命令为止。*/*/
prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
/* 接收并处理定时器命令. */
prvProcessReceivedCommands();
}
}
/*
如果pxCurrentTimerList为空,则通过pxListWasEmpty标识,并返回0
如果pxCurrentTimerList不为空,也通过pxListWasEmpty标识,并返回队首定时器的到期时间(到期的绝对时间)
*/
// pxListWasEmpty:出参,标识当前定时器列表是否为空
static TickType_t prvGetNextExpireTime( BaseType_t * const pxListWasEmpty)
{
TickType_t xNextExpireTime;
*pxListWasEmpty = listLIST_IS_EMPTY( pxCurrentTimerList );
if( *pxListWasEmpty == pdFALSE )
{
// 如果当前定时器列表不为空,则返回队首任务的到期时间
// 软件定时器在定时器列表中是按照到期时间升序排列的
xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
}
else
{
// 如果定时器列表为空,则返回0
xNextExpireTime = ( TickType_t ) 0U;
}
return xNextExpireTime;
/*
* xNextExpireTime:prvGetNextExpireTime函数返回的下个定时器到期时间
* 如果pxCurrentTimerList为空,则返回0
* xListWasEmpty:prvGetNextExpireTime函数返回的pxCurrentTimerList是否为空
*/
static void prvProcessTimerOrBlockTask( const TickType_t xNextExpireTime,
BaseType_t xListWasEmpty )
{
TickType_t xTimeNow;
BaseType_t xTimerListsWereSwitched;
// 关闭调度器
// 保护定时器列表
vTaskSuspendAll();
{
// 返回当前系统时间
// 如果发生系统计时溢出,则处理pxCurrentTimerList上的所有定时器,
// 并且会交换pxCurrentTimerList和pxOverflowTimerList列表,
// 是否发生交换,通过xTimerListWereSwitched标识
xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
if( xTimerListsWereSwitched == pdFALSE )
{
// 如果没有发生计时溢出并交换定时器列表
// 如果pxCurrentTimerList不为空且有定时器到期,
// 则处理到期的定时器
if( ( xListWasEmpty == pdFALSE ) &&
( xNextExpireTime <= xTimeNow ) )
{
( void ) xTaskResumeAll();
prvProcessExpiredTimer( xNextExpireTime, xTimeNow );
}
else
{
// 进入该分支有2种可能,
// 1. pxCurrentTimerList列表为空
// 2. pxCurrentTimerList列表不为空,但队首定时器尚未到期
// 如果pxCurrentTimerList列表为空,
// 则判断pxOverflowTimerList列表是否为空
// 如果2个定时器列表均为空,下面的等待就可以直接死等了
if( xListWasEmpty != pdFALSE )
{
xListWasEmpty =
listLIST_IS_EMPTY( pxOverflowTimerList );
}
// 进入这个分支的另一种情况是pxCurrentTimerList不为空
// 但是队首任务尚未到期
// 此时就保持了xListWasEmpty为pdFALSE,下面的延时就不会死等
// 在定时器命令队列上进行阻塞等待
// 此处通过xListWasEmpty决定是否要死等,
// 也就是直接加入挂起(suspend)列表而不是延时(delay)列表
// 如果pxCurrentTimerList为空,则xNextExpireTime为0
// pxCurrentTimerList有未到期定时器时间线
// --- xTimeNow --- xNextExpireTime --->
// pxCurrentTimerList为空的时间线
// xNextExpireTime(0)--- xTimeNow ----->
// 1. 如果pxOverflowTimerList为空,那么就可以死等
// 2. 如果pxOverflowTimerList不为空,那么此次等待就只会按照
// 最大计时值未溢出的部分进行等待,被唤醒后就可以进行计时
// 溢出的处理
vQueueWaitForMessageRestricted( xTimerQueue,
( xNextExpireTime - xTimeNow ), xListWasEmpty );
if( xTaskResumeAll() == pdFALSE )
{
// 此处一定要触发任务调度,
// 因为此时定时器任务要进入阻塞态,此时已经不在就绪列表中
// 更正:上面的理解是有部分错误的
// 只有当定时器命令消息队列为空时,才会时机加入
// 等待接收队列并开始阻塞
// 但是由于可能会阻塞,所以此处仍需要判断xTaskResumeAll
// 函数的返回值,确保此时任务被调度走
portYIELD_WITHIN_API();
}
}
}
else
{
// 如果发生溢出并交换了定时器列表
// 说明pxCuurentTimerList上的定时器均已被处理
// 则直接恢复调度器并退出
// 此时定时器列表已经交换,因此需要进入下一轮prvTimerTask循环
// 判断交换后的pxCurrentTimerList上的定时器状态
( void ) xTaskResumeAll();
}
}
}
prvProcessTimerOrBlockTask 补充说明
/*
说明1:仅有prvProcessTimerOrBlockTask函数可以让定时器任务进入阻塞状态,此时所有到期定时器已经处理,并且定时器命令队列中没有消息要处理
说明2:系统计时未溢出时,prvProcessTimerOrBlockTask处理流程伪代码
对应prvProcessTimerOrBlockTask函数中,没有发生定时器队列交换的场景,即if (xTimerListsWereSwitched == pdFALSE)条件成立的分支
*/
if (如果有定时器到期)
{
判断定时器到期时,下面2个条件必须同时满足,
1. xListWasEmpty == pdFALSE,也就是pxCurrentTimerList不为空
2. xNextExpireTime <= xTimeNow
需要满足第1个条件,是因为当pxCurrentTimerList为空时,xNextExpireTime会被
置为0,此时肯定也是满足第2个条件的
}
else
{
该分支对应没有定时器到期,如果此时定时器命令队列中没有消息,
则需要将定时器任务阻塞在定时器命令列表的等待接收列表中
此处更进一步,在pxCurrentTimerList为空的情况下,
又判断了pxOverflowTimerList是否为空,如果定时器溢出列表也为空的话,
后续的阻塞就可以设置为"死等"了,即加入任务挂起列表,而不是延时等待列表
static void prvProcessReceivedCommands( void )
{
DaemonTaskMessage_t xMessage;
Timer_t *pxTimer;
BaseType_t xTimerListsWereSwitched, xResult;
TickType_t xTimeNow;
// 在循环中,非阻塞地接收所有定时器命令
while( xQueueReceive( xTimerQueue, &xMessage, tmrNO_DELAY ) != pdFAIL )
{
// 判断定时器命令的有效性
if( xMessage.xMessageID >= ( BaseType_t ) 0 )
{
// 从定时器命令中得到定时器指针
pxTimer = xMessage.u.xTimerParameters.pxTimer;
// 如果定时器已经在定时器列表中,使之出队
if( listIS_CONTAINED_WITHIN( NULL,
&( pxTimer->xTimerListItem ) ) == pdFALSE )
{
( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
}
// 获取系统当前时间
// 期间可能因为计时溢出要交换定时器列表
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 :
// 将任务加入延时列表,如果超时,则直接调用定时器回调函数
if( prvInsertTimerInActiveList( pxTimer,
xMessage.u.xTimerParameters.xMessageValue +
pxTimer->xTimerPeriodInTicks, xTimeNow,
xMessage.u.xTimerParameters.xMessageValue ) != pdFALSE )
{
pxTimer->pxCallbackFunction(
( TimerHandle_t ) 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;
}
}
break;
case tmrCOMMAND_STOP :
case tmrCOMMAND_STOP_FROM_ISR :
// 之前已经将定时器从定时器列表中取出,
// 此处不再加入,就达到了停止定时器的作用
break;
case tmrCOMMAND_CHANGE_PERIOD :
case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
// 修改定时周期后,以当前时间为基准,
// 将定时器加入定时器列表
// 此时是不会发生定时器已到期的情况的
pxTimer->xTimerPeriodInTicks =
xMessage.u.xTimerParameters.xMessageValue;
configASSERT( ( pxTimer->xTimerPeriodInTicks > 0 ) );
( void ) prvInsertTimerInActiveList( pxTimer,
( xTimeNow + pxTimer->xTimerPeriodInTicks ), xTimeNow,
xTimeNow );
break;
case tmrCOMMAND_DELETE :
// 根据实际情况,释放动态分配内存的定时器
#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && \
( configSUPPORT_STATIC_ALLOCATION == 0 ) )
{
vPortFree( pxTimer );
}
#elif( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && \
( configSUPPORT_STATIC_ALLOCATION == 1 ) )
{
if( pxTimer->ucStaticallyAllocated ==
( uint8_t ) pdFALSE )
{
vPortFree( pxTimer );
}
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
break;
default:
/* Don't expect to get here. */
break;
}
}
}
}