freertos之timer浅析

背景

freertos的定时器与我所见得到其他RTOS不一样,我知道的ucosii是在每次tick++的时候会检查定时器链表,smc_rtos也是这样做的,rtt没看过源码不清楚,而freertos是将定时器实现为一个prvTimerTask。

代码分析

freertos是将定时器实现为一个prvTimerTask,一般如果定时n个tick,就会将prvTimerTask阻塞n个tick,但是同时加入xTimerQueue的等待接收列表,如果没有到n个tick时间,但是接收到了xTimerQueue发送的消息就会了唤醒。一般情况下还是会阻塞n个tick,具体n各tick之后是否执行,跟prvTimerTask的任务优先级有关系;如果将prvTimerTask优先级设置得较高,在阻塞之后一般会立刻得到执行,但是如果将prvTimerTask优先级设置得特别低的话,就会导致n个tick之后,prvTimerTask虽然加入到ready_list,但是由于优先级不够无法得到准时执行。

定时器的数据结构

这是定时器的数据结构,

typedef struct tmrTimerControl
{
	const char				*pcTimerName;		
	/*定时器名称,内核其实没有使用,仅仅是方便调试而已*/
	ListItem_t				xTimerListItem;		
	/*所有内核特性用于事件管理的标准链表项。*/
	TickType_t				xTimerPeriodInTicks;
	/*计时器的时间和频率。 */
	UBaseType_t				uxAutoReload;		
	/*如果计时器在过期后应该自动重启,则设置为pdTRUE。
	如果计时器实际上是一次性计时器,则设置为pdFALSE*/
	void 					*pvTimerID;			
	/*标识计时器的ID。这允许在对多个计时器使用相同回调时标识计时器。*/
	TimerCallbackFunction_t	pxCallbackFunction;	
	/*定时器定时时间到之后调用这个函数 */
} xTIMER;

这是定时器接收命令的格式,包含两种格式,两种格式是以union的形式组织的,一种是对定时器开启、停止等操作,一种是执行回调函数

typedef struct tmrTimerParameters
{
	TickType_t			xMessageValue;		
	/*可以选的值,是一系列命令 */
	Timer_t *			pxTimer;			
	/*具体是哪一个定时器 */
} TimerParameter_t;

typedef struct tmrCallbackParameters
{
	PendedFunction_t	pxCallbackFunction;	
	/* 会被执行的回调函数*/
	void *pvParameter1;	
	uint32_t ulParameter2;
} CallbackParameters_t;

/* 包含这两种消息类型以及标识符的结构用于确定哪个消息类型是有效的,
一个是执行定时器start 、reset、stop类型操作的,一个是执行回调函数的 */
typedef struct tmrTimerQueueMessage
{
	BaseType_t			xMessageID;			
	/*被发送到timer task的命令 */
	union
	{
		TimerParameter_t xTimerParameters;

		/* 如果这个宏不是1的话,不要加入下面这个结构每一位会让这个结构变得很大 */
		#if ( INCLUDE_xTimerPendFunctionCall == 1 )
			CallbackParameters_t xCallbackParameters;
		#endif 
	} u;
} DaemonTaskMessage_t;

对了,定时器也是和xDelayedTaskList一样采用的双list的机制解决了tick溢出的问题,在计算本timer溢出时间之后如果超过了tick最大值,就会加入到pxOverflowTimerList中,否则就加入到pxCurrentTimerList中,这些都会在下面具体代码分析中体现。

/* 存储活动计时器的列表。计时器在过期时引用时间顺序,
最近的过期时间在列表的前面。只有计时器服务任务允许访问这些列表。*/
PRIVILEGED_DATA static List_t xActiveTimerList1;
PRIVILEGED_DATA static List_t xActiveTimerList2;
PRIVILEGED_DATA static List_t *pxCurrentTimerList;
PRIVILEGED_DATA static List_t *pxOverflowTimerList;

定时器代码分析

  • 定时器任务的创建
    在上面我们就说过,freertos软件定时器是用task实现的。
    vTaskStartScheduler启动调度器的函数中,如果是使能configUSE_TIMERS定时器开启宏的话,就会执行xTimerCreateTimerTask定时器任务创建函数,定时器任务会自动执行,不需要用户去创建
    这是简化的vTaskStartScheduler
void vTaskStartScheduler( void ){
#if ( configUSE_TIMERS == 1 ){
		if( xReturn == pdPASS ){
			xReturn = xTimerCreateTimerTask();
		}else{
			mtCOVERAGE_TEST_MARKER();
		}
	}
#endif /* configUSE_TIMERS */
}

下面是定时器创建任务的代码,在内部区分了静态还是动态分配任务tcb、stack:

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 );
		}
		#endif /* configSUPPORT_STATIC_ALLOCATION */
	}else{
		mtCOVERAGE_TEST_MARKER();
	}
	configASSERT( xReturn );
	return xReturn;
}
  • 定时器创建
    跟其他数据结构一样分为动态创建和静态创建
TimerHandle_t xTimerCreate(	const char * const pcTimerName,
								const TickType_t xTimerPeriodInTicks,
								const UBaseType_t uxAutoReload,
								void * const pvTimerID,
								TimerCallbackFunction_t pxCallbackFunction );
					
TimerHandle_t xTimerCreateStatic(	const char * const pcTimerName,
									const TickType_t xTimerPeriodInTicks,
									const UBaseType_t uxAutoReload,
									void * const pvTimerID,
									TimerCallbackFunction_t pxCallbackFunction,
									StaticTimer_t *pxTimerBuffer );

在区别就是Timer_t的结构是动态分配的,还是静态的,在有了Timer_t结构之后都会同样调用prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );来初始化创建的定时器。
定时器在创建之后不会立刻执行,需要人为地手动开启。其中xTimerPeriodInTicks是跟定时器定时时间相关的,uxAutoReload是跟会不会自动重装载相关的,只有设置为True才会自动重装载,否则只执行一次。

  • 定时器的命令
    freertos的定时器功能是用task实现的,但是其命令却是用队列queue实现的,这个队列的消息是上面讲到的DaemonTaskMessage_t类型,在别的task中发送xMessage,可以导致prvTimerTask立刻从pxDelayedTaskList中加入pxReadyTasksLists,但是执行是否要看prvTimerTask的优先级了,一旦执行到prvTimerTask,就会转去执行接收xMessage的函数,根据xMessage.xMessageID,具体来处理命令。

命令主要是下面几种,分为三类,一类是callback类,一类是真正的定时器命令,还有一类是可以从中断函数中发送的定时器命令。

/*可以在计时器队列上发送/接收的命令的id。这些是只能通过组成公共软件计时器API的宏
使用,如下定义。从中断发送的命令必须使用使用tmrFIRST_FROM_ISR_COMMAND
这样的最高数字来确定任务是否完成或者使用中断版本的队列发送函数。*/
#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 )

命令的发送如下,消息包含了确定的定时器xTimer,确定的命令xCommandID,确定的消息值xOptionalValue,在接收函数中解析消息之后会对xTimer执行xCommandID的命令操作。

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;
	/* 向计时器服务任务发送消息以在指定定时器执行相应的操作。 */
	if( xTimerQueue != NULL ){
		xMessage.xMessageID = xCommandID;
		xMessage.u.xTimerParameters.xMessageValue = xOptionalValue;
		xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer;

		if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ){//根据xCommandID 判断不是从中断中发送的
			if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ){//如果调度器是在运行中,可阻塞地去发送消息
				xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait );
			}else{//不可阻塞地去发送消息
				xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY );
			}
		}else{
			xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );
		}
		traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn );
	}else{
		mtCOVERAGE_TEST_MARKER();
	}
	return xReturn;
}

命令的接收如下,可以看出,如果是执行回调函数的命令,就会先执行函数然后就结束了,
如果是定时器命令,不管是从中断发出的消息还是普通的消息,都会先将命令中指定的定时器从定时器列表中移除(前提是如果在列表中的话);然后执行xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );,在prvSampleTimeNow中会更新现在的tick值,如果tick溢出等,会立即处理溢出,并更换两个定时器列表;然后根据xMessage.xMessageID具体值去做对应处理,其实分为四种命令,分别是开启定时器、停止定时器、更改定时时间、删除定时器。
在开启定时器的时候还要考虑在这之前定时器是否已经过期了,如果过期还要作相应的处理,但是更改定时器时间则不需要看之前是否已经过期(我只能说是这样处理的,这样做的原因我也不清楚,可以留言告知哈);停止操作的话已经不需要干活了,因为之前已经将他从定时器列表中移除了;删除定时器则需要根据是不是动态分配的内存决定是否vportfree释放内存。这个函数只有在prvTimerTask中被调用了一次。

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 ( INCLUDE_xTimerPendFunctionCall == 1 ){
			/* 负的xMessageID命令是挂起的函数调用,而不是计时器命令。*/
			if( xMessage.xMessageID < ( BaseType_t ) 0 ){
				const CallbackParameters_t * const pxCallback = &( xMessage.u.xCallbackParameters );
				/* 调用这个函数 */
				pxCallback->pxCallbackFunction( pxCallback->pvParameter1, pxCallback->ulParameter2 );
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		#endif /* INCLUDE_xTimerPendFunctionCall */

		/*正的xMessageID命令是定时器命令,而不是挂起的命令函数调用。*/
		if( xMessage.xMessageID >= ( BaseType_t ) 0 ){
			/* 这些消息使用xTimerParameters成员处理软件计时器。*/
			pxTimer = xMessage.u.xTimerParameters.pxTimer;

			if( listIS_CONTAINED_WITHIN( NULL, &( pxTimer->xTimerListItem ) ) == pdFALSE ){
				/* 定时器在定时器列表中,删除它。*/
				( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
			}else{
				mtCOVERAGE_TEST_MARKER();
			}
			/* 在这种情况下,不使用xTimerListsWereSwitched参数,但是它必须出现在函数调用中。
			prvSampleTimeNow()函数 必须在xTimerQueue接收到消息后调用,
			因此没有向消息中添加消息的高优先级任务的可能性在计时器守护进程任务(因为它)
			之前排队在设置xTimeNow值之后抢占计时器守护进程任务)。*/
			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 :
					/* 定时器已经被从列表中移除了,所以这里noting to do*/
					break;

				case tmrCOMMAND_CHANGE_PERIOD :
				case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
					pxTimer->xTimerPeriodInTicks = xMessage.u.xTimerParameters.xMessageValue;
					configASSERT( ( pxTimer->xTimerPeriodInTicks > 0 ) );

					/* 新的定时器过期时间没有参考,而且可以比旧定时器的长或者短。
					command time因此要设置为当前时间,并且因为定时器时间都是不为0的,
					所以下一次定时器过期时间肯定在之后,
					这意味着(与上面的xTimerStart()案例不同)存在这里没有需要处理的失败案例。*/
					( 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 );
						}else{
							mtCOVERAGE_TEST_MARKER();
						}
					}
					#endif 
					break;

				default	:
					break;
			}
		}
	}
}
  • 定时器任务
    下面来分析prvTimerTask任务。
static void prvTimerTask( void *pvParameters ){
TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;
	
	for( ;; ){
		/*查询计时器列表,看它是否包含计时器,如果包含计时器,
		获取下一个计时器将到期的时间。*/
		xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );

		/* 如果计时器已过期,请处理它。
		否则,阻塞此任务直到计时器过期或接收到命令为止。*/
		prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );

		/* Empty the command queue. */
		prvProcessReceivedCommands();
	}
}

一般task都是一个死循环,prvTimerTask也不例外,在这个循环中首先获取下一次定时器过期时间,就是prvGetNextExpireTime函数。
在这个函数中获取下一次定时器过期时间其实是通过定时器列表第一个列表项的值来确定的,可以这样做的前提是,定时器列表中所有列表项都是按过期时间来排列的,且是从小到大。

static TickType_t prvGetNextExpireTime( BaseType_t * const pxListWasEmpty )
{
TickType_t xNextExpireTime;

	/*计时器是按过期时间顺序列出的,位于列表的开头的定时器会先过期。
	获得时间最近过期的计时器的过期时间。
	如果没有活动的定时器的话,将下一个过期时间设置为0。
	这将导致当滴答计数溢出时,此任务将解除阻塞,
	此时计时器列表将被切换,下一个过期时间可以重新。*/
	*pxListWasEmpty = listLIST_IS_EMPTY( pxCurrentTimerList );
	if( *pxListWasEmpty == pdFALSE ){//在不为空的情况下来获取下一次定时器过期时间,否则为0
		xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
	}else{
		/*确保tick溢出时任务解除阻塞。*/
		xNextExpireTime = ( TickType_t ) 0U;
	}
	return xNextExpireTime;
}

然后在prvProcessTimerOrBlockTask中检查下一次函数过期tick时间,与当前tick时间比较,如果定时器过期则处理,如果没有过期的定时器就将自己添加到命令队列中去,且阻塞到下一次定时器过期的时间,将自己添加到延时任务列表中去,定时器任务再次被添加到ready任务列表的条件是 阻塞时间到 或者在阻塞期间 命令队列收到了定时器消息。
下面是prvProcessTimerOrBlockTask源码

static void prvProcessTimerOrBlockTask( const TickType_t xNextExpireTime, 
										BaseType_t xListWasEmpty ){
TickType_t xTimeNow;
BaseType_t xTimerListsWereSwitched;

	vTaskSuspendAll();
	{
		/*获取现在的时间,对计时器是否计时进行评估是否过期。如果获取时间会导致列表切换(这里是指发生tick溢出),
		那就不要处理这个定时器,因为所有定时器都会在prvSampleTimeNow()函数中被处理。*/
		xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
		if( xTimerListsWereSwitched == pdFALSE )
		{/*tick没有溢出*/
			/*滴答计数没有溢出,计时器是否过期呢?*/
			if( ( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow ) )
			{/*有定时器已经过期*/
				( void ) xTaskResumeAll();
				prvProcessExpiredTimer( xNextExpireTime, xTimeNow );//主要的处理定时器溢出的函数
			}else{/*没有定时器过期*/
				/*tick计数没有溢出,下一次过期时间还没有到。因此,这项任务应该阻塞,
				以等待下一个过期时间或者收到一个定时器命令-以先到者为准。
				以下代码不能被执行,除非xNextExpireTime > xTimeNow当前计时器列表为空时的情况。*/
				if( xListWasEmpty != pdFALSE ){
					/*当前计时器列表为空,那么定时器溢出列表也是空的吗?之前我也很疑惑为什么还要判断
					另一个定时器列表是否为空,其实在下面vQueueWaitForMessageRestricted函数下面,
					如果另一个定时器列表也为空的话,那阻塞次任务的时间就为port_MAX了,主要是为了提升效率吧*/
					xListWasEmpty = listLIST_IS_EMPTY( pxOverflowTimerList );
				}
				/*在这个函数中会调用task.c中更加下层的函数vTaskPlaceOnEventListRestricted,
				将本task加入到xTimerQueue中的等待列表中去,这样一旦有定时器消息被发送到xTimerQueue列表中去,
				本task就会唤醒,然后将阻塞本任务自身,阻塞时间是下一个定时器过期的时候,
				如果两个定时器列表都为空,那就阻塞时间设为最大值。*/
				vQueueWaitForMessageRestricted( xTimerQueue, 
						( xNextExpireTime - xTimeNow ), xListWasEmpty );

				if( xTaskResumeAll() == pdFALSE ){
					/*进行任务切换*/
					portYIELD_WITHIN_API();
				}else{
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}else{//如果tick已经溢出了,则所有的处理在prvSampleTimeNow已经完成了。
			( void ) xTaskResumeAll();
		}
	}
}

下面看一下在prvProcessTimerOrBlockTask中调用的两个重要的函数源码
prvProcessExpiredTimer

/*** 主要的定时器处理函数*/
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 );

	/* 从活动计时器列表中删除计时器。之前一些操作已经保证了列表不为空。*/
	( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
	/* 如果计时器是自动重新加载计时器,则计算下一个计时器过期时间,
	并重新将计时器插入活动计时器列表中。*/
	if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE ){
		/* 定时器被插入了一个  i 和任何时间相关的 而不是现在时间的列表中。
		因此,它将被插入了相对于时间的正确的列表。
		就是按过期时间重新插入到定时器列表中去*/
		if( prvInsertTimerInActiveList( pxTimer, ( xNextExpireTime + pxTimer->xTimerPeriodInTicks ),
										xTimeNow, xNextExpireTime ) != pdFALSE ){
			/* 计时器在添加到活动计时器列表之前已过期。现在重新加载。*/
			xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xNextExpireTime, NULL, tmrNO_DELAY );
		}else{
			mtCOVERAGE_TEST_MARKER();
		}
	}else{
		mtCOVERAGE_TEST_MARKER();
	}
	pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
}
void vQueueWaitForMessageRestricted( QueueHandle_t xQueue, 
					TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely ){
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;

		/* 应用程序代码不应调用此函数,因此名字里写着“受限制的”。
		它不是公共API的一部分。它是设计用于内核代码,并有特殊的调用要求。
		它可能导致在只能调用的列表上调用vListInsert()可能会有一个项目,
		所以列表会很快,但甚至因此,应该在锁定调度器的情况下调用它,
		而不是从关键调度器调用部分。*/

		/* 只有在队列中没有消息时才执行任何操作。这个函数不会导致任务阻塞,
		只是把它放在阻塞上列表。它将不会阻塞,直到调度程序被解锁-在那里时间将执行良率。
		如果一个项目被添加到队列中队列被锁定,调用任务在队列上阻塞,然后当队列被解锁时,
		调用任务将被立即解除阻塞。*/
		prvLockQueue( pxQueue );
		if( pxQueue->uxMessagesWaiting == ( UBaseType_t ) 0U ){
			/* 队列中没有任何内容的话,prvTimerTask加入到xTimerQueue 等待接收列表中去,
			并阻塞prvTimerTask指定的时间。*/
			vTaskPlaceOnEventListRestricted( &( pxQueue->xTasksWaitingToReceive ), 
											xTicksToWait, xWaitIndefinitely );
		}
		prvUnlockQueue( pxQueue );
	}

void vTaskPlaceOnEventListRestricted( List_t * const pxEventList, 
								TickType_t xTicksToWait, 
								const BaseType_t xWaitIndefinitely ){
	/* 将TCB的事件列表项放在适当的事件列表中。在这种情况下,假设这是唯一要做的任务
	正在等待这个事件列表,所以更快的vListInsertEnd()函数可以用来代替vListInsert。*/
	/*将本prvTimerTask的xEventListItem  加入到xTimerQueue 的等待接收列表中去*/
	vListInsertEnd( pxEventList, &( pxCurrentTCB->xEventListItem ) );

	/* 如果任务应该无限期地阻塞,那么将阻塞时间设置为一个值,该值将被传给
	prvAddCurrentTaskToDelayedList()函数。*/
	if( xWaitIndefinitely != pdFALSE ){//这个只有在两个定时器列表都为空的时候才会执行
		xTicksToWait = portMAX_DELAY;
	}
	/*将本任务prvTimerTask从ready_list移除,加入到等待列表(可能有两种)中去,
	阻塞xTicksToWait个tick*/
	prvAddCurrentTaskToDelayedList( xTicksToWait, xWaitIndefinitely );
}

最后是prvProcessReceivedCommands();,清空定时器命令列表,这个函数在上面定时器命令已经分析过。

其他函数分析

prvSwitchTimerLists函数是tick溢出之后必须要得操作,软件定时器设计两个定时器列表的目的就是在于解决tick溢出的问题的,这个函数代码体现的就是how来解决tick溢出问题的。

static void prvSwitchTimerLists( void )
{
TickType_t xNextExpireTime, xReloadTime;
List_t *pxTemp;
Timer_t *pxTimer;
BaseType_t xResult;

	/* tick计数已溢出。必须切换计时器列表。如果当前计时器列表中仍然引用计时器
	那么它们一定已经过期了,应该在列表切换之前进行处理。如果列表不为空,就要一直处理*/
	while( listLIST_IS_EMPTY( pxCurrentTimerList ) == pdFALSE ){
		xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );

		/* 将定时器从定时器列表移除 */
		pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );
		( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
		traceTIMER_EXPIRED( pxTimer );

		/* 执行它的回调,然后发送一个命令重新启动计时器,如果
		它是一个自动重新加载计时器。它不能在这里作为列表重新启动还没有转换。*/
		pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
		if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
		{
			/*计算重载值,以及重载值是否导致进入相同计时器列表的计时器已经过期
			计时器应该重新插入到当前列表中在此循环中再次处理。
			否则,应该发送命令重新启动计时器,以确保它只在之后插入到列表中列表已经交换了。*/
			xReloadTime = ( xNextExpireTime + pxTimer->xTimerPeriodInTicks );
			if( xReloadTime > xNextExpireTime )
			{
				listSET_LIST_ITEM_VALUE( &( pxTimer->xTimerListItem ), xReloadTime );
				listSET_LIST_ITEM_OWNER( &( pxTimer->xTimerListItem ), pxTimer );
				vListInsert( pxCurrentTimerList, &( pxTimer->xTimerListItem ) );
			}else{
				xResult = xTimerGenericCommand( pxTimer, tmrCOMMAND_START_DONT_TRACE, xNextExpireTime, NULL, tmrNO_DELAY );
				configASSERT( xResult );
				( void ) xResult;
			}
		}
	}
	/*在处理完溢出列表上的定时器之后,才是真正的列表切换操作*/
	pxTemp = pxCurrentTimerList;
	pxCurrentTimerList = pxOverflowTimerList;
	pxOverflowTimerList = pxTemp;
}

定时器函数中有时候需要得到当前的tick数值与最近得到定时器过期时间比较,就使用prvSampleTimeNow这个函数,巧妙地使用本次xTimeNow和上次xLastTime 的大小就可以得出是否tick溢出,如果溢出就会直接切换定时器列表,就是上面这个函数

/*如果现在tick溢出了,交换两个定时器链表并且处理当前定时器链表中的所有定时器*/
static TickType_t prvSampleTimeNow( BaseType_t * const pxTimerListsWereSwitched )
{
TickType_t xTimeNow;
PRIVILEGED_DATA static TickType_t xLastTime = ( TickType_t ) 0U; /*lint !e956 Variable is only accessible to one task. */

	xTimeNow = xTaskGetTickCount();

	if( xTimeNow < xLastTime )
	{
		prvSwitchTimerLists();
		*pxTimerListsWereSwitched = pdTRUE;
	}
	else
	{
		*pxTimerListsWereSwitched = pdFALSE;
	}

	xLastTime = xTimeNow;

	return xTimeNow;
}
  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeRTOS的定时器是通过一个名为prvTimerTask的任务来实现的,与其他RTOS的实现方式不太一样。在每次系统的tick时,FreeRTOS会检查定时器链表来确定是否有定时器需要触发。 用户需要自行分配所需的内存来创建定时器。可以使用函数xTimerCreateStatic来创建定时器,该函数的参数包括定时器的名称、定时周期、是否自动重载、定时器ID、定时器服务函数以及保存定时器结构体的缓冲区。函数会返回定时器句柄,创建成功则返回句柄,否则返回NULL。 定时器的数据结构包括定时器名称、内核特性链表项、定时周期、是否自动重载、定时器ID以及定时器服务函数等字段。这些字段用于管理定时器的属性和行为。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [freertostimer浅析](https://blog.csdn.net/qq_33894122/article/details/84866270)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【FreeRTOS】软件定时器](https://blog.csdn.net/qq_47713364/article/details/119811802)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值