FreeRTOS-软件定时器

目录

软件定时器

守护任务

创建定时器

删除定时器

启动定时器

停止定时器

复位定时器

修改定时器周期

定时器ID

应用场景:一般使用

应用场景:消除抖动

资源管理

屏蔽中断

暂停/恢复调度器


软件定时器

在FreeRTOS中可以设置无数个软件定时器,都是基于系统滴答中断。

使用软件定时器需要指定时间:启动定时器和运行回调函数。启动定时器和运行回调函数的间隔为定时器的周期。

使用软件定时器需要指定类型:一次性(回调函数只被调用一次,可手动再次启动)或自动加载(回调函数间歇调用)。

使用软件定时器需要指定事件:指定回调函数。

守护任务

FreeRTOS中有一个Tick中断,软件定时器基于Tick来运行。定时器函数一般在中断里执行,如在中断中判断定时器是否超时,如果超时就调用回调函数。

但FreeRTOS是RTOS,不允许在内核、中断中执行不确定的代码(如果定时器函数很耗时会影响整个系统)。所以FreeRTOS中,不在Tick中断中执行定时器函数。

而是在RTOS Damemon Task(RTOS守护任务)里执行。当FreeRTOS配置项configUSE_TIMERS被设置为1,在启动调度器时会自动创建RTOS守护任务。

我们编写的任务函数要使用定时器时,是通过定时器命令队列(timer command queue)和守护任务交互。

守护任务的优先级为:configTIMER_TASK_PRIORITY,定时器命令队列长度为configTIMER_QUEUE_LENGTH。

当守护任务是当前优先级最高的就绪态任务时,它就可以运行。它的工作有两类:

        处理命令:从命令队列里取出命令、处理。

        执行定时器的回调函数。

能否及时处理定时器的命令、能否及时执行定时器的回调函数,严重依赖于守护任务的优先级。

/* 定时器的回调函数 */
void ATimerCallback( TimerHandle_t xTimer );

定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。所以,定时器的回调函数不能影响其他任务:

        回调函数要尽快执行,不能进入阻塞状态。

        不用调用会导致阻塞的API函数,如vTaskDelay()。

        可以调用xQueueReceive()等函数,但是超时时间要设为0,不阻塞。

创建定时器

TimerHandle_t xTimerCreate( const char * const pcTimerName,					// 定时器名字
							const TickType_t xTimerPeriodInTicks,			// 定时器周期, 以Tick为单位
							const UBaseType_t uxAutoReload,					// 定时器是否自动重装载, pdTRUE表示自动加载, pdFALSE表示一次性
							void * const pvTimerID,							// 回调函数可以使用此参数, 比如分辨是哪个定时器
							TimerCallbackFunction_t pxCallbackFunction );	// 回调函数
/* 返回值: 成功则返回TimerHandle_t, 否则返回NULL */

TimerHandle_t xTimerCreateStatic(	const char * const pcTimerName,				// 定时器名字
									TickType_t xTimerPeriodInTicks,				// 定时器周期, 以Tick为单位
									UBaseType_t uxAutoReload,					// 定时器是否自动重装载, pdTRUE表示自动加载, pdFALSE表示一次性
									void * pvTimerID,							// 回调函数可以使用此参数, 比如分辨是哪个定时器
									TimerCallbackFunction_t pxCallbackFunction,	// 回调函数
									StaticTimer_t *pxTimerBuffer );				// 传入一个StaticTimer_t结构体, 将在结构体构造定时器
/* 返回值: 成功则返回TimerHandle_t, 否则返回NULL */

void ATimerCallback( TimerHandle_t xTimer );
typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );

删除定时器

动态分配的定时器,不再需要时可以删除以回收内存。

/* 
 * xTimer: 要删除哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: 	pdFAIL表示"删除命令"在指定超时时间内无法写入队列
 * 			pdPASS表示成功
*/
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

定时器的很多API函数都是通过发送命令到命令队列,由守护任务来实现。如果队列满了,命令就无法立即写入队列,需要指定一个超时时间。

启动定时器

启动定时器就是设置它的状态为运行态。

xTicksToWait不是定时器超时时间,也不是定时器周期。

如果定时器已经被启动,但它的回调函数还没有被执行时,再次执行xTimerStart()函数相当于执行xTimerReset()函数,重新设定它的启动时间。

/* 
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: 	pdFAIL表示"启动命令"在指定超时时间内无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,
							  则*pxHigherPriorityTaskWoken = pdTRUE,表示需要进行任务调度
 * 返回值:  pdFAIL表示"启动命令"无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken );

停止定时器

启动定时器就是设置它的状态为睡眠态,让它无法运行。

/* 
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: 	pdFAIL表示"停止命令"在指定超时时间内无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,
 * 则*pxHigherPriorityTaskWoken = pdTRUE,表示需要进行任务调度
 * 返回值: 	pdFAIL表示"停止命令"无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken );

复位定时器

使用xTimerReset()函数可以让定时器的状态从睡眠态转换为运行态,相当于使用xTimerStart()函数。

如果定时器已经处于运行态,使用xTimerReset()函数相当于重新确定超时时间。

/* 
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: 	pdFAIL表示"复位命令"在指定超时时间内无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,
 * 则*pxHigherPriorityTaskWoken = pdTRUE,表示需要进行任务调度
 * 返回值: 	pdFAIL表示"停止命令"无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken );

修改定时器周期

使用xTimerChangePeriod()函数,除了能修改定时器周期外,还可以让定时器的状态从睡眠态转换为运行态。

修改定时器周期时,会使用新的周期重新计算它的超时时间。

/* 返回值: 	pdFAIL表示"修改周期命令"在指定超时时间内无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerChangePeriod( 	TimerHandle_t xTimer, 		/* xTimer: 哪个定时器 */
								TickType_t xNewPeriod,		/* xNewPeriod: 新周期 */
								TickType_t xTicksToWait );	/* xTicksToWait: 超时时间, 命令写入队列的超时时间 */
								
/* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,
 * 则*pxHigherPriorityTaskWoken = pdTRUE,表示需要进行任务调度
 * 返回值: 	pdFAIL表示"修改周期命令"在指定超时时间内内无法写入队列
 * 			pdPASS表示成功
 */
BaseType_t xTimerChangePeriodFromISR(	TimerHandle_t xTimer,						/* xTimer: 哪个定时器 */
										TickType_t xNewPeriod,						/* xNewPeriod: 新周期 */
										BaseType_t *pxHigherPriorityTaskWoken );	

定时器ID

typedef struct tmrTimerControl
{
	const char 				*pcTimerName;
	ListItem_t 				xTimerListItem;
	TickType_t 				xTimerPeriodInTicks;
	void 					*pvTimerID;			// 定时器ID
	TimerCallbackFunction_t pxCallbackFunction;
#if ( configUSE_TRACE_FACILITY == 1 )
	UBaseType_t uxTimerNumber;
#endif
	uint8 t ucStatus;
} xTIMER;

怎么使用定时器ID,完全由程序来决定:

        可以用来标记定时器,表示自己是什么定时器

        可以用来保存参数,供回调函数使用

它的初始值在创建定时器时由xTimerCreate()函数传入,后续可以使用这些函数来操作:

        更新ID:使用vTimerSetTimerID()函数

        查询ID:使用pvTimerGetTimerID()函数

这两个函数不涉及命令队列,都是直接操作定时器结构体的。

/* 
 * xTimer: 哪个定时器
 * 返回值: 定时器的ID
 */
void *pvTimerGetTimerID( TimerHandle_t xTimer );

/* 
 * xTimer: 哪个定时器
 * pvNewID: 新ID
 */
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

应用场景:一般使用

要使用定时器,需要一些准备工作。

/* 1. 工程中 */
添加 timer.c

/* 2. 配置文件FreeRTOSConfig.h中 */
##define configUSE_TIMERS 1 					/* 使能定时器 */
##define configTIMER_TASK_PRIORITY 		31 		/* 守护任务的优先级, 尽可能高一些 */
##define configTIMER_QUEUE_LENGTH 		5 		/* 命令队列长度 */
##define configTIMER_TASK_STACK_DEPTH 	32 		/* 守护任务的栈大小 */

/* 3. 源码中 */
##include "timers.h"
static volatile uint8_t flagONEShotTimerRun = 0;	// 一次性
static volatile uint8_t flagAutoLoadTimerRun = 0;	// 自动加载

static void vONEShotTimerFunc( TimerHandle_t xTimer );
static void vAutoLoadTimerFunc( TimerHandle_t xTimer );
/*-----------------------------------------------------------*/

##define mainONE_SHOT_TIMER_PERIOD 		pdMS_TO_TICKS( 10 )
##define mainAUTO_RELOAD_TIMER_PERIOD 	pdMS_TO_TICKS( 20 )

int main( void )
{
	TimerHandle_t xOneShotTimer;
	TimerHandle_t xAutoReloadTimer;
	
	prvSetupHardware();
	
	xOneShotTimer = xTimerCreate(
		"OneShot", 						// 名字, 不重要
		mainONE_SHOT_TIMER_PERIOD, 		// 周期
		pdFALSE, 						// 一次性
		0, 								// ID
		vONEShotTimerFunc 				// 回调函数
	);
	
	xAutoReloadTimer = xTimerCreate(
		"AutoReload", 					// 名字, 不重要
		mainAUTO_RELOAD_TIMER_PERIOD, 	// 周期
		pdTRUE, 						// 自动加载
		0, 								// ID
		vAutoLoadTimerFunc 				// 回调函数
	);
	
	if (xOneShotTimer && xAutoReloadTimer)
	{
		/* 启动定时器 */
		xTimerStart(xOneShotTimer, 0);
		xTimerStart(xAutoReloadTimer, 0);
	
		/* 启动调度器 */
		vTaskStartScheduler();
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vONEShotTimerFunc( TimerHandle_t xTimer )
{
	static int cnt = 0;
	flagONEShotTimerRun = !flagONEShotTimerRun;
	printf("run vONEShotTimerFunc %d\r\n", cnt++);
} 

static void vAutoLoadTimerFunc( TimerHandle_t xTimer )
{
	static int cnt = 0;
	flagAutoLoadTimerRun = !flagAutoLoadTimerRun;
	printf("run vAutoLoadTimerFunc %d\r\n", cnt++);
}

应用场景:消除抖动

使用机械开关时经常碰到抖动问题,引脚电平在短时间内反复变化。

怎么读到确定的按键状态呢?

        连续读很多次,直到数值稳定,但浪费CPU资源。

        使用定时器,结合中断使用。

对于第2种方法,处理方法如下。

要使用定时器,需要一些准备工作。

/* 1. 工程中 */
添加 timer.c

/* 2. 配置文件FreeRTOSConfig.h中 */
##define configUSE_TIMERS 1 					/* 使能定时器 */
##define configTIMER_TASK_PRIORITY 		31 		/* 守护任务的优先级, 尽可能高一些 */
##define configTIMER_QUEUE_LENGTH 		5 		/* 命令队列长度 */
##define configTIMER_TASK_STACK_DEPTH 	32 		/* 守护任务的栈大小 */

/* 3. 源码中 */
##include "timers.h"
/*-----------------------------------------------------------*/
static TimerHandle_t xKeyFilteringTimer;

void vEmulateKeyTask( void *pvParameters );
static void vKeyFilteringTimerFunc( TimerHandle_t xTimer );
/*-----------------------------------------------------------*/

#define KEY_FILTERING_PERIOD 	pdMS_TO_TICKS( 20 )

int main( void )
{
	prvSetupHardware();

	xKeyFilteringTimer = xTimerCreate(
		"KeyFiltering", 		// 名字, 不重要
		KEY_FILTERING_PERIOD, 	// 周期
		pdFALSE, 				// 一次性
		0, 						// ID
		vKeyFilteringTimerFunc 	// 回调函数
	);
	
	/* 在这个任务中多次调用xTimerReset来模拟按键抖动 */
	xTaskCreate( vEmulateKeyTask, "EmulateKey", 1000, NULL, 1, NULL );
	
	/* 启动调度器 */
	vTaskStartScheduler();
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

void vEmulateKeyTask( void *pvParameters )
{
	int cnt = 0;
	const TickType_t xDelayTicks = pdMS_TO_TICKS( 200UL );
	
	for( ;; )
	{
		/* 模拟按键抖动, 多次调用xTimerReset */
		xTimerReset(xKeyFilteringTimer, 0); 
		cnt++;
		
		xTimerReset(xKeyFilteringTimer, 0); 
		cnt++;
		
		xTimerReset(xKeyFilteringTimer, 0); 
		cnt++;
		
		printf("Key jitters %d\r\n", cnt);
		
		vTaskDelay(xDelayTicks);
	}
}

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

实验现象:

Key jitters 3

vKeyFilteringTimerFunc 0

Key jitters 6

vKeyFilteringTimerFunc 1

Key jitters 9

vKeyFilteringTimerFunc 2

...

在任务函数中多次调用xTimerReset函数,只触发一次定时器回调函数。

资源管理

屏蔽中断

taskENTER_CRITICA();            // 屏蔽中断
taskEXIT_CRITICAL();            // 重新使能中断

taskENTER_CRITICAL_FROM_ISR();  // 屏蔽中断
taskEXIT_CRITICAL_FROM_ISR();   // 重新使能中断

taskENTER_CRITICA() / taskEXIT_CRITICAL() 间 或 taskENTER_CRITICAL_FROM_ISR() / taskEXIT_CRITICAL_FROM_ISR()间:

        低优先级的中断被屏蔽了。优先级 ≤ configMAX_SYSCALL_INTERRUPT_PRIORITY。

        高优先级的中断可以产生。但在这些高优先级的中断ISR里不允许使用FreeRTOS的API函数。

        任务调度依赖于中断、依赖于API函数。所以这两段代码间不会用任务调度产生。

taskENTER_CRITICA() / taskEXIT_CRITICAL() 间还可以递归使用该宏,内部会记录嵌套的深度,只有嵌套深度变为0时,调用taskEXIT_CRITICAL()才会重新使能中断。

使用 taskENTER_CRITICA() / taskEXIT_CRITICAL() 来访问临界资源是很粗鲁的方法:中断无法正常运行、任务调度无法进行。所以之间的代码要尽可能快速执行。

暂停/恢复调度器

如果有别的任务竞争临界资源,可以把中断关掉,也可以禁止别的任务运行(但代价太大,会影响中断的处理)。

如果只是禁止别的任务竞争,不需要关中断,暂停调度器就可以了。期间中断还是可以发生和处理。

/* 暂停调度器 */
void vTaskSuspendAll( void );

/* 恢复调度器
 * 返回值: pdTRUE表示在暂定期间有更高优先级的任务就绪了,可以不理会这个返回值
 */
BaseType_t xTaskResumeAll( void );


vTaskSuspendScheduler();
/* 访问临界资源 */
xTaskResumeScheduler();

  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeRTOS 中,软件定时器和硬件定时器是两种不同的定时器实现方式,用于实现任务的定时调度和时间管理。 1. 软件定时器(Software Timer): 软件定时器FreeRTOS 提供的一种基于软件定时器机制,通过 FreeRTOS 内核的任务调度进行管理。软件定时器主要由 `xTimerCreate()`、`xTimerStart()`、`xTimerStop()` 等 API 函数来创建、启动、停止和删除。软件定时器适用于需要在任务中使用的相对较低频率的定时操作。 通过软件定时器,可以创建多个定时器以满足不同任务的需求,并且可以在定时器到期时触发回调函数来执行特定的操作。软件定时器使用 FreeRTOS 的任务调度进行管理,因此,如果有其他高优先级任务需要执行,软件定时器会在适当的时机被暂停,并在下一个合适的时间点继续执行。 2. 硬件定时器(Hardware Timer): 硬件定时器是嵌入式系统中的硬件设备,可由硬件芯片提供。硬件定时器通常由专用寄存和计数组成,可用于生成精确的时间延迟或周期性触发中断。在 FreeRTOS 中,可以将硬件定时器软件定时器结合使用,以提供更精确和高频率的定时操作。 使用硬件定时器需要根据硬件平台和具体的芯片手册进行配置和初始化。一旦硬件定时器设置完成,可以在中断服务程序中处理定时器中断,并在中断处理程序中触发所需的操作。 需要根据具体的应用场景和需求来选择使用软件定时器还是硬件定时器软件定时器适用于相对较低频率和较少精度要求的任务调度,而硬件定时器适用于高频率和精确性要求较高的定时操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值