FreeRTOS学习笔记 & 软件定时器
- 软件定时器简介
- 硬件定时器
- 软件定时器
- 硬件定时器和软件定时器的工作机制
- FreeRTOS 软件定时器功能上支持:
- 软件定时器应用场景
- 软件定时器的精度
- 软件定时器 API 函数
-
- xTimerCreate 创建一个软件定时器
- xTimerStart 开始一个软件定时器计时 (任务中使用)
- xTimerStartFromISR 开始一个软件定时器计时(中断中使用)
- xTimerStop 停止一个软件定时器计时(任务中使用)
- xTimerStopFromISR 停止一个软件定时器计时(中断中使用)
- xTimerChangePeriod 改变一个软件定时器计时周期(任务中使用)
- xTimerChangePeriodFromISR 改变一个软件定时器计时周期(中断中使用)
- xTimerDelete 删除一个软件定时器
- xTimerReset 复位一个软件定时器(任务中使用)
- xTimerResetFromISR 复位一个软件定时器(中断中使用)
- vTimerSetReloadMode 设置软件定时器自动重装载模式
- vTimerSetTimerID 设置软件定时器ID
- 查询软件定时器状态 API 函数
软件定时器简介
定时器几乎每个 MCU 都有的外设,有的 MCU 其定时器功能异常强大,比如提供 PWM、输入捕获等功能。但最常用的还是定时器基础定时功能,通过定时器来完成需要周期性处理的事务。
MCU 自带的定时器属于硬件定时器,不同的 MCU 其硬件定时器数量不同,因为要考虑成本问题。
FreeRTOS 也提供了定时器功能,不过是软件定时器,软件定时器的精度肯定是没有硬件定时器那么高,但对于普通的精度要求不高的周期性处理的任务来说够了。
当 MCU 的硬件定时器不够的时候就可以考虑使用 FreeRTOS 的软件定时器
硬件定时器
硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式
软件定时器
软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的
硬件定时器和软件定时器的工作机制
使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中处理信息;
而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(也称超时函数/回调函数)在回调函数中处理信息。
软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数。定时精度与系统时钟的周期有关。一般系统利用 SysTick 作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况,比如 vTaskDelay()以及其它能阻塞任务运行的函数,两次触发回调函数的时间间隔 xTimerPeriodInTicks 叫定时器的定时周期
FreeRTOS 软件定时器功能上支持:
●裁剪:能通过宏关闭软件定时器功能。
●软件定时器创建。
●软件定时器启动。
●软件定时器停止。
●软件定时器复位。
●软件定时器删除。
FreeRTOS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数,用户可以在回调函数中加入要执行的工程代码。
单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。
周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除,如下所示:
FreeRTOS 通过一个 prvTimerTask 任务(也叫守护任务 Daemon)管理软定时器,它是在启动调度器时自动创建的,为了满足用户定时需求。prvTimerTask任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。只有设置 FreeRTOSConfig.h 中的宏定义 configUSE_TIMERS 设置为 1 ,将相关代码编译进来,才能正常使用软件定时器相关功能
软件定时器应用场景
在很多应用中,我们需要一些定时器任务,硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,无法提供更多的定时器,那么可以采用软件定时器来完成,由软件定时器代替硬件定时器任务。
但需要注意的是软件定时器的精度是无法和硬件定时器相比的,而且在软件定时器的定时过程中是极有可能被其它中断所打断,因为软件定时器的执行上下文环境是任务。所以,软件定时器更适用于对时间精度要求不高的任务,一些辅助型的任务
软件定时器的精度
在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为 configTICK_RATE_HZ,该宏在 FreeRTOSConfig.h 中有定义,默认是 1000。那么系统的时钟节拍周期就为 1ms(1s 跳动 1000 下,每一下就为1ms)。软件定时器的所定时数值必须是这个节拍周期的整数倍,例如节拍周期是 10ms,那么上层软件定时器定时数值只能是 10ms,20ms,100ms 等,而不能取值为 15ms。由于节拍定义了系统中定时器能够分辨的精确度,系统可以根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多
软件定时器 API 函数
xTimerCreate 创建一个软件定时器
创建一个新的软件定时器, 并返回定时器的句柄
创建的定时器默认处于休眠状态,不会开始工作,需通过其他API激活定时器开始计时
xTimerStart()、
xTimerReset()、
xTimerStartFromISR()、
xTimerResetFromISR()、
xTimerChangePeriod() 以及 xTimerChangePeriodFromISR() API 函数都可以用于将定时器转换为活动状态
configUSE_TIMERS 和 configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中同时设置为 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configUSE_TIMERS 1
TimerHandle_t xTimerCreate ( const char * const pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
参数:
pcTimerName 分配给定时器的可读文本名称
xTimerPeriod 定时器的周期。 以 tick 为单位指定此周期,宏 pdMS_TO_TICKS() 可用于将以毫秒为单位指定的时间转换为以 tick 为单位指定的时间。 例如, 如果定时器必须要在 100 次 tick 后到期,那么只需将 xTimerPeriod 设置为 100。 或者,如果定时器 必须在 500 毫秒后到期,则需要将 xTimerPeriod 设置为 pdMS_TO_TICKS( 500 )。 使用 pdMS_TO_TICKS() 的唯一前提条件是 configTICK_RATE_HZ 小于或等于 1000。 定时器周期必须大于 0。
uxAutoReload 如果 uxAutoReload 设置为 pdTRUE ,则定时器将按 xTimerPeriod 参数设置的频率重复到期。 如果 uxAutoReload 设置为 pdFALSE,则此定时器为一次性定时器, 它会在到期后进入休眠状态。
pvTimerID 分配给正在创建的定时器的标识符。 通常,此标识符用于定时器回调函数: 当同一个回调函数分配给了多个定时器时,此标识符可以识别哪个定时器已到期。 或者此标识符可与 vTimerSetTimerID() 和 pvTimerGetTimerID() API 函数一起使用, 以便保存调用 定时器回调函数之间的值。
pxCallbackFunction 定时器到期时调用的函数。 回调函数必须有 TimerCallbackFunction_t 定义的原型,即:
void vCallbackFunction( TimerHandle_t xTimer );
Returns:
如果定时器创建成功, 则返回新创建的定时器的句柄。 如果由于剩余的 FreeRTOS 堆不足以分配定时器结构体而无法创建定时器, 则返回 NULL。
用法示例:
#define NUM_TIMERS 5
/* An array to hold handles to the created timers. */
TimerHandle_t xTimers[ NUM_TIMERS ];
/* 定义一个将被多个计时器实例使用的回调函数。
回调函数什么也不做,只是计算相关定时器超时的次数,并在定时器超时10次时停止定时器。计数保存为计时器的ID. */
void vTimerCallback( TimerHandle_t xTimer )
{
const uint32_t ulMaxExpiryCountBeforeStopping = 10;
uint32_t ulCount;
/* 查询触发回调函数的定时器ID. */
ulCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
/* ID自增. */
ulCount++;
/* 判断ID是否到达10. */
if( ulCount >= ulMaxExpiryCountBeforeStopping )
{
/* ID 到达10 停止此定时器 */
xTimerStop( xTimer, 0 );
}
else
{
/* 将递增的计数存储回定时器的ID字段. */
vTimerSetTimerID( xTimer, ( void * ) ulCount );
}
}
void main( void )
{
long x;
/* Create then start some timers. Starting the timers before
the RTOS scheduler has been started means the timers will start
running immediately that the RTOS scheduler starts. */
for( x = 0; x < NUM_TIMERS; x++ )
{
xTimers[ x ] = xTimerCreate
( /* Just a text name, not used by the RTOS
kernel. */
"Timer",
/* The timer period in ticks, must be
greater than 0. */
( 100