freeRTOS软件定时器(software timer)

目录

前言

软件定时器的特性

软件定时器的上下文

守护任务

回调函数

 软件定时器的函数

 创建

删除

启动/停止

复位

修改周期

定时器 ID

结尾


前言

软件定时器就是"闹钟",你可以设置闹钟,
        \bullet 在 30 分钟后让你起床工作
        \bullet 每隔 1 小时让你例行检查机器运行情况

软件定时器也可以完成两类事情:
        \bullet 在"未来"某个时间点,运行函数
        \bullet 周期性地运行函数

        在FreeRTOS里,我们可以设置无数个"软件定时器",它们都是基于系统滴答中断 (Tick Interrupt)


软件定时器的特性

我们在手机上添加闹钟时,需要指定时间、指定类型(一次性的,还是周期性的)、指定做什么事;还有一些过时的、不再使用的闹钟。如下图所示:

使用定时器跟使用手机闹钟是类似的:
\bullet 指定时间:启动定时器和运行回调函数,两者的间隔被称为定时器的周期 (period)。
\bullet 指定类型,定时器有两种类型:
        \circ 一次性(One-shot timers):
                这类定时器启动后,它的回调函数只会被调用一次;
                可以手工再次启动它,但是不会自动启动它。
        \circ 自动加载定时器(Auto-reload timers ):
                这类定时器启动后,时间到之后它会自动启动它;
                这使得回调函数被周期性地调用。
\bullet 指定要做什么事,就是指定回调函数

实际的闹钟分为:有效、无效两类。软件定时器也是类似的,它由两种状态:
\bullet 运行(Running、Active):运行态的定时器,当指定时间到达之后,它的回调函数会被调用
\bullet 冬眠(Dormant):冬眠态的定时器还可以通过句柄来访问它,但是它不再运行,它的回调函数不     会被调用

定时器运行情况示例如下:
\bullet Timer1:它是一次性的定时器,在 t1 启动,周期是 6 个 Tick。经过 6 个 tick 后,在 t7 执行回调函数。它的回调函数只会被执行一次,然后该定时器进入冬眠 状态。
\bullet Timer2:它是自动加载的定时器,在 t1 启动,周期是 5 个 Tick。每经过 5 个 tick 它的回调函数都被执行,比如在 t6、t11、t16 都会执行。


软件定时器的上下文

守护任务

        要理解软件定时器 API 函数的参数,特别是里面的 xTicksToWait,需要知道定时器执行的过程。FreeRTOS中有一个Tick中断,软件定时器基于Tick来运行。在哪里执行定时器函数?第一印象就是在Tick中断里执行:
        \bullet 在 Tick 中断中判断定时器是否超时
        \bullet 如果超时了,调用它的回调函数

        FreeRTOS 是 RTOS,它不允许在内核、在中断中执行不确定的代码:如果定时器函数很耗时,会影响整个系统。
        所以,FreeRTOS中,不在Tick中断中执行定时器函数。
        在哪里执行?在某个任务里执行,这个任务就是:RTOS Damemon Task,RTOS守护任务。以前被称为"Timer server",但是这个任务要做并不仅仅是定时器相关,所以改名为:RTOS Damemon Task
        当FreeRTOS的配置项configUSE_TIMERS被设置为1时,在启动调度器时,会自动创建RTOS Damemon Task

        我们自己编写的任务函数要使用定时器时,是通过"定时器命令队列"(timer command queue)和守护任务交互,如下图所示:

回调函数

定时器的回调函数的原型如下:

void ATimerCallback( TimerHandle_t xTimer );

        定时器的回调函数是在守护任务中被调用的,守护任务不是专为某个定时器服务的,它还要处理其他定时器。
        所以,定时器的回调函数不要影响其他人:
                \bullet 回调函数要尽快实行,不能进入阻塞状态
                \bullet 不要调用会导致阻塞的 API 函数,比如 vTaskDelay();
                
\bullet 可以调用 xQueueReceive()之类的函数,但是超时时间要设为 0:即刻返回,不可阻塞


 软件定时器的函数

根据定时器的状态转换图,就可以知道所涉及的函数:

 创建

要使用定时器,需要先创建它,得到它的句柄。(老套路了)
有两种方法创建定时器:动态分配内存、静态分配内存。函数原型如下:

/* 使用动态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以 Tick 为单位
* uxAutoReload: 类型, pdTRUE 表示自动加载, pdFALSE 表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* 返回值: 成功则返回 TimerHandle_t, 否则返回 NULL
*/
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 表示自动加载, pdFALSE 表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* pxTimerBuffer: 传入一个 StaticTimer_t 结构体, 将在上面构造定时器
* 返回值: 成功则返回 TimerHandle_t, 否则返回 NULL
*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                                 TickType_t xTimerPeriodInTicks,
                                 UBaseType_t uxAutoReload,
                                 void * pvTimerID,
                                 TimerCallbackFunction_t pxCallbackFunction,
                                 StaticTimer_t *pxTimerBuffer );

回调函数的类型是:

void ATimerCallback( TimerHandle_t xTimer );

typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );

删除

动态分配的定时器,不再需要时可以删除掉以回收内存。删除函数原型如下:

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

定时器的很多 API 函数,都是通过发送"命令"到命令队列,由守护任务来实现。
如果队列满了,"命令"就无法即刻写入队列。我们可以指定一个超时时间xTicksToWait, 等待一会。

启动/停止

启动定时器就是设置它的状态为运行态(Running、Active)。
停止定时器就是设置它的状态为冬眠(Dormant),让它不能运行。
涉及的函数原型如下:

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

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

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

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

        注意,这些函数的 xTicksToWait 表示的是,把命令写入命令队列的超时时间。命令队列可能已经满了,无法马上把命令写入队列里,可以等待一会。 (xTicksToWait不是定时器本身的超时时间,不是定时器本身的"周期"。)

        创建定时器时,设置了它的周期(period)。xTimerStart()函数是用来启动定时器。假设调用(这里注意是调用,而不是函数返回时的时间)xTimerStart()的时刻是tX,定时器的周期是n,那么在tX+n时刻定时器的回调函数被调用。

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

复位

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

        如果定时器已经处于运行态,使用 xTimerReset() 函数就相当于重新确定超时时间。假设调用xTimerReset()的时刻是tX,定时器的周期是n,那么tX+n就是重新确定的超时时间。

复位函数的原型如下:

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

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

修改周期

        从定时器的状态转换图可以知道,使用 xTimerChangePeriod() 函数,处理能修改它的周期外,还可以让定时器的状态从冬眠态转换为运行态
        修改定时器的周期时,会使用新的周期重新计算它的超时时间。假设调用 xTimerChangePeriod() 函数的时间tX,新的周期是n,则tX+n就是新的超时时间。

相关函数的原型如下:

/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* xTicksToWait: 超时时间, 命令写入队列的超时时间
* 返回值: pdFAIL 表示"修改周期命令"在 xTicksToWait 个 Tick 内无法写入队列
* pdPASS 表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
                               TickType_t xNewPeriod,
                               TickType_t xTicksToWait );

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

定时器 ID

定时器的结构体如下,里面有一项 pvTimerID,它就是定时器 ID:

怎么使用定时器ID,完全由程序来决定:
\bullet 可以用来标记定时器,表示自己是什么定时器
\bullet 可以用来保存参数,给回调函数使用 

它的初始值在创建定时器时由 xTimerCreate() 这类函数传入,后续可以使用这些函数来操作:
\bullet 更新ID:使用 vTimerSetTimerID() 函数
\bullet 查询ID:查询 pvTimerGetTimerID() 函数
这两个函数不涉及命令队列,它们是直接操作定时器结构体

函数原型如下:

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

/* 设置定时器的 ID
* xTimer: 哪个定时器
* pvNewID: 新 ID
* 返回值: 无
*/
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

结尾

        通常会使用软件定时器来消除按键抖动,具体操作可以去看韦东山老师的freeRTOS手册,里面有详细的代码讲解。

        至此freeRTOS的更新就到这里了,没讲的freeRTOS的中断管理资源管理都是比较深入的知识了:中断管理详细讲述了任务调度,同步互斥等内部实现的核心,就是关闭中断/关闭任务调度;而资源管理一般是用来优化程序,调整任务的栈大小,查看任务的cpu占用率,最大压榨芯片的性能。有对内部机制感兴趣可以去看韦东山老师的freeRTOS内部机制讲解,我以后可能也会更新内部机制的文章,在这里感谢韦老师对嵌入式行业做出的贡献,我的rtos都是从韦老师那里学习的,推荐大伙都去看看。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sakabu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值