FreeRTOS 教程指南 学习笔记 第五章 软件计时器

FreeRTOS 教程指南 学习笔记 第五章 软件计时器

一、简介

软件计时器用于在未来设定的时间,或以固定的频率定期安排功能的执行。由软件计时器执行的函数称为软件计时器的回调函数。
软件计时器由FreeRTOS内核实现,并在其控制之下。它们不需要硬件支持,也与硬件计时器或硬件计数器无关。
请注意,根据FreeRTOS使用创新设计以确保最大效率的理念,软件计时器不占用任何处理时间,除非软件计时器回调函数实际执行。
软件计时器功能是可选的。要使用软件计时器功能:

  1. 构建FreeRTOS源文件 FreeRTOS/Source/timers.c作为项目的一部分。
  2. 在FreeRTOSConfig.h中将configUSE_TIMERS设置为1。

本章旨在让读者很好地理解:

  • 软件计时器的特征与任务的特征相比。
  • RTOS守护进程任务。
  • 计时器命令队列。
  • 一次性软件计时器和周期性软件计时器之间的区别。
  • 如何创建、启动、重置和更改软件计时器的时间周期。

二、软件计时器回调函数

软件计时器回调函数被实现为C函数。它们唯一的特别之处在于它们的原型,它必须返回void,并将一个软件计时器的句柄作为其唯一的参数。回调函数原型如清单72所示。

//Listing 72. The software timer callback function prototype
void ATimerCallback( TimerHandle_t xTimer );

软件计时器回调函数从开始执行到结束尾执行,并以正常方式退出。它们应保持短小,并且不能进入阻塞状态。
注意:正如将看到的,软件计时器回调函数在FreeRTOS调度程序启动时自动创建的任务上下文中执行。因此,软件计时器回调函数必须不能调用FrereRTOS的API函数,这将导致调用任务进入阻塞状态。但只有当函数的xTicksToWait参数(指定函数的块时间)设置为0的时候,可以调用函数如xQueueReceive()。调用像vTaskDelay()这样的函数是不允许的,因为调用vTaskDelay()总是会将调用任务置于阻塞状态。

三、软件计时器的属性和状态

软件计时器的时间周期

软件计时器的“周期”是指从软件计时器被启动到软件计时器的回调函数被执行之间的时间。

一次性和自动重载计时器

有两种类型的软件计时器:

  1. 一次性计时器
    一旦启动,一次性计时器将只执行其回调函数一次。一次性计时器可以手动重新启动,但不会重新启动。
  2. 自动重载计时器
    一旦启动,一个自动重载计时器将在每次到期时重新启动自己,从而导致定期执行其回调函数。

图38显示了一次性计时器和自动重载计时器之间的行为差异。虚线表示了滴答中断发生的时间。
在这里插入图片描述
参见图38:

  • 计时器1
    计时器1是一个一次性计时器,周期为6次滴答。它在时间t1开始,所以它的回调函数在时间t7之后执行6个标记。由于计时器1是一次性计时器,所以它的回调函数不会再次执行。
  • 计时器2
    Timer 2是一个自动重新加载计时器,其周期为5个滴度。它在时间t1开始,所以它的回调函数在时间t1之后每5次标记执行一次。在图38中,这是在时刻t6、t11和t16。
软件计时器状态

一个软件计时器可以处于以下两种状态之一:

  • 休眠
    一个休眠软件计时器存在,并且可以被它的句柄引用,但没有运行,所以它的回调函数将不会执行。
  • 运行
    一个正在运行的软件计时器将在其启动后或者最近一次被重置后,并完成一个周期,则执行其回调函数。

图39和图40分别显示了自动重载计时器和一次性计时器的休眠状态和运行状态之间的可能转换。这两个图之间的关键区别是在计时器过期后输入的状态;自动重载计时器执行它的回调函数,然后重新进入运行状态,一次性计时器执行它的回调函数,然后进入休眠状态。
xTimerDelete()API函数将删除一个计时器。一个定时器可以随时被删除。
在这里插入图片描述

四、一个软件计时器的上下文

RTOS守护进程(计时器服务)任务

所有软件定时器回调函数都在同一个RTOS守护(或“定时器服务”)任务的上下文中执行。
守护任务是一个标准的FreeRTOS任务,它在调度程序启动时自动创建。它的优先级和栈大小分别由configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH编译时配置常量设置。这两个常量都是在FreeRTOSConfig.h中定义的。
软件定时器回调函数不能调用将导致调用任务进入阻止状态的函数FreeRTOS API,因为这样做将导致守护进程任务进入阻止状态。

计时器命令队列

软件计时器API函数将来命令通过一个名为“计时器命令队列”的队列,从函数的调用任务发送到守护任务。这一点如图41所示。命令的例子包括“启动计时器”、“停止计时器”和“重置计时器”。
计时器命令队列是一个标准的FreeRTOS队列,它在调度程序启动时自动创建。计时器命令队列的长度是由FreeRTOSConfig.h中的configTIMER_QUEUE_LENGTH编译时配置常量设置的。
在这里插入图片描述

守护进程任务调度

守护任务像任何其他FreeRTOS任务一样被调度;当它是能够运行的最高优先级任务时,它只会处理命令,或执行计时器回调函数。图42和图43演示了configTIMER_TASK_PRIORITY设置如何影响执行模式。
图42显示了当守护进程任务的优先级低于调用xTimerStart()API函数的任务的优先级时的执行模式。
在这里插入图片描述
参见图42,其中Task 1的优先级高于守护任务的优先级,守护任务的优先级高于空闲任务的优先级:

  1. 时间t1
    任务1处于“运行”状态,守护进程任务处于“阻止”状态。
    如果一个命令被发送到计时器命令队列,守护进程任务将离开阻塞状态,在这种情况下,它将处理该命令,或者如果一个软件计时器超时,在这种情况下,它将执行软件计时器的回调函数。
  2. 时间t2
    任务1调用xTimerStart()。
    xTimerStart()向定时器命令队列发送命令,导致守护进程任务离开“阻止”状态。任务1的优先级高于守护进程任务的优先级,因此守护进程任务不会抢占任务1。
    任务1仍处于“运行”状态,守护进程任务已离开“阻止”状态并进入“就绪”状态。
  3. 时间t3
    任务1完成执行xTimerStart()API函数。任务1执行了xTimerStart(),从函数开始到函数结束,而不离开运行状态。
  4. 时间t4
    Task 1调用一个API函数,导致其进入阻塞状态。守护进程任务现在是处于已就绪状态下的最高优先级任务,因此调度程序选择守护进程任务作为进入正在运行状态的任务。然后,守护进程任务开始处理由任务1发送到计时器命令队列的命令。
    注意:启动软件计时器将过期的时间是从“启动计时器”命令发送到计时器命令队列开始计算的——它不是从守护进程任务从计时器命令队列收到“启动计时器”命令开始计算的。
  5. 时间t5
    守护进程任务已经完成了由任务1发送给它的命令的处理,并尝试从计时器命令队列接收更多的数据。计时器命令队列为空,因此守护进程任务将重新进入“已阻止”状态。如果命令发送到定时器命令队列,或者软件定时器过期,守护进程任务将再次离开“阻止”状态。
    空闲任务现在是处于“就绪”状态下的最高优先级任务,因此调度程序选择空闲任务作为进入“正在运行”状态的任务。

图43显示了与图42所示的类似的场景,但这次守护进程任务的优先级高于调用xTimerStart()的任务的优先级。
在这里插入图片描述

参见图43,其中守护进程任务的优先级高于任务1的优先级,任务1的优先级高于空闲任务的优先级:

  1. 时间t1
    与前面一样,任务1处于运行状态,守护进程任务处于“阻止”状态。
  2. 时间t2
    任务1调用xTimerStart()。
    xTimerStart()向定时器命令队列发送命令,导致守护进程任务离开“阻止”状态。守护进程任务的优先级高于任务1的优先级,因此调度程序选择守护进程任务作为进入正在运行的状态的任务。
    任务1在完成执行xTimerStart()函数之前已被守护进程任务抢占,现在处于“就绪”状态。
    守护进程任务开始处理由任务1发送到计时器命令队列的命令。
  3. 时间t3
    守护进程任务已经完成了由任务1发送给它的命令的处理,并尝试从计时器命令队列接收更多的数据。计时器命令队列为空,因此守护进程任务将重新进入“已阻止”状态。
    任务1现在是处于“已就绪”状态下的最高优先级任务,因此调度程序选择任务1作为要进入“正在运行”状态的任务。
  4. 时间t4
    任务1在完成执行xTimerStart()函数之前被守护任务抢占,并且只在重新进入运行状态后退出(返回)xTimerStart()。
  5. t5时刻
    Task 1调用一个API函数,导致其进入阻塞状态。空闲任务现在是处于“就绪”状态下的最高优先级任务,因此调度程序选择空闲任务作为进入“正在运行”状态的任务。

在图42所示的场景中,任务1向计时器命令队列发送命令,到守护进程任务接收和处理该命令,之间传递的时间。在图43所示的场景中,守护进程任务在任务1从发送该命令的函数返回之前,已经接收并处理了Task 1发送给它的命令。
发送到计时器命令队列的命令包含一个时间戳。时间戳用于计算,在应用程序任务正在发送的命令,到守护进程任务处理此命令,之间的时间间隔。例如,如果发送一个“启动计时器”命令来启动一个周期为10个tick的计时器,则时间戳用于确保这10个tick是应用程序从发送命令开始,而不是在守护进程任务处理命令后开始的的第10个tick。

五、创建和启动一个软件计时器

The xTimerCreate() API Function

软件计时器必须显式创建,然后才能使用。
软件计时器由TimerHandle_t类型的变量引用。xTimerCreate()用于创建一个软件计时器,并返回一个TimerHandle_t来引用它创建的软件计时器。软件计时器是在休眠状态下创建的。
软件计时器可以在调度程序运行之前创建,也可以在调度程序启动后从任务中创建。

//Listing 73. The xTimerCreate() API function prototype
TimerHandle_t xTimerCreate( const char * const pcTimerName,
 							TickType_t xTimerPeriodInTicks,
 							UBaseType_t uxAutoReload,
 							void * pvTimerID,
 							TimerCallbackFunction_t pxCallbackFunction );
/*参数pcTimerName:计时器的一个描述性名称。FreeRTOS没有以任何方式使用它。它纯粹作为调试辅助工具。用人类可读的名称来识别一个计时器比试图用其句柄来识别它要简单得多。*/
/*参数xTimerPeriodInTicks:以ticks指定的计时器周期。pdMS_TO_TICKS()宏可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。*/
/*参数uxAutoReload:将uxAutoReload设置为pdTRUE,以创建一个自动重载计时器。将uxAutoReload设置为pdFALSE,以创建一次性计时器。*/
/*参数pvTimerID:每个软件计时器都有一个ID值。ID是一个void型指针,应用程序编写器可以用于任何目的。当相同的回调函数被多个软件计时器使用时,该ID特别有用,因为它可以用于提供特定于计时器的存储。在本章中的一个示例中演示了计时器ID的使用。pvTimerID为正在创建的任务的ID设置一个初始值。*/
/*参数pxCallbackFunction:软件计时器回调函数只是C函数,它们符合清单72中所示的原型函数。pxCallbackFunction参数是指向回调函数(实际上只是函数名)的指针。*/
/*返回值:如果返回NULL,则无法创建软件计时器,因为FreeRTOS没有足够的堆内存来分配必要的数据结构。返回的非NULL值表示软件计时器已成功创建。返回的值是所创建的计时器的句柄。第2章提供了关于堆内存管理的更多信息。*/
The xTimerStart() API Function

xTimerStart()用于启动处于休眠状态的软件计时器,或重置(重新启动)处于正在运行状态的软件计时器。xTimerStop()用于停止处于“正在运行”状态的软件计时器。停止软件计时器就等于将计时器转换为休眠状态。
可以在调度器启动之前调用xTimerStart(),但这样完成后,软件计时器直到调度器启动时才会实际启动。
注意:永远不要从中断服务例程调用xTimerStart()。应该使用中断安全版本xTimerStartFromISR()。

//Listing 74. The xTimerStart() API function prototype
BaseType_t xTimerStart ( TimerHandle_t xTimer, TickType_t xTicksToWait );
/*参数xTimer:正在启动或重置的软件计时器的句柄。该句柄将从调用xTimerCreate()时返回,用于创建软件计时器。*/
/*参数xTicksToWait: xTimerStart()使用计时器命令队列发送“启动计时器”命令到守护进程任务。xTicksToWait指定调用任务在队列已满时,保持在阻塞状态等待计时器命令队列上空间可用的最大时间。
					如果xTicksToWait为零,且计时器命令队列已满,则xTimerStart()将立即返回。
					阻塞时间以滴答周期指定,因此它所表示的绝对时间取决于滴答频率。宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。
					如果INCLUDE_vTaskSuspend在FreeRTOSConfig.h中的设置为1,那么将xTicksToWait设置为portMAX_DELAY将导致调用任务无限期地处于阻塞状态(没有超时),等待计时器命令队列中的空间可用。
					如果在调度程序启动之前调用xTimerStart(),则将忽略,并且xTimerStart()的行为就好像“开始等待”已设置为零*/
/*返回值:有两个可能的返回值:
         1.pdPASS只有当“启动一个计时器”命令被成功地发送到计时器命令队列时,pdPASS才会被返回。如果守护进程任务的优先级高于调用xTimerStart()的任务的优先级,那么调度程序将确保在xTimerStart()返回之前处理启动命令。这是因为一旦定时器命令队列中有数据,守护进程任务就会抢占调用xTimerStart()的任务。如果指定了阻塞时间(xTicksToWait不是零),则可能将调用任务放入阻塞状态,等待函数返回之前计时器命令队列中的可用空间,但数据在阻塞时间过期之前成功写入计时器命令队列。
         1. 如果“启动计时器”命令无法写入计时器命令队列,则将返回pdFALSE pdFALSE。如果指定了阻塞时间(xTicksToWait不是零),那么调用任务将被放置入阻塞状态,等待守护进程任务在计时器命令队列中腾出空间,但指定的阻塞时间在发生之前已经过期。*/
Example 13. Creating one-shot and auto-reload timers
//Listing 75. Creating and starting the timers used in Example 13
/* The periods assigned to the one-shot and auto-reload timers are 3.333 second and half a 
second respectively. */
#define mainONE_SHOT_TIMER_PERIOD pdMS_TO_TICKS( 3333 )
#define mainAUTO_RELOAD_TIMER_PERIOD pdMS_TO_TICKS( 500 )
int main( void )
{
	TimerHandle_t xAutoReloadTimer, xOneShotTimer;
	BaseType_t xTimer1Started, xTimer2Started;
 	/* Create the one shot timer, storing the handle to the created timer in xOneShotTimer. */
 	xOneShotTimer = xTimerCreate( 
 								/* Text name for the software timer - not used by FreeRTOS. */
 								"OneShot", 
 								/* The software timer's period in ticks. */
 								mainONE_SHOT_TIMER_PERIOD,
 								/* Setting uxAutoRealod to pdFALSE creates a one-shot software timer. */
 								pdFALSE, 
 								/* This example does not use the timer id. */
 								0, 
 								/* The callback function to be used by the software timer being created. */
 								prvOneShotTimerCallback ); 
 	/* Create the auto-reload timer, storing the handle to the created timer in xAutoReloadTimer. */
 	xAutoReloadTimer = xTimerCreate( 
 								/* Text name for the software timer - not used by FreeRTOS. */
 								"AutoReload", 
 								/* The software timer's period in ticks. */
 								mainAUTO_RELOAD_TIMER_PERIOD, 
 								/* Setting uxAutoRealod to pdTRUE creates an auto-reload timer. */
 								pdTRUE, 
 								/* This example does not use the timer id. */
 								0, 
 								/* The callback function to be used by the software timer being created. */
 								prvAutoReloadTimerCallback ); 
 	/* Check the software timers were created. */
 	if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
 	{
 		/* Start the software timers, using a block time of 0 (no block time). The scheduler has not been started yet so any block time specified here would be ignored anyway. */
 		xTimer1Started = xTimerStart( xOneShotTimer, 0 );
 		xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );
 		/* The implementation of xTimerStart() uses the timer command queue, and xTimerStart() will fail if the timer command queue gets full. The timer service task does not get  created until the scheduler is started, so all commands sent to the command queue will  stay in the queue until after the scheduler has been started. Check both calls to  xTimerStart() passed. */
 		if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
 		{
 			/* Start the scheduler. */
 			vTaskStartScheduler();
 		}
 	}
 /* As always, this line should not be reached. */
 for( ;; );
}

static void prvOneShotTimerCallback( TimerHandle_t xTimer )
{
	TickType_t xTimeNow;
 	/* Obtain the current tick count. */
 	xTimeNow = xTaskGetTickCount();
 	/* Output a string to show the time at which the callback was executed. */
 	vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );
 	/* File scope variable. */
 	ulCallCount++;
}

static void prvAutoReloadTimerCallback( TimerHandle_t xTimer )
{
	TickType_t xTimeNow;
 	/* Obtain the current tick count. */
 	xTimeNow = uxTaskGetTickCount();
 	/* Output a string to show the time at which the callback was executed. */
 	vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );
 	ulCallCount++;
}

执行此示例将生成图44所示的输出。图44显示了自动重加载计时器的回调函数,执行周期为500(清单75中mainAUTO_RELOAD_TIMER_PERIOD设置为500),一次性计时器的回调函数为3333(mainONE_SHOT_TIMER_PERIOD为3333,清单75中设置为3333)。
在这里插入图片描述

六、计时器ID

每个软件计时器都有一个ID,它是应用程序编写器可以用于任何目的的标记值。ID存储在一个空指针(void*)中,因此可以直接存储一个整数值,指向任何其他对象,或用作函数指针。
在创建软件计时器时,为ID分配一个初始值之后,可以使用vTimerSetTimerID()API函数更新ID,并使用pvTimerGetTimerID()API函数进行查询。
与其他软件计时器API功能不同,vTimerSetTimerID()和pvTimerGetTimerID()直接访问软件计时器——它们不会向计时器命令队列发送命令。

The vTimerSetTimerID() API Function
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );
/*参数xTimer:正在使用新的ID值更新的软件计时器的句柄。该句柄将从调用xTimerCreate()时返回,用于创建软件计时器。*/
/*参数pvNewID:将设置软件计时器ID的值。*/
The pvTimerGetTimerID() API Function
void *pvTimerGetTimerID( TimerHandle_t xTimer );
/*参数:正在查询的软件计时器的句柄。该句柄将从调用xTimerCreate()时返回,用于创建软件计时器。*/
/*返回值:正在查询的软件计时器的ID。*/
Example 14. Using the callback function parameter and the software timer ID

相同的回调函数可以分配给多个软件计时器。完成后,回调函数参数用于确定哪个软件计时器已过期。
示例13使用了两个单独的回调函数;一个回调函数被一次性计时器使用,另一个回调函数被自动重载计时器使用。示例14为两个软件计时器分配了同一个回调函数。

/* Create the one shot timer software timer, storing the handle in xOneShotTimer. */
xOneShotTimer = xTimerCreate( "OneShot", 
 								mainONE_SHOT_TIMER_PERIOD,
 								pdFALSE,
 								/* The timer’s ID is initialized to 0. */
 								0, 
 								/* prvTimerCallback() is used by both timers. */
 								prvTimerCallback ); 
/* Create the auto-reload software timer, storing the handle in xAutoReloadTimer */
xAutoReloadTimer = xTimerCreate( "AutoReload", 
 								mainAUTO_RELOAD_TIMER_PERIOD, 
 								pdTRUE,
 								/* The timer’s ID is initialized to 0. */ 
 								0, 
 								/* prvTimerCallback() is used by both timers. */
 								prvTimerCallback );

static void prvTimerCallback( TimerHandle_t xTimer )
{
	TickType_t xTimeNow;
	uint32_t ulExecutionCount;
 	/* A count of the number of times this software timer has expired is stored in the timer's ID. Obtain the ID, increment it, then save it as the new ID value. The ID is a void pointer, so is cast to a uint32_t. */
 	ulExecutionCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
 	ulExecutionCount++;
 	vTimerSetTimerID( xTimer, ( void * ) ulExecutionCount );
 	/* Obtain the current tick count. */
 	xTimeNow = xTaskGetTickCount();
 	/* The handle of the one-shot timer was stored in xOneShotTimer when the timer was created. Compare the handle passed into this function with xOneShotTimer to determine if it was the  one-shot or auto-reload timer that expired, then output a string to show the time at which  the callback was executed. */
 	if( xTimer == xOneShotTimer )
 	{
 		vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );
 	}
 	else
 	{
 		/* xTimer did not equal xOneShotTimer, so it must have been the auto-reload timer that expired. */
 		vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );
 		if( ulExecutionCount == 5 )
 		{
 			/* Stop the auto-reload timer after it has executed 5 times. This callback function executes in the context of the RTOS daemon task so must not call any functions that  might place the daemon task into the Blocked state. Therefore a block time of 0 is  used. */
 			xTimerStop( xTimer, 0 );
 		}
 	}
 }

在这里插入图片描述

七、更改计时器的时间周期

The xTimerChangePeriod() API Function

使用xTimerChangePeriod()功能更改软件计时器的周期。
如果使用xTimerChangePeriod()来更改已经运行的计时器的周期,则计时器将使用新的周期值来重新计算其到期时间。重新计算的过期时间相对于调用xTimerChangePeriod()的时间,而不是相对于计时器最初启动的时间。
如果使用xTimerChangePeriod()来更改处于休眠状态的计时器(未运行的计时器),则计时器将计算到期时间,并过渡到运行状态(计时器将开始运行)。
注意:不要从中断服务例程调用xTimerChangePeriod()。应该使用中断安全版本的xTimerChangePeriodFromISR()来代替它。

//Listing 82. The xTimerChangePeriod() API function prototype
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewTimerPeriodInTicks,TickType_t xTicksToWait );
/*参数xTimer:正在用一个新的周期值更新的软件计时器的句柄。句柄将从调用xTimerCreate()用于创建软件计时器*/
/*参数xNewTimerPeriodInTicks:软件计时器的新周期,以刻号指定。pdMS_TO_TICKS()宏可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。*/
/*参数xTicksToWait:()使用计时器命令队列将“更改进程”命令发送到守护进程任务。xTicksToWait指定调用任务在队列已满时,保持在阻塞状态等待计时器命令队列上空间可用的最大时间。
					如果xTicksToWait为零且计时器命令队列已满,则xTimerChangePeriod()将立即返回。
					宏pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为在刻度中指定的时间。
					如果INCLUDE_vTaskSuspend在FreeRTOSConfig.h中设置为1,那么将xTicksToWait设置为portMAX_DELAY将导致调用任务无限期地处于阻塞状态(没有超时),等待计时器命令队列中的空间可用。
					如果在调度程序启动之前调用xTimerChangePeriod(),则忽略时刻等待的值,并且xTimerChangePeriod()的行为好像时刻等待被设置为零。*/
/*返回值:有两个可能的返回值:
		1.pdPASS:只有当数据被成功地发送到计时器命令队列时,才会返回pdPASS。如果指定了阻塞时间(xTicksToWait不是零),则可能将调用任务放入阻塞状态,等待函数返回之前计时器命令队列中的可用空间,但数据在阻塞时间过期之前成功写入计时器命令队列。
		2.pdFALSE:如果“更改期间”命令无法写入计时器命令队列,则将返回pdFALSE。如果指定了阻塞时间(xTicksToWait不是零),那么调用任务将被放入“阻塞”状态,以等待守护进程任务在队列中腾出空间,但指定的阻塞时间在此状态发生之前已过期。*/

清单83显示了在软件计时器回调函数中包含自检功能的FreeRTOS示例如何使用xTimerChangePeriod()来提高在自检失败时LED切换的速率。执行自检的软件计时器被称为“检查计时器”。

/* The check timer is created with a period of 3000 milliseconds, resulting in the LED toggling every 3 seconds. If the self-checking functionality detects an unexpected state, then the check timer’s period is changed to just 200 milliseconds, resulting in a much faster toggle rate. */
const TickType_t xHealthyTimerPeriod = pdMS_TO_TICKS( 3000 );
const TickType_t xErrorTimerPeriod = pdMS_TO_TICKS( 200 );
/* The callback function used by the check timer. */
static void prvCheckTimerCallbackFunction( TimerHandle_t xTimer )
{
	static BaseType_t xErrorDetected = pdFALSE;
 	if( xErrorDetected == pdFALSE )
 	{
 		/* No errors have yet been detected. Run the self-checking function again. The function asks each task created by the example to report its own status, and also checks  that all the tasks are actually still running (and so able to report their status correctly). */
 		if( CheckTasksAreRunningWithoutError() == pdFAIL )
 		{
 			/* One or more tasks reported an unexpected status. An error might have occurred. Reduce the check timer’s period to increase the rate at which this callback function  executes, and in so doing also increase the rate at which the LED is toggled. This  callback function is executing in the context of the RTOS daemon task, so a block  time of 0 is used to ensure the Daemon task never enters the Blocked state. */
 			xTimerChangePeriod( xTimer, /* The timer being updated. */
 								xErrorTimerPeriod, /* The new period for the timer. */
 								0 ); /* Do not block when sending this command. */
 		}
 		/* Latch that an error has already been detected. */
 		xErrorDetected = pdTRUE;
 	}
 /* Toggle the LED. The rate at which the LED toggles will depend on how often this function is called, which is determined by the period of the check timer. The timer’s period will  have been reduced from 3000ms to just 200ms if CheckTasksAreRunningWithoutError() has ever  returned pdFAIL. */
 ToggleLED();
}

重置软件计时器

重置软件计时器意味着重新启动计时器;==计时器的到期时间被重新计算为相对于重置计时器的时间,而不是计时器最初启动的时间。==图46演示了这一点,它显示了一个计时器,其启动周期为6,然后重置两次,然后最终到期并执行其回调函数。
在这里插入图片描述
参见图46:

  • 计时器1在时间t1启动。它的周期为6,所以它执行回调函数的时间最初计算为t7,即它启动后的6个刻度。
  • 计时器1在到达时间t7之前被重置,所以在它过期并执行其回调函数之前。计时器1在时间t5被重置,因此它将执行其回调函数的时间被重新计算为t11,即重置后的6个刻度。
  • 计时器1在时间t11之前再次重置,所以在它过期并执行其回调函数之前再次重置。计时器1在时间t9被重置,因此它将执行其回调函数的时间被重新计算为t15,这是它最后一次重置后的6个刻度。
  • 计时器1不会再次重置,所以它在t15到期,它的回调函数相应执行。
The xTimerReset() API Function

使用xTimerReset()API函数重置计时器。
xTimerReset()也可以用来启动一个处于休眠状态的计时器。
注意:永远不要从中断服务例程调用xTimerReset()。应该使用中断安全版本的xTimerResetFromISR()来代替它。

//Listing 84. The xTimerReset() API function prototype
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
/*参数xTimer:正在重置或正在启动的软件定时器的句柄。该句柄将从调用xTimerCreate()时返回,用于创建软件计时器。*/
/*参数xTicksToWait:xTimerChangePeriod()使用计时器命令队列将“重置”命令发送给守护进程任务。xTicksToWait指定调用任务在队列已满时,保持在阻塞状态等待计时器命令队列上空间可用的最大时间。
					如果xTicksToWait为零,并且计时器命令队列已经满,则定xTimerReset()将立即返回。
					如果INCLUDE_vTaskSuspend在FreeRTOSConfig.h中的设置为1,那么将xTicksToWait设置为portMAX_DELAY将导致调用任务无限期地处于阻塞状态(没有超时),等待计时器命令队列中的空间可用。 */
/*返回值:有两个可能的返回值:
		1.只有当数据成功发送到定时器命令队列才会返回到pdPASS。如果指定了块时间(xTicksToWait不是零),则可能将调用任务放入阻塞状态,等待函数返回之前计时器命令队列中的可用空间,但数据在块时间过期之前成功写入计时器命令队列。
		2.如果无法将“reset”命令写入计时器命令队列,因为该队列已满,则将返回pdFALSE。如果指定了块时间(xTicksToWait不是零),那么调用任务将被放入“阻塞”状态,以等待守护进程任务在队列中腾出空间,但指定的块时间在此状态发生之前已过期。*/
Example 15. Resetting a software timer

本示例模拟了手机上的背光灯的行为。背光灯:

  • 当按下一个键时,灯就会打开。
  • 灯光保持亮一断时间,等待下次按键。
  • 如果在一定时间内没有按键,则会自动关闭。

一个一次性的软件计时器用于实现此行为:

  • [模拟]背光在按键按下时被打开,并在软件计时器的回调功能中关闭。
  • 每次按下一个键时,软件定时器将被重置。
  • 因此,必须按下一个键以防止背光被关闭的时间段等于软件计时器的周期;如果在计时器到期前没有通过按键重置软件计时器,则计时器的回调功能执行,背光被关闭。
static void prvBacklightTimerCallback( TimerHandle_t xTimer )
{
	TickType_t xTimeNow = xTaskGetTickCount();
 	/* The backlight timer expired, turn the backlight off. */
 	xSimulatedBacklightOn = pdFALSE;
 	/* Print the time at which the backlight was turned off. */
 	vPrintStringAndNumber("Timer expired, turning backlight OFF at time\t\t", xTimeNow );
}

static void vKeyHitTask( void *pvParameters )
{
	const TickType_t xShortDelay = pdMS_TO_TICKS( 50 );
	TickType_t xTimeNow;
 	vPrintString( "Press a key to turn the backlight on.\r\n" );
 	/* Ideally an application would be event driven, and use an interrupt to process key presses. It is not practical to use keyboard interrupts when using the FreeRTOS Windows  port, so this task is used to poll for a key press. */
 	for( ;; )
 	{
 		/* Has a key been pressed? */
 		if( _kbhit() != 0 )
 		{
 			/* A key has been pressed. Record the time. */
 			xTimeNow = xTaskGetTickCount();
 			if( xSimulatedBacklightOn == pdFALSE )
 			{
 				/* The backlight was off, so turn it on and print the time at which it was turned on. */
 				xSimulatedBacklightOn = pdTRUE;
 				vPrintStringAndNumber("Key pressed, turning backlight ON at time\t\t", xTimeNow );
 			}
 			else
 			{
 				/* The backlight was already on, so print a message to say the timer is about to be reset and the time at which it was reset. */
 				vPrintStringAndNumber("Key pressed, resetting software timer at time\t\t", xTimeNow );
 			}
 			/* Reset the software timer. If the backlight was previously off, then this call will start the timer. If the backlight was previously on, then this call will restart the timer. A real application may read key presses in an interrupt. If this function was an interrupt service routine then xTimerResetFromISR() must be used instead of xTimerReset(). */
 			xTimerReset( xBacklightTimer, xShortDelay );
 			/* Read and discard the key that was pressed – it is not required by this simple example. */
 			( void ) _getch();
 		}
 	}
}

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值