FreeRTOS 定时器


概述

定时器的使用与定时器任务(Tmr Svc)服务的理解.
freertos的定时器功能是用task实现的,但是其命令却是用队列queue实现的.
一旦别的task中发送xMessage,可以导致prvTimerTask立刻从pxDelayedTaskList(等待列表)中加入pxReadyTasksLists(就绪列表).
由于Daemon优先级最高,因此可以立刻执行到prvTimerTask,通过接收xMessage的函数,根据xMessage.xMessageID,具体来处理命令。
在这里插入图片描述

一、定时器的数据结构

这是定时器的数据结构
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
定时器接收命令的格式:
包含两种格式,两种格式是以union的形式组织的,一种是对定时器开启、停止等操作,一种是执行回调函数
在这里插入图片描述

二、代码分析

1.创建时间任务xTimerCreateTimerTask

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();
}

2.相关指令信息

//中断函数调用
#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 )

3.启动定时器

// 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中发送定时器命令到定时器命令消息队列,之后由定时器任务接收并处理该命令,因此是一个异步处理流程

② 因为是异步处理,所以需要考虑实际处理定时器命令与发送定时器命令之间的时延

4.中断任务中启动定时器

与任务级调用相比,
① 不能带发送延时
② 需要通过pxHigherPriorityTaskWoken返回是否需要触发任务调度

#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) \
xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, \
( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )

5.修改定时值

//修改定时值传递的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 ) )

6.定时器任务

整体认知:调用关系

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是否为空,如果定时器溢出列表也为空的话,
    后续的阻塞就可以设置为"死等"了,即加入任务挂起列表,而不是延时等待列表

7.处理指令prvProcessReceivedCommands

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;
            }
        }
    }
}

总结

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值