FreeRTOS笔记篇:第五章 --软件定时

测试环境如下

stm32F103C8T6
MDK keil5
stm32cube + FreeRTOS

概述

软件定时器用于在未来设定的时间内,或以固定的频率周期性地安排函数的执行。 由软件定时器执行的函数称为软件定时器的回调函数

软件定时器由Free RTOS内核实现并控制。 它们不需要硬件支持,也与硬件定时器或硬件计数器无关。

请注意,按照自由RTOS使用创新设计以确保最大效率的理念,除非实际执行软件定时器回调函数,否则软件定时器不使用任何处理时间。

软件定时器功能可选。 要包含软件定时器功能

  • 构建Free RTOS源文件Free RTOS/Source/timers.c作为项目的一部分。
  • 在Free RTOSConfig.h中将configUSE_TIMERS设置为1。

在这里插入图片描述

读者

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

软件定时器回调函数 ATimerCallback()

注意:可以看到,软件计时器回调函数在任务的上下文中执行,该任务是在启动Free RTOS调度程序时自动创建的。 因此,软件定时器回调函数绝不能调用FreeRTOSAPI函数,这将导致调用任务进入阻塞状态。 调用函数如xQueue Receive()是可以的,但只有当函数的xTick to Wait参数(它指定函数的块时间)设置为0时才可以。 调用 vTaskDelay()等函数是不可以的,因为 **vTaskDelay()**总是将调用任务放置到阻塞状态

软件计时器的属性和状态

软件定时器的“周期”是从软件定时器开始到软件定时器回调函数执行之间的时间。
One-shot and Auto-reload Timers

  • One-shot : 一旦启动,一次性计时器将只执行一次回调函数。 一次性计时器可以手动重新启动,但不会重新启动本身。
  • Auto-reload Timers: 一旦启动,自动重新加载计时器将在每次到期时重新启动,从而定期执行其回调函数。
  • 在这里插入图片描述
  • 计时器1是一个一次性计时器,周期为6个滴答。 它是在时间t1启动的,所以它的回调函数稍后执行6个滴答,时间t7。 由于定时器1是一次性定时器,其回调函数不会再次执行。
  • 计时器2是一个自动重新加载计时器,有5个滴答周期。 它是在时间t1启动的,所以它的回调函数在时间t1之后每5次执行一次。 在图38中,有时是t6、t11和t16

软件定时器可以处于以下两种状态之一

  • 休眠软件定时器存在,可以由其句柄引用,但不运行,因此其回调函数不会执行。
  • 运行软件计时器将在软件计时器进入运行状态或软件计时器上次重置后经过与其周期相等的时间后执行其回调函数。

自动重新加载计时器执行其回调函数,然后重新输入运行状态,
自动重新加载软件定时器状态和转换-------------图
在这里插入图片描述

一次性计时器执行其回调函数,然后进入休眠状态。
一次性软件计时器状态和转换

在这里插入图片描述

计时器服务-任务

守护进程任务是一个标准的免费RTOS任务,当调度程序启动时自动创建。 它的优先级和堆栈大小分别由configTIMER_TASK_PRIORITYconfigTIMER_TASK_STACK_DEPTH编译时间配置常量来设置。 这两个常量都是在FreeRTOSConfig.h中定义的。
软件定时器回调函数不能调用FreeRTOSAPI函数,这些函数将导致调用任务进入阻塞状态,因此将导致守护进程任务进入阻塞状态
在这里插入图片描述

在这里插入图片描述

计时器命令-队列

软件定时器API函数将命令从调用任务发送到称为“定时器命令队列”的队列上的守护进程任务’。 如图41所示。 命令的例子包括“启动计时器”、“停止计时器”和“重置计时器’。

定时器命令队列是一个标准的FreeRTOS队列,在调度程序启动时自动创建。 定时器命令队列的长度由Free RTOSConfig.h中的configTIMER_QUEUE_LENGTH编译时间配置常量来设置。
在这里插入图片描述
守护进程任务与任何其他自由RTOS任务一样被调度;当它是能够运行的最高优先级任务时,它只会处理命令或执行计时器回调函数。 图42和图43演示了configTIMER_TASK_PRIORITY设置如何影响执行模式。 图42显示了当守护进程任务的优先级低于调用x Timer Start()API函数的任务的优先级时的执行模式。
在这里插入图片描述

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

图43显示了与图42所示类似的场景,但是这次守护进程任务的优先级高于调用x Timer Start()的任务的优先级。

在这里插入图片描述
参考图43,其中守护进程任务的优先级高于任务1的优先级,任务1的优先级高于空闲任务的优先级:

  • 时间t1和以前一样,任务1处于运行状态,守护进程任务处于阻塞状态。
  • 在时间t2任务1调用xTimerStart().。xTimerStart().向计时器命令队列发送命令,导致守护进程任务离开阻塞状态。 守护任务的优先级高于任务1的优先级,因此调度程序选择守护任务作为任务进入运行状态。
    任务1在完成执行x Timer Start()函数之前,已被守护进程任务预先加密,现在处于就绪状态。 守护进程任务开始处理任务1发送到计时器命令队列的命令。
  • 在T3时,守护进程任务已经完成了对任务1发送给它的命令的处理,并计时器命令队列中接收更多数据。 计时器命令队列为空,因此守护进程任务重新进入阻塞状态。 任务1现在是Ready状态下的最高优先级任务,因此调度程序选择Task1作为任务进入运行状态。
  • At time t4任务1在完成执行x Timer Start()函数之前就被守护进程任务预先加密,并且在重新进入运行状态后只退出(返回)x Timer Start()。
  • At time t5任务1调用导致其进入阻塞状态的API函数。 空闲任务现在是Ready状态下的最高优先级任务,因此调度程序选择空闲任务作为进入运行状态的任务。

在图42所示的场景中,在任务1向定时器命令队列发送命令和守护进程任务接收和处理命令之间传递时间。 在图43所示的场景中,守护进程任务在任务1从发送命令的函数返回之前已经接收并处理了任务1发送给它的命令。

发送到计时器命令队列的命令包含时间戳。 时间戳用于说明应用程序任务发送的命令与守护进程任务处理的相同命令之间的任何时间。 例如,如果发送“启动计时器”命令来启动周期为10个滴答的计时器,则使用时间戳来确保正在启动的计时器在命令发送后10个滴答过期,而不是在由守护进程任务处理命令后10个滴答过期。

创建和启动软件计时器

xTimerCreate()

软件定时器由TimerHandle_t类型的变量引用。 x Timer Create()用于创建软件计时器,并返回引用它创建的软件计时器的TimerHandle_t。 软件定时器是在休眠状态下创建的。
在这里插入图片描述
参数说明

  • pcTimerName
    计时器的描述性名称。 这一点不被自由RTOS以任何方式使用。 它纯粹是作为调试辅助工具而包含的。 用人类可读的名称识别计时器要比试图用其句柄识别计时器简单得多。
  • xTimerPeriodInTicks
    计时器在滴答中指定的周期。 pdMS_TO_TICKS()宏可用于将以毫秒为单位指定的时间转换为以滴答为单位指定的时间。
  • uxAutoReload 将ux Auto Reload设置为pd TRUE以创建自动重新加载计时器。 将ux自动重加载到pd false以创建一次计时器。
  • pvTimerID
    每个软件定时器都有一个ID值。 ID是一个空指针,应用程序编写器可以用于任何目的。 当一个以上的软件定时器使用相同的回调函数时,ID特别有用,因为它可以用来提供特定于定时器的存储。 使用计时器的ID将在本章的一个示例中演示。 PV计时器ID为正在创建的任务的ID设置初始值。
  • pxCallbackFunction
    软件定时器回调函数只是符合清单72所示原型的C函数。 px Callback Function参数是指向函数(实际上,只是函数名)的指针,该函数用作正在创建的软件计时器的回调函数。
  • Returned value
    如果返回NULL,那么就不能创建软件计时器,因为FreeRTOS没有足够的堆内存来分配必要的数据结构。 返回的非空值表示软件计时器已成功创建。 返回的值是创建的计时器的句柄。 第二章提供了更多关于堆内存管理的信息。

xTimerStart()

x定时器启动()用于启动处于休眠状态的软件定时器,或重置(重新启动)处于运行状态的软件定时器。 计时器停止()用于停止处于运行状态的软件计时器。 停止软件计时器与将计时器转换为休眠状态相同。

可以在调度程序启动之前调用x定时器启动(),但当此操作完成后,软件定时器将不会实际启动,直到调度程序启动时。
在这里插入图片描述
参数说明

  • xTimer
    正在启动或重置的软件计时器的句柄。 句柄将从调用到用于创建软件计时器的x定时器Create()返回
  • xTicksToWait

案例 — 创建一次性和自动加载计时器

TimerHandle_t xAutoReloadTimer, xOneShotTimer;
BaseType_t xTimer1Started, xTimer2Started;

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define mainONE_SHOT_TIMER_PERIOD pdMS_TO_TICKS( 3333 )
#define mainAUTO_RELOAD_TIMER_PERIOD pdMS_TO_TICKS( 500 )

	xOneShotTimer = xTimerCreate("OneShot", mainONE_SHOT_TIMER_PERIOD,pdFALSE, 0, prvOneShotTimerCallback );

  /* definition and creation of myTimer02 */
	xAutoReloadTimer = xTimerCreate("AutoReload", mainAUTO_RELOAD_TIMER_PERIOD,pdTRUE, 0, prvAutoReloadTimerCallback );

  /* USER CODE BEGIN RTOS_TIMERS */

  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* definition and creation of Task_LED1 */
  osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);
  Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL);

  /* definition and creation of Task_LED2 */
  osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128);
  Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
	if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
	{
		xTimer1Started = xTimerStart( xOneShotTimer, 0 );
		xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );
		 if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
		 {
		 /* Start the scheduler. */
		 vTaskStartScheduler();
		 }
	}

void prvOneShotTimerCallback(void const * argument)
{
  /* USER CODE BEGIN prvOneShotTimerCallback */
  TickType_t xTimeNow;
	xTimeNow = xTaskGetTickCount();
	printf( "One-shot timer callback executing  = %d\r\n", xTimeNow );
//	ulCallCount++;
  /* USER CODE END prvOneShotTimerCallback */
}

/* prvAutoReloadTimerCallback function */
void prvAutoReloadTimerCallback(void const * argument)
{
  TickType_t xTimeNow;
	 xTimeNow = xTaskGetTickCount();
	 /* Output a string to show the time at which the callback was executed. */
	 printf( "Auto-reload timer callback executing  = %d\r\n", xTimeNow );
//		ulCallCount++;
  /* USER CODE END prvAutoReloadTimerCallback */
}

在这里插入图片描述

vTimerSetTimerID()

在这里插入图片描述
参数说明

  • xTimer
    用新的ID值更新软件计时器的句柄。 句柄将从调用到用于创建软件计时器的x定时器Create()返回
  • pvNewID
    软件定时器的ID将被设置到的值。

pvTimerGetTimerID()

在这里插入图片描述

  • xTimer
    正在查询的软件定时器的句柄。 句柄将从调用到用于创建软件计时器的xTimerCreate()返回

案例 -使用回调函数参数和timer ID

当两个计时器过期时,PRV计时器回调()将执行。 prv计时器回调()的实现使用函数的参数来确定是否调用它是因为一次性计时器过期,还是因为自动重新加载计时器过期。

PRV计时器回调()还演示了如何使用软件计时器ID作为计时器特定的存储;每个软件计时器在自己的ID中保留过期次数的计数,并且自动重新加载计时器使用计数在第五次执行时停止自己。

下面展示一些 内联代码片

	xOneShotTimer = xTimerCreate("OneShot", mainONE_SHOT_TIMER_PERIOD,pdFALSE, 0, prvOneShotTimerCallback );

  /* definition and creation of myTimer02 */
	xAutoReloadTimer = xTimerCreate("AutoReload", mainAUTO_RELOAD_TIMER_PERIOD,pdTRUE, 0, prvOneShotTimerCallback );
  osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);
  Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL);

  /* definition and creation of Task_LED2 */
  osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128);
  Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
	if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
	{
		xTimer1Started = xTimerStart( xOneShotTimer, 0 );
		xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );
		 if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
		 {
		 /* Start the scheduler. */
		 vTaskStartScheduler();
		 }
	}
 void prvOneShotTimerCallback(void const * argument)
{
  /* USER CODE BEGIN prvOneShotTimerCallback */
	TickType_t xTimeNow;
	uint32_t ulExecutionCount;
	
		ulExecutionCount = ( uint32_t ) pvTimerGetTimerID( (void *)argument );
	 ulExecutionCount++;
	 vTimerSetTimerID( (void *)argument, ( void * ) ulExecutionCount );
	 
	 xTimeNow = xTaskGetTickCount();
	 
			if( argument == xOneShotTimer )
	 {
			printf( "One-shot timer callback executing  = %d\r\n", xTimeNow );
	 }
	 else
	 {

		printf( "Auto-reload timer callback executing  = %d\r\n", xTimeNow );
	 if( ulExecutionCount == 5 )
		 {
		 xTimerStop( (void *)argument, 0 );
		 }
	 }
	 
  /* USER CODE END prvOneShotTimerCallback */
}

在这里插入图片描述

改变时间周期

每个官方的免费RTOS端口都提供一个或多个示例项目。 大多数示例项目都是自我检查,LED用于对项目的状态进行视觉反馈;如果自我检查总是通过,那么LED就会缓慢切换,如果自检失败,那么LED就会快速切换。

一些示例项目在任务中执行自我检查,并使用v vTaskDelay()功能来控制LED切换的速率。 其他示例项目在软件计时器回调函数中执行自我检查,并使用计时器的周期来控制LED切换的速率。

xTimerChangePeriod()

用于更改已经运行的计时器的周期,然后计时器将使用新的周期值重新计算其过期时间。 重新计算的过期时间是相对于调用x定时器更改周期()的时间,而不是相对于计时器最初启动的时间

如果x定时器更改周期()用于更改处于休眠状态的计时器的周期(未运行的计时器),则计时器将计算过期时间,并过渡到运行状态(计时器将开始运行)

注意:不要从中断服务例程()调用x定时器更改周期。 中断安全版本x计时器更改周期从ISR()应该使用在它的位置

在这里插入图片描述
参数说明

  • xTimer
    用新的周期值更新软件计时器的句柄。 句柄将从调用到用于创建软件计时器的xxTimerCreate()返回。
  • xTimerPeriodInTicks
    软件定时器的新周期,在滴答中指定。 pdMS_TO_TICKS()宏可用于将以毫秒为单位指定的时间转换为以滴答为单位指定的时间。
  • xTicksToWait

案例 (未完成)

显示了在软件计时器回调函数中包含自检功能的Free RTOS示例如何使用x Timer Change Period()来增加如果自检失败时LED切换的速率。 执行自我检查的软件计时器称为“检查计时器
下面展示一些 内联代码片

void prvAutoReloadTimerCallback(void const * argument)
{
  /* USER CODE BEGIN prvAutoReloadTimerCallback */
			static unsigned char i = 1;
			xTimerChangePeriod(myTimer02Handle,pdMS_TO_TICKS(i),0);
			i++;
	  	printf( "Time TASK is Runing\r\n");
			printf( "\r\n");
  /* USER CODE END prvAutoReloadTimerCallback */
}

假如 i = 0 的话 这边定时器运行会失败

重新设置软件计时器

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

  • 计时器1在时间T1启动。 它的周期为6,所以它将执行其回调函数的时间最初计算为t7,这是启动后的6个滴答。
  • 计时器1在到达t7之前被重置,因此在它过期并执行其回调函数之前。 计时器1在时间t5被重置,因此它将执行其回调函数的时间被重新计算为t11,这是重置后的6个滴答。
  • 计时器1在时间t11之前再次重置,因此在它过期并执行其回调函数之前再次重置。 计时器1在时间t9被重置,因此它将执行其回调函数的时间被重新计算为t15,这是上次重置后的6个滴答。
  • 计时器1不会再次重置,因此它在t15时过期,并且相应地执行其回调函数
    ·

xTimerReset()

使用x定时器重置()API函数重置计时器。 计时器重置()也可以用来启动处于休眠状态的计时器。

在这里插入图片描述
参数说明

  • xTimer
    软件定时器的手柄被重置或启动。 句柄将从调用到用于创建软件计时器的x定时器Create()返回。
  • xTicksToWait
    x计时器更改周期()使用计时器命令队列将“重置”命令发送到守护进程任务。 x Ticks to Wait指定调用任务应保持在阻塞状态的最大时间,以等待在计时器命令队列上可用的空间,如果队列已经满了。 如果x滴答等待为零,并且计时器命令队列已经满,则x计时器重置()将立即返回。 如果在Free RTOS Config.h中将INCLUDE_vTaskSuspend设置为1,则将xTick设置为等待portMAX_DELAY将导致调用任务无限期地保持在阻塞状态(没有超时),以等待计时器命令队列中的空间可用。

案例 – 重置软件计时器

  • 当按下钥匙时打开。
  • 在一定时间内按下所提供的进一步键。
  • 如果在一定时间内没有按键,则自动关闭。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值