FreeRTOS入门教程(软件定时器)


前言

本篇文章开始带大家来学习一下什么是软件定时器,并掌握相关API函数的使用方法。

一、软件定时器概念

软件定时器是FreeRTOS中的一个重要概念,用于在实时应用程序中生成定时事件,而无需硬件定时器的支持。以下是有关FreeRTOS中软件定时器的概念:

软件定时器的用途:

软件定时器允许在RTOS任务中创建和管理定时事件,而无需硬件定时器。
它们常用于在一段时间后触发特定任务或处理程序,例如周期性数据采集、任务定时唤醒等。

二、守护任务

FreeRTOS中的守护任务(daemon task)是一种特殊类型的任务,通常用于执行与系统时钟和软件定时器相关的内部任务。这些任务的特殊之处在于它们不受普通应用程序任务的干扰,因此可以用于实现系统级的功能,例如软件定时器的管理。

以下是关于FreeRTOS中守护任务和软件定时器之间的一些关键概念:

1.守护任务的作用:

守护任务是FreeRTOS内部的任务,用于执行与时间管理相关的任务。
最常见的用途之一是管理软件定时器。它负责在定时器到期时调用相应的回调函数,以执行定时任务。

2.软件定时器的管理:

守护任务通常会创建、启动和监视软件定时器。
它会检查每个软件定时器的状态,以确定哪些定时器已经超时,然后触发与这些定时器相关联的回调函数。

3.定时器队列:

守护任务使用一个特殊的队列来管理定时器,这个队列被称为 “定时器队列”。
定时器队列中包含了所有已经创建的软件定时器,以及它们的状态信息、超时时间等。

4.守护任务的优先级:

通常,守护任务会分配一个较高的优先级,以确保它能够及时响应定时器的超时事件。
这样可以确保守护任务具有更高的调度优先级,而不会受到普通应用程序任务的影响。

总的来说,守护任务是FreeRTOS中的一个特殊任务,用于管理系统级的功能,尤其是软件定时器。通过它,可以确保定时器的准确触发和回调函数的执行,而不受其他任务的影响。这有助于保持系统时钟的准确性和可靠性,以满足实时应用程序的要求。在使用守护任务时,需要特别注意设置任务的优先级,以确保它具有足够的时间响应定时器事件。

当configUSE_TIMERS这个宏被配置为1时会自动创建守护任务
在这里插入图片描述
守护任务的优先级需要配置的比其他任务的优先级都高,这样当定时器时间到了的时候,守护任务才可以抢占其他任务执行定时器的回调函数。

守护任务优先级由下面这个宏决定:

#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1)
在这里插入图片描述
使用定时器函数时是通过定时器命令队列和守护任务进行交互。

在这里插入图片描述
在这里插入图片描述

三、软件定时器中的回调函数

在FreeRTOS中的软件定时器中,回调函数是一个用户定义的函数,用于在定时器超时时执行特定的任务或操作。回调函数是软件定时器的核心部分,它允许你在特定的时间间隔内执行所需的操作。以下是有关FreeRTOS中软件定时器回调函数的详细解释:

1.回调函数的定义:

回调函数是一个普通的C函数,由您编写并提供给软件定时器创建函数。通常,它的原型如下:void vTimerCallback(TimerHandle_t xTimer);
回调函数的名称和参数类型可以根据您的应用程序需求进行自定义。

2.回调函数的目的:

回调函数的目的是执行与定时事件相关的任务或操作。这可以包括数据采集、数据处理、状态更新、通信等等,取决于您的应用。

3.回调函数的执行时机:

回调函数将在软件定时器超时时自动执行。
当定时器超时时,FreeRTOS内核会调用与定时器关联的回调函数,从而触发特定的操作。
传递给回调函数的参数:

回调函数通常会接受一个参数,即与定时器关联的TimerHandle_t类型的定时器句柄。
通过该句柄,您可以在回调函数内访问定时器的信息,如定时器的名称、定时器的状态、定时器的周期等。

4.回调函数的注意事项:

回调函数应尽量保持简洁和高效,以确保不会影响其他任务的执行。
避免在回调函数中使用阻塞操作,因为这可能会导致定时器的不准确性和系统性能问题。
如果需要执行较长时间的操作,可以考虑将任务委托给其他任务,而不是在回调函数中执行。

回调函数指针原型:

在这里插入图片描述

四、软件定时器相关函数

首先我们先来看两张定时器状态转换图:
在这里插入图片描述

在这里插入图片描述
通过上面两张图我们就可以清楚的明白软件定时器涉及到了哪些函数了。

1.xTimerCreate():

函数原型:

TimerHandle_t xTimerCreate(const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction);

功能:创建一个软件定时器。
参数解释:
pcTimerName:定时器的名称,用于调试和跟踪。
xTimerPeriodInTicks:定时器的周期,以时钟节拍(tick)为单位。
uxAutoReload:如果设置为pdTRUE,则定时器会在超时后自动重新加载,否则只触发一次。
pvTimerID:用户可以指定的定时器标识符,通常设置为NULL。
pxCallbackFunction:定时器超时时要调用的回调函数。

2.xTimerStart():

函数原型:

BaseType_t xTimerStart(TimerHandle_t xTimer, const TickType_t xTicksToWait);

功能:启动或重新启动一个软件定时器。
参数解释:
xTimer:要启动的定时器句柄。
xTicksToWait:在启动之前等待的时间,通常设置为0。

3.xTimerStop():

函数原型:

BaseType_t xTimerStop(TimerHandle_t xTimer, const TickType_t xTicksToWait);

功能:停止一个软件定时器。
参数解释:
xTimer:要停止的定时器句柄。
xTicksToWait:在停止之前等待的时间,通常设置为0。

4.xTimerChangePeriod():

函数原型:

BaseType_t xTimerChangePeriod(TimerHandle_t xTimer, const TickType_t xNewPeriod, const TickType_t xTicksToWait);

功能:修改一个运行中定时器的周期。
参数解释:
xTimer:要修改的定时器句柄。
xNewPeriod:新的定时器周期,以时钟节拍(tick)为单位。
xTicksToWait:在修改之前等待的时间,通常设置为0。

5.xTimerDelete():

函数原型:

void xTimerDelete(TimerHandle_t xTimer, const TickType_t xTicksToWait);

功能:删除一个软件定时器。
参数解释:
xTimer:要删除的定时器句柄。
xTicksToWait:在删除之前等待的时间,通常设置为0。

五、软件定时器的使用

1.一般使用

下面的代码使用软件定时器的回调函数每隔100ms打印一句话。

static TimerHandle_t xMyTimerHandle;
static int flagTimer = 0;

void Task1Function(void * param)
{
    
    
	volatile int i = 0;

	xTimerStart(xMyTimerHandle, 0);
	
	while (1)
	{
    
    
		printf("Task1Function ...\r\n");
	}
}

void MyTimerCallbackFunction( TimerHandle_t xTimer )
{
    
    
	static int cnt = 0;
	flagTimer = !flagTimer;
	printf("MyTimerCallbackFunction_t cnt = %d\r\n", cnt++);
}

xMyTimerHandle = xTimerCreate("mytimer", 100, pdTRUE, NULL, MyTimerCallbackFunction);

xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);

运行结果:

这里可以看到软件定时器的回调函数能够被成功执行。

在这里插入图片描述

2.按键消抖

接触过单片机的同学都知道读取按键的电平状态时是需要进行消抖的,因为当按键按下时,会发生抖动,在此时读取按键的电平状态得到的是一个不稳定的状态。

在这里插入图片描述
我们通常使用的消抖方法就是使用延时函数来进行消抖,但是使用延时函数进行消抖的话会降低CPU的利用率,这个方法并不是一个最好的方法。

void MyTimerCallbackFunction( TimerHandle_t xTimer )
{
    
    
	static int cnt = 0;
	flagTimer = !flagTimer;
	printf("Get GPIO Key cnt = %d\r\n", cnt++);
}

void EXTI0_IRQHandler(void)
{
    
    
	static int cnt = 0;
	if(EXTI_GetITStatus(EXTI_Line0) != RESET)
	{
    
    
		printf("EXTI0_IRQHandler cnt = %d\r\n", cnt++);
		/* 使用定时器消除抖动 */
		xTimerReset(xMyTimerHandle, 0); /* Tcur + 20 */
		
		EXTI_ClearITPendingBit(EXTI_Line0);     //清除中断
	}     
}

xMyTimerHandle = xTimerCreate("mytimer", 20, pdFALSE, NULL, MyTimerCallbackFunction);

当外部中断触发时,定时器会被重置,然后开始倒数计时。如果在定时器倒数计时期间再次发生外部中断触发,定时器会重新开始计时,直到计时器达到0才会执行回调函数。这样可以有效地去除外部中断的抖动,确保只有真正的信号变化才会触发回调函数。

总结

本篇文章主要给大家讲解了软件定时器的使用方法和注意事项。

猜你喜欢

转载自blog.csdn.net/m0_49476241/article/details/133656870