提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、软件定时器的介绍
- 1.1 软件定时器的特性
- 1.2 软件定时器的特性
- 1.3 守护任务
- 二、软件定时器的使用
- 2.1 回调函数
- 2.2 创建定时器
- 创建动态定时器
- 创建静态定时器
- 2.3 删除
- 2.4 启动/停止
- 2.5 复位
- 2.6 修改周期
- 2.7 定时器ID
- 总结
前言
在嵌入式系统开发中,时间管理是至关重要的一部分,特别是对于需要按时执行任务或操作的应用。FreeRTOS 提供了软件定时器的功能,使得开发者能够轻松地实现定时任务和事件调度。本篇文章将介绍 FreeRTOS 中的软件定时器,探讨其基础概念和简单应用,帮助读者快速上手并合理利用这一重要特性。
一、软件定时器的介绍
1.1 软件定时器的特性
软件定时器就像是你手机上的闹钟一样,它是一种程序内部的计时器。你可以设置它,在一段时间后触发一个事件或执行一个任务。比如,你可以设置一个软件定时器,在每天早上7点时触发一个提醒事件,让你起床。在嵌入式系统中,软件定时器常用于执行周期性任务、定时触发事件或定时检查某些条件。在 FreeRTOS 里,我们也可以设置无数个"软件定时器",它们都是基于系统滴答中断(Tick Interrupt)。
1.2 软件定时器的特性
使用定时器跟使用手机闹钟是类似的:
指定时间:启动定时器和运行回调函数,两者的间隔被称为定时器的周期
(period)。
指定类型,定时器有两种类型:
一次性(One-shot timers):
这类定时器启动后,它的回调函数只会被调用一次;可以手工再次启动它,但是不会自动启动它。
自动加载定时器(Auto-reload timers ):
这类定时器启动后,时间到之后它会自动启动它;这使得回调函数被周期性地调用。
指定要做什么事,就是指定回调函数实际的闹钟分为:有效、无效两类。软件定时器也是类似的,它由两种状态:
运行(Running、Active):运行态的定时器,当指定时间到达之后,它的回调函数会被调用
冬眠(Dormant):冬眠态的定时器还可以通过句柄来访问它,但是它不再运行,它的回调函数不会被调用
定时器运行情况示例如下:
Timer1:它是一次性的定时器,在 t1 启动,周期是 6 个 Tick。经过 6 个
tick 后,在 t7 执行回调函数。它的回调函数只会被执行一次,然后该定时器进入
冬眠状态。
Timer2:它是自动加载的定时器,在 t1 启动,周期是 5 个 Tick。每经过 5
个 tick 它的回调函数都被执行,比如在 t6、t11、t16 都会执行。
1.3 守护任务
FreeRTOS 中有一个 Tick 中断,软件定时器基于 Tick 来运行。在哪里执行定时器函数?
第一印象就是在 Tick 中断里执行:
在 Tick 中断中判断定时器是否超时
如果超时了,调用它的回调函数
FreeRTOS 是 RTOS,它不允许在内核、在中断中执行不确定的代码:如果定时器函数很耗时,会影响整个系统。
所以,FreeRTOS 中,不在 Tick 中断中执行定时器函数。在哪里执行?在某个任务里执行,这个任务就是:RTOS Damemon Task,RTOS 守护任务。以前被称为"Timer server",但是这个任务要做并不仅仅是定时器相关,所以改名为:RTOS Damemon Task。
当 FreeRTOS 的配置项 configUSE_TIMERS
被设置为 1 时,在启动调度器时,会自动创建 RTOS Damemon Task。
我们自己编写的任务函数要使用定时器时,是通过"定时器命令队列"(timer command queue)和守护任务交互,如下图所示:
守护任务的 优先级为: configTIMER_TASK_PRIORITY
;定时器命令队列的长度为
configTIMER_QUEUE_LENGTH
。一般来说,守护任务的优先级一般都比较高,这样才能让定时器有较为准时的定时
二、软件定时器的使用
2.1 回调函数
定时器的回调函数如下:
void ATimerCallback( TimerHandle_t xTimer );
定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。
所以,定时器的回调函数不要影响其他人:
回调函数要尽快实行,不能进入阻塞状态
不要调用会导致阻塞的 API 函数,比如 vTaskDelay()
可以调用 xQueueReceive()之类的函数,但是超时时间要设为 0:即刻返回,不可阻塞
2.2 创建定时器
创建动态定时器
我们可以使用下面这个函数创建动态定时器:
TimerHandle_t xTimerCreate( const char * const pcTimerName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction )
参数1为定时器的名称。参数2为定时器的周期。参数3为是否自动重新加载,就是执行一次和一直执行的区别,取值为pdTRUE
和pdFLASE
.参数4的作用为让回调函数可以使用此参数, 比如分辨是哪个定时器。参数5为回调函数。
创建静态定时器
我们可以使用下面这个函数静态的创建定时器:
TimerHandle_t xTimerCreateStatic( const char * const pcTimerName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */const TickType_t xTimerPeriodInTicks,const UBaseType_t uxAutoReload,void * const pvTimerID,TimerCallbackFunction_t pxCallbackFunction,StaticTimer_t *pxTimerBuffer )
对于静态定时器,和动态定时器的区别只在最后一个参数。他会在上面构造定时器。
2.3 删除
我们可以使用下面这个函数删除某一个定时器
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );
参数1为你要删除的定时器handle。参数2为等待时间,定时器的很多 API 函数,都是通过发送"命令"到命令队列,由守护任务来实现。如果队列满了, " 命 令 " 就无法即刻写入队列。我们可以指定一个超时时间
xTicksToWait,等待一会。
2.4 启动/停止
启动定时器就是设置它的状态为运行态(Running、Active)。
停止定时器就是设置它的状态为冬眠(Dormant),让它不能运行。
我们可以使用下面这个函数启动软件定时器:
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
如果你在中断中调用,就使用下面这个:
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );
我们可以使用下面这个函数停止软件定时器:
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
如果你在中断停止软件定时器:
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );
2.5 复位
我们可以使用下面这个函数进行定时器的复位:
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
如果你想在中断中复位,使用下面这个函数:
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken );
2.6 修改周期
我们可以使用下面这个函数修改定时器的周期:
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,TickType_t xNewPeriod,TickType_t xTicksToWait );
参数2为定时器的新周期
我们可以使用下面这个函数在中断里修改周期:
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,TickType_t xNewPeriod,BaseType_t *pxHigherPriorityTaskWoken );
2.7 定时器ID
定时器的结构体如下,里面有一项 pvTimerID,它就是定时器 ID:
怎么使用定时器 ID,完全由程序来决定:
可以用来标记定时器,表示自己是什么定时器
可以用来保存参数,给回调函数使用它的初始值在创建定时器时由 xTimerCreate()这类函数传入,后续可以使用这些函数来操作:
更新 ID:使用 vTimerSetTimerID()函数
查询 ID:查询 pvTimerGetTimerID()函数
这两个函数不涉及命令队列,它们是直接操作定时器结构体
总结
软件定时器是 FreeRTOS 中的一个重要特性,为嵌入式系统的时间管理提供了方便而灵活的解决方案。通过软件定时器,开发者可以轻松实现任务的定时执行、事件的定期处理等功能,提高系统的可靠性和灵活性。
在使用软件定时器时,需要注意合理设置定时器的周期和任务的优先级,避免因任务阻塞或优先级冲突而导致定时器无法准确执行。通过深入理解和应用软件定时器,开发者可以更好地管理系统的时间资源,提高系统的性能和稳定性。
总的来说,软件定时器作为 FreeRTOS 提供的重要特性之一,为嵌入式系统的时间管理提供了可靠的解决方案。希望本文能够为初学者提供一个简明易懂的入门指南,帮助读者更好地利用 FreeRTOS 的软件