目录

STM32单片机FreeRTOS系统11-系统时钟节拍和时间管理,定时器组

STM32单片机FreeRTOS系统11 系统时钟节拍和时间管理,定时器组

目录


一、FreeRTOS 的时钟节拍

任何操作系统都需要提供一个时钟节拍,以供系统处理诸如延时、 超时等与时间相关的事件。

时钟节拍是特定的周期性中断, 这个中断可以看做是系统心跳。 中断之间的时间间隔取决于不同的应用,一般是 1ms – 100ms。时钟的节拍中断使得内核可以将任务延迟若干个时钟节拍,以及当任务等待事件发生时,提供等待超时等依据。时钟节拍率越快,系统的额外开销就越大。

对于 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407 以及 F429, 教程配套的例子都是用滴答定时器来实现系统时钟节拍的,单位为Hz。

#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )

二、FreeRTOS 的时间管理

时间管理功能是 FreeRTOS 操作系统里面最基本的功能, 同时也是必须要掌握好的。

1、时间延迟

FreeRTOS 中的时间延迟函数主要有以下两个作用:

  • 为周期性执行的任务提供延迟。
  • 对于抢占式调度器,让高优先级任务可以通过时间延迟函数释放 CPU 使用权, 从而让低优先级任务可以得到执行。

https://i-blog.csdnimg.cn/direct/a9614cd9c3b141419636492154313381.png

2、vTaskDelay ()

void vTaskDelay(
const TickType_t xTicksToDelay ); /* 延迟时间长度 */

前文进行过介绍,其单位就是定时器的单位,例如前面设置的1ms。用于任务的延迟。

3、vTaskDelayUntil()

void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, /* 存储任务上次处于非阻塞状态时刻的变量地址 */
const TickType_t xTimeIncrement ); /* 周期性延迟时间 */

函数 vTaskDelayUntil 用于周期性延迟。

https://i-blog.csdnimg.cn/direct/339b8c1ddddd404cb400c6522189914e.png

https://i-blog.csdnimg.cn/direct/60a9ad8079e6492aac2d33d71503b3b4.png

如此可以看出和vTaskDelay的区别。

4、xTaskGetTickCount()

volatile TickType_t xTaskGetTickCount( void );

函数 xTaskGetTickCount 用于获取系统当前运行的时钟节拍数,此函数用于在任务代码里面调用, 如果在中断服务程序里面调用的话, 需要使用下面的函数。

5、xTaskGetTickCountFromISR()

volatile TickType_t xTaskGetTickCountFromISR( void );

此函数用于在中断服务程序里面调用, 如果在任务里面调用的话, 需要使用上面的函数。

static void vTaskLED(void *pvParameters)
{
	TickType_t xLastWakeTime;
	const TickType_t xFrequency = 200;

	/* 获取当前的系统时间 */
    xLastWakeTime = xTaskGetTickCount();
	
    while(1)
    {
		bsp_LedToggle(2);
		
		/* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
}
static void vTaskMsgPro(void *pvParameters)
{
	TickType_t xDelay, xNextTime;
	const TickType_t xFrequency = 200;
	
	/* 获取xFrequency个时钟节拍后的时间 */
	xNextTime = xTaskGetTickCount() + xFrequency;
	
    while(1)
    {
		bsp_LedToggle(3);
		
		/* 用vTaskDelay实现vTaskDelayUntil() */
		xDelay = xNextTime - xTaskGetTickCount();
		xNextTime += xFrequency;
		
		if(xDelay <= xFrequency)
		{
			vTaskDelay(xDelay);
		}
	}
}

三、定时器组介绍

FreeRTOS 软件定时器组的时基是基于系统时钟节拍实现的, 之所以叫软件定时器是因为它的实现不需要使用任何硬件定时器, 而且可以创建很多个 ,综合这些因素,这个功能就被称之为软件定时器组。

既然是定时器,那么它实现的功能与硬件定时器也是类似的。 在硬件定时器中,我们是在定时器中断中实现需要的功能,而使用软件定时器时,我们是在创建软件定时器时指定软件定时器的回调函数,在回调函数中实现相应的功能。

1、单次模式

单次模式就是用户创建了定时器并启动了定时器后, 定时时间到将不再重新执行, 这就是单次模式软件定时器的含义。

2、周期模式

周期模式就是此定时器会按照设置的时间周期重复去执行, 这就是周期模式软件定时器的含义。 另外就是单次模式或者周期模式的定时时间到后会调用定时器的回调函数,用户可以回调函数中加入需要执行的工程代码。

四、定时器任务(Daemon 任务)

为了更好的管理 FreeRTOS 的定时器组件, 专门创建了一个定时器任务, 或者称之为 Daemon 任务。

FreeRTOS 定时器组的大部分 API 函数都是通过消息队列给定时器任务发消息,在定时器任务里面执行实际的操作。 为了更好的说明这个问题,我们将官方在线版手册中的这个截图贴出来进行说明:

https://i-blog.csdnimg.cn/direct/fbd5d66f47444ec7974134b9deddeb02.png

左侧图是用户应用程序,右侧是定时器任务。 在用户应用程序里面调用了定时器组API函数xTimerReset,这个函数会通过消息队列给定时器任务发消息,在定时器任务里面执行实际操作。 消息队列在此处的作用有一个专门的名字: Timer command queue, 即专门发送定时器组命令的队列。

五、使用软件定时器组注意事项

定时器回调函数是在定时器任务中执行的, 实际应用中切不可在定时器回调函数中调用任何将定时器任务挂起的函数,比如vTaskDelay(), vTaskDelayUntil()以及非零延迟的消息队列和信号量相关的函数。将定时器任务挂起, 会导致定时器任务负责的相关功能都不能正确执行了。

六、定时器组 API 函数

多达20多个,主要介绍如下:

1、xTimerCreate()

TimerHandle_t xTimerCreate
( const char * const pcTimerName, /* 定时器名字 */
const TickType_t xTimerPeriod, /* 定时器周期, 单位系统时钟节拍 */
const UBaseType_t uxAutoReload, /* 选择单次模式或者周期模式 */
void * const pvTimerID, /* 定时器 ID */
TimerCallbackFunction_t pxCallbackFunction ); /* 定时器回调函数 */

函数 xTimerCreate 用于创建软件定时器。当创建不同的定时器, 但使用相同的回调函数时, 在回调函数中通过不同的ID 号来区分不同的定时器。

2、xTimerStart ()

BaseType_t xTimerStart( TimerHandle_t xTimer, /* 定时器句柄 */
TickType_t xBlockTime ); /* 成功启动定时器前的最大等待时间设置, 单位系统时钟节拍 */

函数 xTimerStart 用于启动软件定时器,定时器组的大部分 API函数不是直接运行的,而是通过消息队列给定时器任务发消息来实现的,此最大等待时间参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。

3、pvTimerGetTimerID ()

void *pvTimerGetTimerID( TimerHandle_t xTimer ); /* 定时器句柄 */

函数 pvTimerGetTimerID 用于返回使用函数 xTimerCreate 创建的软件定时器 ID。

七、定时器组实现

/*
*********************************************************************************************************
*	函 数 名: AppObjCreate
*	功能说明: 创建任务通信机制
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void AppObjCreate (void)
{
	uint8_t i;
	const TickType_t  xTimerPer[2] = {100, 100};
	
	/* 
	  1. 创建定时器,如果在RTOS调度开始前初始化定时器,那么系统启动后才会执行。
	  2. 统一初始化两个定时器,他们使用共同的回调函数,在回调函数中通过定时器ID来区分
	     是那个定时器的时间到。当然,使用不同的回调函数也是没问题的。
	*/
	for(i = 0; i < 2; i++)
	{
		xTimers[i] = xTimerCreate("Timer",          /* 定时器名字 */
							       xTimerPer[i],    /* 定时器周期,单位时钟节拍 */
							       pdTRUE,          /* 周期性 */
							       (void *) i,      /* 定时器ID */
							       vTimerCallback); /* 定时器回调函数 */

		if(xTimers[i] == NULL)
		{
			/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
		}
		else
		{
			 /* 启动定时器,系统启动后才开始工作 */
			 if(xTimerStart(xTimers[i], 100) != pdPASS)
			 {
				 /* 定时器还没有进入激活状态 */
			 }
		}
	}
}

/*
*********************************************************************************************************
*	函 数 名: vTimerCallback
*	功能说明: 定时器回调函数
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void vTimerCallback(xTimerHandle pxTimer)
{
	uint32_t ulTimerID;

	configASSERT(pxTimer);

	/* 获取那个定时器时间到 */
	ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);
	
	/* 处理定时器0任务 */
	if(ulTimerID == 0)
	{
		bsp_LedToggle(1);
	}
	
	/* 处理定时器1任务 */
	if(ulTimerID == 1)
	{
		bsp_LedToggle(2);
	}
}