FreeRTOS--软件定时器

示例源码基于FreeRTOS V9.0.0

软件定时器

1. 概述

软件定时器用来在未来某个时间点执行某个动作,或者周期性地执行某个动作;软件定时器的实现依赖于系统滴答中断。

2. 设计原理

定时器分为单次定时器和周期定时器,定时时间到执行注册的回调函数。单次定时器触发后即睡眠(不会销毁,依旧可以操作),周期定时器则会周期性触发。

那么,由谁执行定时器?是由定时器任务(守护任务)执行

由谁操作定时器(创建,启动,删除,复位,修改)?是由用户任务或ISR执行

img

注:定时器的起始时间,从xTimerStart() 函数被调用时算起。而不是定时器任务执行start操作算起;

3. 代码实现

3.1 定时器结构

/* The definition of the timers themselves. */
typedef struct tmrTimerControl
{
	const char				*pcTimerName;		/*<< Text name.  This is not used by the kernel, it is included simply to make debugging easier. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	ListItem_t				xTimerListItem;		/*<< Standard linked list item as used by all kernel features for event management. */
	TickType_t				xTimerPeriodInTicks;/*<< How quickly and often the timer expires. */
	UBaseType_t				uxAutoReload;		/*<< Set to pdTRUE if the timer should be automatically restarted once expired.  Set to pdFALSE if the timer is, in effect, a one-shot timer. */
	void 					*pvTimerID;			/*<< An ID to identify the timer.  This allows the timer to be identified when the same callback is used for multiple timers. */
	TimerCallbackFunction_t	pxCallbackFunction;	/*<< The function that will be called when the timer expires. */
	#if( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t			uxTimerNumber;		/*<< An ID assigned by trace tools such as FreeRTOS+Trace */
	#endif

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t 			ucStaticallyAllocated; /*<< Set to pdTRUE if the timer was created statically so no attempt is made to free the memory again if the timer is later deleted. */
	#endif
} xTIMER;

/* The old xTIMER name is maintained above then typedefed to the new Timer_t
name below to enable the use of older kernel aware debuggers. */
typedef xTIMER Timer_t;
  • pcTimerName:定时器名称;
  • xTimerListItem:定时器链表节点;
  • xTimerPeriodInTicks:定时器周期,tick单位,表示多少个tick后触发执行定时任务;
  • uxAutoReload:pdFALSE表示是一次性定时器,pdTRUE表示是周期性定时器;
  • pvTimerID:定时器ID,回调函数参数,可用于回调函数内区别不同的定时器;
  • pxCallbackFunction:回调函数,定时时间到后触发执行;

3.2 定时器使用的几个全局变量

/* The list in which active timers are stored.  Timers are referenced in expire
time order, with the nearest expiry time at the front of the list.  Only the
timer service task is allowed to access these lists. */
PRIVILEGED_DATA static List_t xActiveTimerList1;
PRIVILEGED_DATA static List_t xActiveTimerList2;
PRIVILEGED_DATA static List_t *pxCurrentTimerList;
PRIVILEGED_DATA static List_t *pxOverflowTimerList;

/* A queue that is used to send commands to the timer service task. */
PRIVILEGED_DATA static QueueHandle_t xTimerQueue = NULL;
PRIVILEGED_DATA static TaskHandle_t xTimerTaskHandle = NULL;

FreeRTOS软件定时器的实现依赖于两个链表,一个队列:

  • 两个链表xActiveTimerList1和xActiveTimerList2存储不同的定时器,根据定时器的到期时间升序排列,首个链表节点即为最早到期的定时器,其xItemValue即为到期时间。两个链表一个为当前定时器链表,一个为溢出的定时器链表,指针pxCurrentTimerList和pxOverflowTimerList分别指向这两者,当current tick溢出时,两个链表进行切换(即指针切换指向);
  • 一个队列xTimerQueue用于接收来自其他任务或ISR发送的定时器操作指令,并处理。FreeRTOS提供的相关定时器API(启动、复位、删除、修改)等并不直接操作定时器,而是通过此队列发送操作,由定时器任务执行具体的操作;

3.3 定时器消息队列数据

/* The definition of messages that can be sent and received on the timer queue.
Two types of message can be queued - messages that manipulate a software timer,
and messages that request the execution of a non-timer related callback.  The
two message types are defined in two separate structures, xTimerParametersType
and xCallbackParametersType respectively. */
typedef struct tmrTimerParameters
{
	TickType_t			xMessageValue;		/*<< An optional value used by a subset of commands, for example, when changing the period of a timer. */
	Timer_t *			pxTimer;			/*<< The timer to which the command will be applied. */
} TimerParameter_t;


typedef struct tmrCallbackParameters
{
	PendedFunction_t	pxCallbackFunction;	/* << The callback function to execute. */
	void *pvParameter1;						/* << The value that will be used as the callback functions first parameter. */
	uint32_t ulParameter2;					/* << The value that will be used as the callback functions second parameter. */
} CallbackParameters_t;

/* The structure that contains the two message types, along with an identifier
that is used to determine which message type is valid. */
typedef struct tmrTimerQueueMessage
{
	BaseType_t			xMessageID;			/*<< The command being sent to the timer service task. */
	union
	{
		TimerParameter_t xTimerParameters;

		/* Don't include xCallbackParameters if it is not going to be used as
		it makes the structure (and therefore the timer queue) larger. */
		#if ( INCLUDE_xTimerPendFunctionCall == 1 )
			CallbackParameters_t xCallbackParameters;
		#endif /* INCLUDE_xTimerPendFunctionCall */
	} u;
} DaemonTaskMessage_t;

调用定时器API(启动、复位、删除、修改)的任务或ISR,通过构建DaemonTaskMessage_t消息,发往定时器消息队列xTimerQueue,由定时器任务执行操作;

其中:

  • xMessageID为具体的操作码,取值为tmrCOMMAND...等,见3.4.3介绍;
  • u为联合体,具体类型可为TimerParameter_t和CallbackParameters_t,根据不同的xMessageID取不同的类型。当xMessageID < 0(tmrCOMMAND_EXECUTE_CALLBACK和tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR)时,采用CallbackParameters_t类型(事件组的xEventGroupSetBitsFromISR接口即采用此类型),其他采用TimerParameter_t类型;

3.4 定时器的创建

3.4.1 xTimerCreate

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	TimerHandle_t xTimerCreate(	const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, 
		void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ) PRIVILEGED_FUNCTION; 
#endif

xTimerCreate用于动态创建定时器,定时器的结构Timer_t由pvPortMalloc动态申请。

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	TimerHandle_t xTimerCreate(	const char * const pcTimerName,
								const TickType_t xTimerPeriodInTicks,
								const UBaseType_t uxAutoReload,
								void * const pvTimerID,
								TimerCallbackFunction_t pxCallbackFunction ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
	Timer_t *pxNewTimer;

		pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) );

		if( pxNewTimer != NULL )
		{
			prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );

			#if( configSUPPORT_STATIC_ALLOCATION == 1 )
			{
				/* Timers can be created statically or dynamically, so note this
				timer was created dynamically in case the timer is later
				deleted. */
				pxNewTimer->ucStaticallyAllocated = pdFALSE;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */
		}

		return pxNewTimer;
	}

#endif /* configSUPPORT_STATIC_ALLOCATION */

函数主要做两件事:

  • 调用pvPortMalloc分配Timer_t结构;
  • 调用prvInitialiseNewTimer对定时器做初始化;

prvInitialiseNewTimer实现如下:

static void prvInitialiseNewTimer(	const char * const pcTimerName,
									const TickType_t xTimerPeriodInTicks,
									const UBaseType_t uxAutoReload,
									void * const pvTimerID,
									TimerCallbackFunction_t pxCallbackFunction,
									Timer_t *pxNewTimer ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
{
	/* 0 is not a valid value for xTimerPeriodInTicks. */
	configASSERT( ( xTimerPeriodInTicks > 0 ) );

	if( pxNewTimer != NULL )
	{
		/* Ensure the infrastructure used by the timer service task has been
		created/initialised. */
		prvCheckForValidListAndQueue();

		/* Initialise the timer structure members using the function
		parameters. */
		pxNewTimer->pcTimerName = pcTimerName;
		pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
		pxNewTimer->uxAutoReload = uxAutoReload;
		pxNewTimer->pvTimerID = pvTimerID;
		pxNewTimer->pxCallbackFunction = pxCallbackFunction;
		vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );
		traceTIMER_CREATE( pxNewTimer );
	}
}

prvInitialiseNewTimer内部对定时器结构体做初始化前,会调用prvCheckForValidListAndQueue,在首次创建定时器的同时,初始化定时器相关的链表和消息队列

static void prvCheckForValidListAndQueue( void )
{
	/* Check that the list from which active timers are referenced, and the
	queue used to communicate with the timer service, have been
	initialised. */
	taskENTER_CRITICAL();
	{
		if( xTimerQueue == NULL )
		{
			vListInitialise( &xActiveTimerList1 );
			vListInitialise( &xActiveTimerList2 );
			pxCurrentTimerList = &xActiveTimerList1;
			pxOverflowTimerList = &xActiveTimerList2;

			#if( configSUPPORT_STATIC_ALLOCATION == 1 )
			{
				/* The timer queue is allocated statically in case
				configSUPPORT_DYNAMIC_ALLOCATION is 0. */
				static StaticQueue_t xStaticTimerQueue;
				static uint8_t ucStaticTimerQueueStorage[ configTIMER_QUEUE_LENGTH * sizeof( DaemonTaskMessage_t ) ];

				xTimerQueue = xQueueCreateStatic( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ), &( ucStaticTimerQueueStorage[ 0 ] ), &xStaticTimerQueue );
			}
			#else
			{
				xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
			}
			#endif

			#if ( configQUEUE_REGISTRY_SIZE > 0 )
			{
				if( xTimerQueue != NULL )
				{
					vQueueAddToRegistry( xTimerQueue, "TmrQ" );
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			#endif /* configQUEUE_REGISTRY_SIZE */
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();
}

prvCheckForValidListAndQueue主要做如下处理:

  • 初始化xActiveTimerList1、xActiveTimerList2链表,并将pxCurrentTimerList指向xActiveTimerList1,将pxOverflowTimerList指向xActiveTimerList2;
  • 创建定时器队列xTimerQueue;
  • 调用vQueueAddToRegistry注册定时器队列"TmrQ"(可选,将队列写入全局数组xQueueRegistry[configQUEUE_REGISTRY_SIZE]中);

vQueueAddToRegistry定义在queue.c

#if ( configQUEUE_REGISTRY_SIZE > 0 )

    void vQueueAddToRegistry( QueueHandle_t xQueue, const char *pcQueueName ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
    {
    UBaseType_t ux;

        /* See if there is an empty space in the registry.  A NULL name denotes
        a free slot. */
        for( ux = ( UBaseType_t ) 0U; ux < ( UBaseType_t ) configQUEUE_REGISTRY_SIZE; ux++ )
        {
            if( xQueueRegistry[ ux ].pcQueueName == NULL )
            {
                /* Store the information on this queue. */
                xQueueRegistry[ ux ].pcQueueName = pcQueueName;
                xQueueRegistry[ ux ].xHandle = xQueue;

                traceQUEUE_REGISTRY_ADD( xQueue, pcQueueName );
                break;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    }

#endif /* configQUEUE_REGISTRY_SIZE */

3.4.2 xTimerCreateStatic

#if( configSUPPORT_STATIC_ALLOCATION == 1 )
	TimerHandle_t xTimerCreateStatic(	const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload,
		void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction, StaticTimer_t *pxTimerBuffer ) PRIVILEGED_FUNCTION; 
#endif /* configSUPPORT_STATIC_ALLOCATION */

xTimerCreateStatic用于静态创建定时器, 定时器的结构StaticTimer_t由函数外部事先分配。

#if( configSUPPORT_STATIC_ALLOCATION == 1 )

	TimerHandle_t xTimerCreateStatic(	const char * const pcTimerName,
										const TickType_t xTimerPeriodInTicks,
										const UBaseType_t uxAutoReload,
										void * const pvTimerID,
										TimerCallbackFunction_t pxCallbackFunction,
										StaticTimer_t *pxTimerBuffer ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
	Timer_t *pxNewTimer;

		#if( configASSERT_DEFINED == 1 )
		{
			/* Sanity check that the size of the structure used to declare a
			variable of type StaticTimer_t equals the size of the real timer
			structures. */
			volatile size_t xSize = sizeof( StaticTimer_t );
			configASSERT( xSize == sizeof( Timer_t ) );
		}
		#endif /* configASSERT_DEFINED */

		/* A pointer to a StaticTimer_t structure MUST be provided, use it. */
		configASSERT( pxTimerBuffer );
		pxNewTimer = ( Timer_t * ) pxTimerBuffer; /*lint !e740 Unusual cast is ok as the structures are designed to have the same alignment, and the size is checked by an assert. */

		if( pxNewTimer != NULL )
		{
			prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );

			#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
			{
				/* Timers can be created statically or dynamically so note this
				timer was created statically in case it is later deleted. */
				pxNewTimer->ucStaticallyAllocated = pdTRUE;
			}
			#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
		}

		return pxNewTimer;
	}

#endif /* configSUPPORT_STATIC_ALLOCATION */

函数主要调用prvInitialiseNewTimer对定时器进行初始化;

3.4.3 定时器操作

定时器支持启动、停止、修改周期、删除、复位的操作,通知支持在ISR(中断服务内)启动、停止、修改周期和复位。

// 启动定时器
#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )

// 停止定时器
#define xTimerStop( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )

// 修改定时器周期
#define xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD, ( xNewPeriod ), NULL, ( xTicksToWait ) )

// 删除定时器
#define xTimerDelete( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_DELETE, 0U, NULL, ( xTicksToWait ) )

// 复位定时器
#define xTimerReset( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )

// ISR内启动定时器
#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )
// ISR内停止定时器
#define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP_FROM_ISR, 0, ( pxHigherPriorityTaskWoken ), 0U )

// ISR内修改定时器周期
#define xTimerChangePeriodFromISR( xTimer, xNewPeriod, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_CHANGE_PERIOD_FROM_ISR, ( xNewPeriod ), ( pxHigherPriorityTaskWoken ), 0U )

// ISR内复位定时器
#define xTimerResetFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_RESET_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U )

可见,定时器的各类操作均是调用xTimerGenericCommand函数实现,区别在于参数的不同。通过参数xCommandID传递不同的操作码,实现不同的操作,操作码定义如下:

/* IDs for commands that can be sent/received on the timer queue.  These are to
be used solely through the macros that make up the public software timer API,
as defined below.  The commands that are sent from interrupts must use the
highest numbers as tmrFIRST_FROM_ISR_COMMAND is used to determine if the task
or interrupt version of the queue send function should be used. */
#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 )

xTimerGenericCommand函数实现如下:

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

	/* Send a message to the timer service task to perform a particular action
	on a particular timer definition. */
	if( xTimerQueue != NULL )
	{
		/* Send a command to the timer service task to start the xTimer timer. */
		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 );
		}

		traceTIMER_COMMAND_SEND( xTimer, xCommandID, xOptionalValue, xReturn );
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	return xReturn;
}

xTimerGenericCommand并没有直接操作定时器,而是构建了DaemonTaskMessage_t消息,将命令操作码,操作值,还有定时器句柄,发往定时器队列xTimerQueue,由定时器任务读取队列并执行相应操作;

以下为各个操作的option value值

command option value
tmrCOMMAND_START xTaskGetTickCount()
tmrCOMMAND_RESET xTaskGetTickCount()
tmrCOMMAND_START_FROM_ISR xTaskGetTickCountFromISR()
tmrCOMMAND_RESET_FROM_ISR xTaskGetTickCountFromISR()
tmrCOMMAND_CHANGE_PERIOD xNewPeriod
tmrCOMMAND_CHANGE_PERIOD_FROM_ISR xNewPeriod
tmrCOMMAND_STOP 0
tmrCOMMAND_DELETE 0
tmrCOMMAND_STOP_FROM_ISR 0

其中,xTaskGetTickCount()获取当前tick计数,xTaskGetTickCountFromISR()用于在ISR中获取当前tick计数;

3.4.4 定时器任务处理

在启动调度器的时候(vTaskStartScheduler(),定义在task.c),会同时创建定时器任务(如果开启了宏configUSE_TIMERS)。定时器任务的处理函数为prvTimerTask。

调用关系为:vTaskStartScheduler()-->xTimerCreateTimerTask()-->xTaskCreate(prvTimerTask, ...)

函数prvTimerTask的实现如下:

static void prvTimerTask( void *pvParameters )
{
TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;

	/* Just to avoid compiler warnings. */
	( void ) pvParameters;

	#if( configUSE_DAEMON_TASK_STARTUP_HOOK == 1 )
	{
		extern void vApplicationDaemonTaskStartupHook( void );

		/* Allow the application writer to execute some code in the context of
		this task at the point the task starts executing.  This is useful if the
		application includes initialisation code that would benefit from
		executing after the scheduler has been started. */
		vApplicationDaemonTaskStartupHook();
	}
	#endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */

	for( ;; )
	{
		/* Query the timers list to see if it contains any timers, and if so,
		obtain the time at which the next timer will expire. */
		xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );

		/* If a timer has expired, process it.  Otherwise, block this task
		until either a timer does expire, or a command is received. */
		prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );

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

定时器任务处理实现主要分3步:

  • prvGetNextExpireTime:获取定时器列表内最近定时器的到期时间,返回值xNextExpireTime为到期时间,参数xListWasEmpty返回定时器链表是否为空;
  • prvProcessTimerOrBlockTask:处理到期的定时器,或者阻塞定时器任务(等待定时器到期,或者定时器消息队列有数据到达);
  • prvProcessReceivedCommands:处理定时器消息队列内的数据;
3.4.4.1 prvGetNextExpireTime
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;
}

参数xListWasEmpty返回定时器链表是否为空;
函数返回值为到期时间,当链表为空时返回0;

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

函数实现如下:

  • 挂起调度器;
  • 调用prvSampleTimeNow获取当前tick,同时接口出参xTimerListsWereSwitched返回是否切换了定时器链表;
  • 如果切换了定时器链表,不做处理,恢复调度器后退出;
  • 如果没有切换定时器链表:
    • 链表非空,定时器已到期(( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow )),恢复调度器,并调用prvProcessExpiredTimer处理到期的定时器;
    • 链表为空,或者还没有定时器到期,调用vQueueWaitForMessageRestricted阻塞当前任务(等待定时器到期,或者定时器队列有数据到达),恢复调度器;

prvSampleTimeNow实现如下:

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

当前的tick通过xTaskGetTickCount()获取,而是否切换定时器链表,根据tick是否溢出判断(溢出时做切换);

prvSwitchTimerLists(切换定时器链表)实现如下:

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

	/* The tick count has overflowed.  The timer lists must be switched.
	If there are any timers still referenced from the current timer list
	then they must have expired and should be processed before the lists
	are switched. */
	while( listLIST_IS_EMPTY( pxCurrentTimerList ) == pdFALSE )
	{
		xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );

		/* Remove the timer from the list. */
		pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );
		( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
		traceTIMER_EXPIRED( pxTimer );

		/* Execute its callback, then send a command to restart the timer if
		it is an auto-reload timer.  It cannot be restarted here as the lists
		have not yet been switched. */
		pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );

		if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
		{
			/* Calculate the reload value, and if the reload value results in
			the timer going into the same timer list then it has already expired
			and the timer should be re-inserted into the current list so it is
			processed again within this loop.  Otherwise a command should be sent
			to restart the timer to ensure it is only inserted into a list after
			the lists have been swapped. */
			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;
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

	pxTemp = pxCurrentTimerList;
	pxCurrentTimerList = pxOverflowTimerList;
	pxOverflowTimerList = pxTemp;
}

在切换pxCurrentTimerList之前,需要先将pxCurrentTimerList内遗留的定时器删除,并执行定时器的回调函数。

对于周期性定时器:

  • 如果下一个周期到期时间溢出,通过往定时器队列发送tmrCOMMAND_START_DONT_TRACE操作,重新启动定时器;
  • 如果下一个周期到期时间没有溢出,重新将定时器插入pxCurrentTimerList链表,再次循环处理直至到期时间溢出;

即切换操作保证了,非周期定时器得到处理并移除,周期性定时器在到期时间溢出前得到处理,溢出后移入了另一定时器链表;

(TODO 没明白为什么不直接insert到OverflowTimerList,而是发送tmrCOMMAND_START_DONT_TRACE操作)

处理到期定时器(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 );

	/* 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 );
}

函数内移除了定时器,并执行了定时器的回调函数。对于周期性定时器,调用prvInsertTimerInActiveList处理;

prvInsertTimerInActiveList返回pdTRUE,表示即刻处理,否则,表示插入定时器链表;
这里当prvInsertTimerInActiveList返回pdTRUE时,发送tmrCOMMAND_START_DONT_TRACE操作,重新启动定时器(TODO 没看懂为什么这样操作);

static BaseType_t prvInsertTimerInActiveList( Timer_t * const pxTimer, const TickType_t xNextExpiryTime, const TickType_t xTimeNow, const TickType_t xCommandTime )
{
BaseType_t xProcessTimerNow = pdFALSE;

	listSET_LIST_ITEM_VALUE( &( pxTimer->xTimerListItem ), xNextExpiryTime );
	listSET_LIST_ITEM_OWNER( &( pxTimer->xTimerListItem ), pxTimer );

	if( xNextExpiryTime <= xTimeNow )
	{
		/* Has the expiry time elapsed between the command to start/reset a
		timer was issued, and the time the command was processed? */
		if( ( ( TickType_t ) ( xTimeNow - xCommandTime ) ) >= pxTimer->xTimerPeriodInTicks ) /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
		{
			/* The time between a command being issued and the command being
			processed actually exceeds the timers period.  */
			xProcessTimerNow = pdTRUE;
		}
		else
		{
			vListInsert( pxOverflowTimerList, &( pxTimer->xTimerListItem ) );
		}
	}
	else
	{
		if( ( xTimeNow < xCommandTime ) && ( xNextExpiryTime >= xCommandTime ) )
		{
			/* If, since the command was issued, the tick count has overflowed
			but the expiry time has not, then the timer must have already passed
			its expiry time and should be processed immediately. */
			xProcessTimerNow = pdTRUE;
		}
		else
		{
			vListInsert( pxCurrentTimerList, &( pxTimer->xTimerListItem ) );
		}
	}

	return xProcessTimerNow;
}

(TODO prvInsertTimerInActiveList的实现也没看懂)

  • (xNextExpiryTime <= xTimeNow) && ((xTimeNow - xCommandTime) >= pxTimer->xTimerPeriodInTicks)时,表示定时器到期?xProcessTimerNow=pdTRUE;
  • (xNextExpiryTime <= xTimeNow) && ((xTimeNow - xCommandTime) < pxTimer->xTimerPeriodInTicks)时,表示xNextExpiryTime溢出且定时器未到期?插入pxOverflowTimerList;
  • (xTimeNow < xCommandTime <= xNextExpiryTime) ,表示xTimeNow溢出?xProcessTimerNow=pdTRUE;
  • (xTimeNow < xNextExpiryTime < xCommandTime) || (xCommandTime <= xTimeNow < xNextExpiryTime),正常?插入pxCurrentTimerList;

vQueueWaitForMessageRestricted用于阻塞当前任务(直至有定时器到期,或者定时器队列有数据到达)

#if ( configUSE_TIMERS == 1 )

    void vQueueWaitForMessageRestricted( QueueHandle_t xQueue, TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely )
    {
    Queue_t * const pxQueue = ( Queue_t * ) xQueue;

        /* This function should not be called by application code hence the
        'Restricted' in its name.  It is not part of the public API.  It is
        designed for use by kernel code, and has special calling requirements.
        It can result in vListInsert() being called on a list that can only
        possibly ever have one item in it, so the list will be fast, but even
        so it should be called with the scheduler locked and not from a critical
        section. */

        /* Only do anything if there are no messages in the queue.  This function
        will not actually cause the task to block, just place it on a blocked
        list.  It will not block until the scheduler is unlocked - at which
        time a yield will be performed.  If an item is added to the queue while
        the queue is locked, and the calling task blocks on the queue, then the
        calling task will be immediately unblocked when the queue is unlocked. */
        prvLockQueue( pxQueue );
        if( pxQueue->uxMessagesWaiting == ( UBaseType_t ) 0U )
        {
            /* There is nothing in the queue, block for the specified period. */
            vTaskPlaceOnEventListRestricted( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait, xWaitIndefinitely );
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
        prvUnlockQueue( pxQueue );
    }

#endif /* configUSE_TIMERS */
// 定义在task.c
#if( configUSE_TIMERS == 1 )

	void vTaskPlaceOnEventListRestricted( List_t * const pxEventList, TickType_t xTicksToWait, const BaseType_t xWaitIndefinitely )
	{
		configASSERT( pxEventList );

		/* This function should not be called by application code hence the
		'Restricted' in its name.  It is not part of the public API.  It is
		designed for use by kernel code, and has special calling requirements -
		it should be called with the scheduler suspended. */


		/* Place the event list item of the TCB in the appropriate event list.
		In this case it is assume that this is the only task that is going to
		be waiting on this event list, so the faster vListInsertEnd() function
		can be used in place of vListInsert. */
		vListInsertEnd( pxEventList, &( pxCurrentTCB->xEventListItem ) );

		/* If the task should block indefinitely then set the block time to a
		value that will be recognised as an indefinite delay inside the
		prvAddCurrentTaskToDelayedList() function. */
		if( xWaitIndefinitely != pdFALSE )
		{
			xTicksToWait = portMAX_DELAY;
		}

		traceTASK_DELAY_UNTIL( ( xTickCount + xTicksToWait ) );
		prvAddCurrentTaskToDelayedList( xTicksToWait, xWaitIndefinitely );
	}

#endif /* configUSE_TIMERS */

实际调用:

vTaskPlaceOnEventListRestricted( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait, xWaitIndefinitely );

vQueueWaitForMessageRestricted在定时器队列为空时,调用vTaskPlaceOnEventListRestricted->prvAddCurrentTaskToDelayedList将任务阻塞,阻塞时间为vQueueWaitForMessageRestricted的实参xNextExpireTime - xTimeNow,即等待下个定时器到期的tick时间,如果定时器链表为空,则永久阻塞直到定时器队列有数据(启动新定时器是往队列发送指令实现的,所以队列有数据且为start类型时才会往定时器链表插入定时器实例);

等待的事件链表为pxQueue->xTasksWaitingToReceive,定时器任务阻塞后,其他任务如果启动定时器,会调用xTimerGenericCommand,内部调用关系为:

xTimerGenericCommand->xQueueSendToBack->xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToReceive))->prvAddTaskToReadyList

这样,会重新将定时器任务唤醒,就绪等待执行;

3.4.4.3 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 ) /*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 )
			{
				/* 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;
			}
		}
	}
}

prvProcessReceivedCommands用于处理定时器队列内的数据,数据即为定时器操作指令(启动、复位、停止、删除、修改);

函数内部循环处理队列内的所有数据,读取的阻塞时间为tmrNO_DELAY,即不阻塞。

  • 对于操作码xMessageID < 0,即tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR和tmrCOMMAND_EXECUTE_CALLBACK,直接执行参数指定的回调函数,事件组的xEventGroupSetBitsFromISR实现就用了这种;
  • 对于操作码xMessageID >= 0,即定时器操作相关的操作码,不管什么操作,先把当前定时器从链表移除,然后:
    • 对于启动和复位,调用prvInsertTimerInActiveList,如果返回pdTRUE,立即执行定时器回调。如果是周期性定时器,调用xTimerGenericCommand重新往队列发送启动操作(发送的option value需要根据周期修改),等下次循环执行;
    • 对于停止,不做操作,因为定时器已经不在链表内了;
    • 对于修改(修改到期时间,或是周期时间),调用prvInsertTimerInActiveList,修改到期时间后,重新将定时器插入链表;
    • 对于删除,调用vPortFree直接释放定时器的内存结构;

以上可见,对于定时器,必须发送tmrCOMMAND_DELETE操作才会销毁,即调用xTimerDelete。即使是单次定时器,到期执行完毕后,只要不调用xTimerDelete,就不会销毁,依旧可以根据定时器句柄操作(比如再次启动),它只是睡眠而已。

参考链接

百问网-FreeRTOS入门与工程实践-基于STM32F103-第16章 软件定时器(software timer)

posted @ 2024-02-03 14:44  流翎  阅读(296)  评论(0编辑  收藏  举报