freertos之timer浅析

2023-05-16

背景

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;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

freertos之timer浅析 的相关文章

  • setTimeout 的最小毫秒值是多少?

    我想把 var minValue 0 if typeof callback function setTimeout callback minValue 当我用 JavaScript 实现回调函数时 这段代码 但我发现现代浏览器和一些旧浏览器
  • 计时器和 JFrame 错误

    我正在制作一个带有计时器和 JFrame 的游戏 以及许多其他东西 但只有这两个会引起问题 在运行下面的片段后 我收到了一个奇怪的错误 至少对于之前从未使用过这些类的我来说是这样 开始执行这个 private static GameView
  • 如何实现带进度条的闪屏? - 安卓

    我在启动时有一个闪屏 与以下编码 public class Splash extends Activity Override protected void onCreate Bundle savedInstanceState TODO Au
  • 如果前一个线程仍然繁忙,如何让计时器跳过刻度

    我创建了一个 Windows 服务 该服务应该每 60 秒检查数据库中的某个表是否有新行 对于添加的每个新行 我都需要在服务器上进行一些繁重的处理 有时可能需要 60 秒以上的时间 我在服务中创建了一个 Timer 对象 该对象每 60 秒
  • 快速使函数中的计时器无效

    我正在尝试创建一个带有主比赛时钟和开始 停止按钮的曲棍球比赛时钟应用程序 但我的 stopGameclock 函数遇到了问题 计时器不会失效 通过在这里搜索其他问题 我认为这与我有关 var gameclockTimer NSTimer 接
  • 应如何管理函数范围内声明的计时器的清理?

    在下面的代码中 一个Timer在函数内部声明 它也订阅了Elapsed event void StartTimer System Timers Timer timer new System Timers Timer 1000 timer E
  • 如何在 iOS 7 中让 NSTimer 在后台运行超过 180 秒?

    我已经尝试过 但在 iOS 7 和 Xcode 4 6 2 中工作时间不超过 180 秒 请帮我 UIBackgroundTaskIdentifier bgTask UIBackgroundTaskInvalid UIApplication
  • .net 计时器有多可靠?

    我正在考虑在 Windows 服务中使用 System Timers Timer 我想知道它们的可靠性和准确性如何 尤其 对于它们的运行频率有任何保证吗 当处理器或内存过载时会发生什么 在这种情况下 ElapsedEventArgs Sig
  • 为什么 Asp.net 计时器/更新面板会在 Chrome 中泄漏内存?可以修复/解决吗?

    我为我们公司建立了一套内部网站来管理我们的一些流程 我注意到这些页面存在大量内存泄漏 导致页面使用超过 150mb 的内存 这对于由单个表单和 GridView 组成的网页来说是荒谬的 该 GridView 在同一时间显示 7 10 行数据
  • GNU Arm Cortex m4 上的 C++ 异常处理程序与 freertos

    2016 年 12 月更新现在还有一个关于此行为的最小示例 https community nxp com message 862676 https community nxp com message 862676 我正在使用带有 free
  • 定时器可以提早吗?

    显然 System Threading Timer 回调应该会延迟一点 然而 可以提前调用吗 例如 如果您启动秒表并安排计时器在 1000 毫秒内运行回调 那么秒表是否有可能在回调中显示 999 或者我们可以指望它必须显示 1000 或更多
  • 在特定时间段内使用 JavaScript 更改 HTML 元素的背景颜色?

    我有大约 26 个 html 元素 我希望它们在 JavaScript 中具有以下效果 有可能做这样的事情吗 我试图这样做 var j 2000 for i 1 i lt 26 i setInterval function document
  • 在 Swift 上设置计时器

    我尝试重复执行函数 pepe 我没有收到错误 但它不起作用 这是我的代码 public class MyClass var timer Timer objc func pepe gt String let hola hola return
  • Linux下如何用C实现定时器的回调函数

    我已经在许多论坛上搜索了几天可能的解决方案 但没有运气 我在这里发布我的问题 非常感谢您的回复 主意 使用脚本控制灯光 Linux下C语言 应用场景 我有三盏灯 红 蓝 绿 脚本有控制它们的时间表 例如 从现在起10秒后 亮红灯2秒 从现在
  • Clock_nanosleep() 尚不支持 CLOCK_MONOTONIC_RAW。这该如何处理呢?

    现在clock nanosleepDebian Jessie 上的 CLOCK MONOTONIC RAW 返回 EOPNOTSUPP 如何解决该问题并补偿可能应用于计时器循环中的 CLOCK MONOTONIC 的 NTP 调整 Is c
  • 如何在Android中制作倒计时器?

    我有两个 XML 格式的 EditText 在一个 EditText 中 用户可以输入一个数字作为分钟 在另一个 EditText 中输入一个数字作为秒 单击完成按钮后 EditText 应该开始倒计时并每秒更新其文本 此外 我怎样才能保持
  • 如何在我的 Lua 脚本中添加“睡眠”或“等待”?

    我正在尝试通过更改一天中的时间来为游戏制作一个简单的脚本 但我想快速完成 这就是我要说的 function disco hour minute setTime 1 0 SLEEP setTime 2 0 SLEEP setTime 3 0
  • Android相当于javascript的setTimeout和clearTimeout?

    setTimeout 有一个答案https stackoverflow com a 18381353 433570 https stackoverflow com a 18381353 433570 它没有提供我们是否可以像在 JavaSc
  • 停止从标准输入读取

    我正在用 LInux C 编写一个简单的控制台应用程序 它接受来自命令行的用户输入 我在用std getline std cin std cin gt gt text在一个线程中 10 秒后 我想停止接受控制台输入并写一条短信 然后做其他事
  • java定时器任务调度

    通过阅读 Stack Overflow 我发现很多人不建议使用 Timer Task 嗯 但我已经实现了这个 我有这个代码 detectionHandlerTimer schedule myTimerTask 60 1000 60 1000

随机推荐

  • 传递函数极点与微分方程的解

    如何解微分方程 setting y 61 e rx xff0c 点睛之笔
  • MPC控制

    基于状态空间模型的控制 模型预测控制 xff08 MPC xff09 简介 对基于状态空间模型的控制理解得很到位 在这里我重点讲解一下状态空间 模型 那么什么是状态 xff1f 输出是不是也是状态的一种 xff1f 对的 xff0c 输出也
  • @卡尔曼滤波理解

    Kalman Filter For Dummies 翻译 如何用卡尔曼滤波算法求解电池SOC xff08 基础篇 xff09 转载留存 卡尔曼滤波算法详细推导 这一篇对预备知识的介绍还是很好的 xff0c 过程与原理讲解也很到位 xff0c
  • 全景避障、VIO

    VINS Mono代码分析与总结 完整版 单目与IMU的融合可以有效解决单目尺度不可观测的问题 鱼眼摄像头SLAM xff1a PAN SLAM 全景相机SLAM 原论文 xff1a Panoramic SLAM from a multip
  • CAN总线-ACK应答机制分析

    1 xff1a 应答场定义 应答场长度为 2 个位 xff0c 包含应答间隙 xff08 ACK SLOT xff09 和应答界定符 xff08 ACK DELIMITER xff09 在应答场里 xff0c 发送站发送两个 隐性 位 当接
  • 树莓派4b 引脚图

    树莓派 4B 详细资料
  • 控制~线性系统~的能控性和能观性

    现控笔记 xff08 四 xff09 xff1a 能控性和能观性 能控性 xff1a 是控制作用u t 支配系统的状态向量x t 的能力 xff1b 回答u t 能否使x t 作任意转移的问题 能观性 xff1a 是系统的输出y t 反映系
  • 创建功能包

    创建功能包 xff1a catkin create pkg 在Amos WS src路径下 xff0c 打开控制台输入catkin create pkg my package std msgs rospy roscpp 创建一个名为my p
  • SLAM算法

    一 概述 Simultaneous Localization and Mapping SLAM 原本是Robotics领域用来做机器人定位的 xff0c 最早的SLAM算法其实是没有用视觉camera的 xff08 Robotics领域一般
  • 激光雷达入门

    转载自 xff1a https zhuanlan zhihu com p 33792450 前言 上一次的分享里 xff0c 我介绍了一个重要的感知传感器 摄像机 摄像机作为视觉传感器 xff0c 能为无人车提供丰富的感知信息 但是由于本身
  • 【超详细】韦东山:史上最全嵌入式Linux学习路线图

    我是1999年上的大学 xff0c 物理专业 在大一时 xff0c 我们班里普遍弥漫着对未来的不安 xff0c 不知道学习了物理后出去能做什么 你当下的经历 当下的学习 xff0c 在未来的一天肯定会影响到你 毕业后我们也各自找到了自己的职
  • ArUco码辅助定位——计算机视觉

    使用USB网络摄像头和ROS跟踪ArUco Markers
  • 基于D435i的点云重建

    Task 采用D435i采集深度图和RGB图像 xff0c 进行点云重建和聚类 1 xff09 解析Bag数据 xff1a import os import cv2 import numpy as np import rosbag from
  • vncviewer黑屏问题解决

    最近在重启服务器后 xff0c 用vnc进行远程桌面连接时 xff0c vnc能够连上 xff0c 或有提示错误 xff0c 或无提示错误 xff0c 但显示黑屏 在网上搜索了甚久 xff0c 各种google xff0c 各种baidu
  • Unbuntu 系统及VNC Viewer显示中文

    一行命令搞定 xff1a apt get install ttf wqy zenhei
  • 在嵌入式Linux系统上安装打印机

    简介 xff1a 在Linux环境中安装打印机 xff0c 通常是cups ghostscript等 xff0c 但体积通常很大 xff0c 几十兆 在我应用的环境 xff0c 要求打印模块不大于5M xff0c 在网上搜索的方案是将cup
  • 深度学习环境搭建:win10+GTX1060 + tensorflow1.5+keras+cuda9.0+cudnn7

    2018年 2月8日下午 xff0c 开始搭建环境 我新买了联想Y720笔记本电脑一台 xff0c 希望用它来开展深度学习的探索 根据之前的一点点经验 xff0c 搭建深度学习的环境 本篇博客主要记录的是流程 xff0c 不提供相关数据的下
  • Linux C/C++面试题汇总

    Linux C C 43 43 面试题汇总 前言计算机基础程序的内存空间进程和线程相关 关键字conststaticvolatile C C 43 43 指针 前言 最近面试的比较多 xff0c 看了很多关于面试的内容 xff0c 有些平时
  • NVIDIA TX2--3--NVIDIA Jetson TX2 查看系统版本参数状态及重要指令

    Yolov 1 TX2上用YOLOv3训练自己数据集的流程 VOC2007 TX2 GPU Yolov 2 一文全面了解深度学习性能优化加速引擎 TensorRT Yolov 3 TensorRT中yolov3性能优化加速 xff08 基于
  • freertos之timer浅析

    背景 freertos的定时器与我所见得到其他RTOS不一样 xff0c 我知道的ucosii是在每次tick 43 43 的时候会检查定时器链表 xff0c smc rtos也是这样做的 xff0c rtt没看过源码不清楚 xff0c 而