FreeRTOS — 定时器组
以下内容转载自安富莱电子:http://forum.armfly.com/forum.php
FreeRTOS 支持的定时器组,或者叫软件定时器,又或者叫用户定时器均可。软件定时器的功能比较简单,也容易掌握。被称为定时器组是因为用户可以创建多个定时器,创建的个数是可配置的。
1 、定 时 器 组 介 绍
FreeRTOS 软件定时器组的时基是基于系统时钟节拍实现的,之所以叫软件定时器是因为它的实现不需要使用任何硬件定时器,而且可以创建很多个,综合这些因素,这个功能就被称之为软件定时器组。
既然是定时器,那么它实现的功能与硬件定时器也是类似的。在硬件定时器中,我们是在定时器中断中实现需要的功能,而使用软件定时器时,我们是在创建软件定时器时指定软件定时器的回调函数,在回调函数中实现相应的功能。
1.1 单次模式和周期模式
FreeRTOS 提供的软件定时器支持单次模式和周期性模式,单次模式就是用户创建了定时器并启动了定时器后,定时时间到将不再重新执行,这就是单次模式软件定时器的含义。周期模式就是此定时器会按照设置的时间周期重复去执行,这就是周期模式软件定时器的含义。另外就是单次模式或者周期模式的定时时间到后会调用定时器的回调函数,用户可以回调函数中加入需要执行的工程代码。
2 、 定 时 器 任 务 ( Daemon 任 务)
为了更好的管理 FreeRTOS 的定时器组件,专门创建了一个定时器任务,或者称之为 Daemon 任务。关于这个任务,我们上章节在讲解事件标志组的时候有用到。
FreeRTOS 定时器组的大部分 API 函数都是通过消息队列给定时器任务发消息,在定时器任务里面执行实际的操作。为了更好的说明这个问题,我们将官方在线版手册中的这个截图贴出来进行说明:
左侧图是用户应用程序,右侧是定时器任务。在用户应用程序里面调用了定时器组API函数xTimerReset,这个函数会通过消息队列给定时器任务发消息,在定时器任务里面执行实际操作。消息队列在此处的作用有一个专门的名字:Timer command queue,即专门发送定时器组命令的队列。
3、 使 用 软 件 定 时 器 组 注 意 事 项
定时器回调函数是在定时器任务中执行的,实际应用中切不可在定时器回调函数中调用任何将定时器任务挂起的函数,比如vTaskDelay(), vTaskDelayUntil()以及非零延迟的消息队列和信号量相关的函数。将定时器任务挂起,会导致定时器任务负责的相关功能都不能正确执行了。
4、 定 时 器组 API 函 数
使用如下 20 个函数可以实现 FreeRTOS 的定时器组:
xTimerCreate()
xTimerCreateStatic()
xTimerIsTimerActive()
xTimerStart()
xTimerStop()
xTimerChangePeriod()
xTimerDelete()
xTimerReset()
xTimerStartFromISR()
xTimerStopFromISR()
xTimerChangePeriodFromISR()
xTimerResetFromISR()
pvTimerGetTimerID()
vTimerSetTimerID()
xTimerGetTimerDaemonTaskHandle()
xTimerPendFunctionCall()
xTimerPendFunctionCallFromISR()
pcTimerGetName()
xTimerGetPeriod()
xTimerGetExpiryTime()
关于这 20 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册:
这里重点的说以下 3 个函数:
xTimerCreate()
xTimerStart ()
pvTimerGetTimerID ()
因为本章节的例子使用的是这 3 个函数。
4.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 用于创建软件定时器。
第 1 个参数是定时器名字,用于调试目的,方便识别不同的定时器。
第 2 个参数是定时器周期,单位系统时钟节拍。
第 3 个参数是选择周期模式还是单次模式,若参数为 pdTRUE,则表示选择周期模式,若参数为pdFALSE,则表示选择单次模式。
第 4 个参数是定时器 ID,当创建不同的定时器,但使用相同的回调函数时,在回调函数中通过不同的ID 号来区分不同的定时器。
第 5 个参数是定时器回调函数。
返回值,创建成功返回定时器的句柄,由于 FreeRTOSCongfig.h 文件中 heap 空间不足,或者定时器周期设置为 0,会返回 NULL。
使用这个函数要注意以下问题:
1. 在 FreeRTOSConfig.h 文件中使能宏定义:
#define configUSE_TIMERS 1
使用举例:
static TimerHandle_t xTimers = NULL; /* ********************************************************************************************************* * 函 数 名: AppObjCreate * 功能说明: 创建任务通信机制 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void AppObjCreate (void) { const TickType_t xTimerPer = 100; /* 创建定时器,如果在 RTOS 调度开始前初始化定时器,那么系统启动后才会执行。 */ xTimers = xTimerCreate("Timer", /* 定时器名字 */ xTimerPer, /* 定时器周期,单位时钟节拍 */ pdTRUE, /* 周期性 */ (void *) i, /* 定时器 ID */ vTimerCallback); /* 定时器回调函数 */ if(xTimers == NULL) { /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */ } else { /* 启动定时器,系统启动后才开始工作 */ if(xTimerStart(xTimers, 100) != pdPASS) { /* 定时器还没有进入激活状态 */ } } }
4.2 函数 xTimerStart
函数原型:
BaseType_t xTimerStart( TimerHandle_t xTimer, /* 定时器句柄 */
TickType_t xBlockTime ); /* 成功启动定时器前的最大等待时间设置,单位系统时钟节拍 */
函数描述:
函数 xTimerStart 用于启动软件定时器。
第 1 个参数是定时器句柄。
第 2 个参数是成功启动定时器前的最大等待时间设置,单位系统时钟节拍,定时器组的大部分 API函数不是直接运行的,而是通过消息队列给定时器任务发消息来实现的,此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。
返回值,返回 pdFAIL 表示此函数向消息队列发送消息失败,返回 pdPASS 表示此函数向消息队列发送消息成功。定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放 CPU 权才可以得到执行。
使用这个函数要注意以下问题:
1. 使用前一定要保证定时器组已经通过函数 xTimerCreate 创建了。
2. 在 FreeRTOSConfig.h 文件中使能宏定义:
#define configUSE_TIMERS 1
3. 对于已经被激活的定时器,即调用过函数 xTimerStart 进行启动,再次调用此函数相当于调用了函数xTimerReset 对定时器时间进行了复位。
4. 如果在启动 FreeRTOS 调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了 FreeRTOS调度器才会得到执行,即从此刻开始计时,达到xTimerCreate 中设置的单次或者周期性延迟时间才会执行相应的回调函数。
使用举例:同上.
4.3 函数 pvTimerGetTimerID
函数原型:
void *pvTimerGetTimerID( TimerHandle_t xTimer ); /* 定时器句柄 */
函数描述:
函数 pvTimerGetTimerID 用于返回使用函数 xTimerCreate 创建的软件定时器 ID。
第 1 个参数是定时器句柄。
返回值,返回定时器 ID。
使用这个函数要注意以下问题:
1. 使用前一定要保证定时器组已经通过函数 xTimerCreate 创建了。
2. 在 FreeRTOSConfig.h 文件中使能宏定义:
#define configUSE_TIMERS 1
3. 创建不同的定时器时,可以对定时器使用相同的回调函数,在回调函数中通过此函数获取是哪个定时器的时间到了,这个功能就是此函数的主要作用。
使用举例:
/* ********************************************************************************************************* * 函 数 名: 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); } }
实验例程:
配置项:
实验验证:
使用软件定时器,100ms一次实现led反转,1000ms一次Beep翻转。
主要展示定时器任务和回调函数:
定时器任务:
static void AppObjCreate (void) { uint8_t i; /*使用软件定时器,1000ms一次实现led反转,1000ms一次Beep翻转*/ const TickType_t xTimerPer[2] = {1000, 1000}; /* 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) //等待延时100ms,其实设置成0在简单任务下也是可以的,这个数值根据项目需求更改 { /* 定时器还没有进入激活状态 */ } } } }
回调函数:
static void vTimerCallback(xTimerHandle pxTimer) { uint8_t ulTimerID; configASSERT(pxTimer); /* 获取那个定时器时间到 */ // ulTimerID = *(uint8_t *)pvTimerGetTimerID(pxTimer); ulTimerID = (uint8_t )pvTimerGetTimerID(pxTimer); /* 处理定时器0任务 */ if(ulTimerID == 0) { LED1_TOGGLE; } /* 处理定时器1任务 */ if(ulTimerID == 1) { BEEP_TOGGLE; } }
通过ID不同,判断是哪个定时器时间到,然后做相应的动作。
欢迎加入作者的小圈子
扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并,获取更多隐藏干货,QQ交流群:859800032 微信公众号:Crystal软件学堂
作者:Liu_Jing bilibili视频教程地址:https://space.bilibili.com/5782182 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。 如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。 文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。 |