摘要(From AI):
这篇笔记全面介绍了 FreeRTOS 软件定时器的核心概念和使用方法,包括定时器的创建、管理、常用 API 和辅助函数,并通过示例代码演示了如何启动、重置和更改定时器的周期。它强调了软件定时器的灵活性、平台无关性以及与硬件定时器的对比
前言:本文档是本人在依照B站UP:Michael_ee的视频教程进行学习时所做的学习笔记,可能存在疏漏和错误,如有发现,望指正。
上一篇:ESP32学习笔记_FreeRTOS(2)——Queue
文章目录
- Software Timer
- Creating a Software Timer
- xTimerCreate()
- Managing Software Timers
- xTimerStart()
- xTimerStop()
- pcTimerGetName()
- pvTimerGetTimerID()
- xTimerReset()
- xTimerChangePeriod()
- Example Code:Using Software Timers
参考资料:
[Michael_ee视频教程] https://www.bilibili.com/video/BV1Nb4y1q7xz?spm_id_from=333.788.videopod.sections&vd_source=4d8bd0ed3878ef81b239bf69bf38e741
freeRTOS官网
espressif 在线文档
Software Timer
硬件定时器会有数量等方面的限制,使用较不灵活,而软件定时器使用更为灵活,其与硬件、平台无关,在不同的 MCU 都可以使用 FreeRTOS API 进行调用
特性 | 硬件定时器 | 软件定时器 |
---|---|---|
数量 | 固定,受 MCU 硬件资源限制(通常只有几个) | 灵活,可以根据需要动态创建(受内存和任务管理能力限制) |
依赖性 | 依赖具体硬件平台,配置方式和功能因芯片而异 | 与硬件平台无关,可通过 FreeRTOS API 在不同 MCU 上使用 |
精度 | 高精度,直接由硬件计时,通常用于实时性要求高的场景 | 精度依赖于 RTOS 的调度周期(tick 周期),不适合极高实时性场景 |
性能 | 高性能,独立运行,不占用 CPU 资源 | 运行在 RTOS 守护任务上下文中,占用 CPU 资源 |
适用场景 | 适合时间敏感的应用,如 PWM 信号生成、脉冲捕获、输入输出事件计时等 | 适合通用定时功能,如定时任务执行、软件超时处理等 |
灵活性 | 配置固定,功能和用途受限 | 灵活性高,可动态调整超时时间、回调函数等 |
使用复杂度 | 配置复杂,需根据芯片手册手动设置寄存器 | 使用方便,通过 FreeRTOS API 调用 |
移植性 | 差,代码与硬件平台强耦合 | 好,代码与硬件无关,便于跨平台移植 |
所有软件定时器的回调函数都在同一个RTOS守护任务(也称为“定时器服务任务”)的上下文中执行(该任务最初被称为“定时器服务任务”,因为最初它只用于执行软件定时器回调函数。现在同一任务也用于其他用途,因此被改名为更通用的“RTOS 守护任务”)
守护任务是一个标准的FreeRTOS任务,会在调度器启动时自动创建。其优先级和堆栈大小由编译时配置常量configTIMER_TASK_PRIORITY
和configTIMER_TASK_STACK_DEPTH
分别设置,这两个常量在FreeRTOSConfig.h
中定义
需要注意,软件定时器的回调函数是在 RTOS 守护任务的上下文中执行的,而不是在独立的任务中运行。因此,回调函数中不能调用可能使任务进入阻塞状态的 FreeRTOS API 函数,因为这会阻塞整个守护任务,导致系统运行异常
Creating a Software Timer
xTimerCreate()
xTimerCreate()
用于创建一个新的软件定时器,并返回一个句柄以引用创建的定时器
#include “FreeRTOS.h”
#include “timers.h”
TimerHandle_t xTimerCreate( const char *pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
参数
pcTimerName
定时器的名称,仅用于调试xTimerPeriod定时器周期,单位为系统 tick,不能为 0。可以使用
pdMS_TO_TICKS()` 宏将毫秒转换为 tick。例如:- 100 tick直接设置为 100
- 500ms可使用
pdMS_TO_TICKS(500)
,前提是configTICK_RATE_HZ <= 1000
uxAutoReload
设置定时器类型:pdTRUE
自动重载定时器(周期性触发)pdFALSE
一次性定时器(仅触发一次,可手动重新启动)
pvTimerID
定时器标识符,用于在回调函数中区分不同的定时器,或在回调调用之间存储值pxCallbackFunction
:定时器到期时执行的回调函数,需符合TimerCallbackFunction_t
原型:
void vCallbackFunctionExample(TimerHandle_t xTimer);
configTICK_RATE_HZ
是 FreeRTOS 配置文件FreeRTOSConfig.h
中定义的一个宏,它表示 每秒系统 Tick 的次数,即 FreeRTOS 的调度器每秒中断的频率(单位为 Hz)
例如:
如果configTICK_RATE_HZ = 1000
,表示系统每 1 毫秒触发一次 Tick 中断
如果configTICK_RATE_HZ = 100
,表示系统每 10 毫秒触发一次 Tick 中断
返回值:
NULL
定时器创建失败,原因可能是 FreeRTOS 堆内存不足其他值
定时器创建成功,返回的句柄可用于引用该定时器
配置要求(一般不用动)
- 在
FreeRTOSConfig.h
文件中,configUSE_TIMERS
和configSUPPORT_DYNAMIC_ALLOCATION
必须都设置为1
- 如果
configSUPPORT_DYNAMIC_ALLOCATION
未定义,其默认值为1
创建定时器并不会立即启动。可以使用以下函数来启动或管理定时器
// 启动定时器。如果定时器已经在运行,则从当前时间重新开始。
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
// 重置(重新启动)定时器。确保定时器启动或重新计算到期时间。
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
// 从中断上下文启动定时器。等效于 xTimerStart(),用于中断中调用。
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
// 从中断上下文重置(重新启动)定时器。等效于 xTimerReset(),用于中断中调用。
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
// 更改定时器的周期。如果定时器未运行,则会启动定时器。
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xTicksToWait );
// 从中断上下文更改定时器的周期。等效于 xTimerChangePeriod(),用于中断中调用。
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
TickType_t xNewPeriod,
BaseType_t *pxHigherPriorityTaskWoken );
Managing Software Timers
xTimerStart()
xTimerStart() 用于启动一个软件定时器的运行
- 如果定时器尚未运行,
xTimerStart()
会计算一个到期时间,该时间相对于调用xTimerStart()
的时刻 - 如果定时器已经在运行,则
xTimerStart()
相当于调用了xTimerReset()
,即重置定时器 - 定时器会在定义的周期后(即
xTimerStart()
调用后n
个 tick)触发回调函数,除非定时器在此期间被停止、删除或重置
#include “FreeRTOS.h”
#include “timers.h”
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
参数
xTimer
:要启动、重置或重新启动的定时器句柄xTicksToWait
指定调用任务在timer command queue
队列已满的情况下,等待空间可用的最大时间(单位为 tick)。这是任务在进入 Blocked 状态时的阻塞时间。如果队列已满,任务会被阻塞,直到有足够的空间来发送命令- 设置
xTicksToWait
为portMAX_DELAY
将导致任务无限期等待,直到队列中有空间 - 如果在调度器启动之前调用
xTimerStart()
,则xTicksToWait
会被忽略
- 设置
当任务调用
xTimerStart()
或其他定时器相关 API 时,这些命令并不会立即由任务执行,而是通过一个队列传递给定时器服务任务
如果队列已满,新的命令会被阻塞,直到队列有空间可用。这时,调用xTimerStart()
等 API 的任务会根据指定的阻塞时间(xTicksToWait
)进入阻塞状态,等待队列空间变得可用
timer command queue
的大小由 FreeRTOS 的配置项决定。队列的大小设置影响系统可以同时处理多少个定时器命令。如果队列大小太小,可能会导致命令丢失或任务阻塞:
configTIMER_QUEUE_LENGTH
:定义了timer command queue
队列的最大长度(即可以存放多少个定时器命令)
configUSE_TIMERS
:必须设置为 1,才能启用定时器功能和相关队列
返回值
pdPASS
启动命令成功发送到定时器命令队列。如果指定了阻塞时间(即xTicksToWait
不为零),则可能会因为队列已满,任务进入阻塞状态等待空间释放,直到数据成功写入队列- 定时器命令的处理时间会根据定时器服务任务的优先级而有所不同,但定时器的到期时间是相对于实际调用
xTimerStart()
时刻(从队列中取出命令并实际启动定时器)的 - 定时器命令的处理时间受定时器服务任务优先级的影响,定时器服务任务的优先级由
configTIMER_TASK_PRIORITY
配置常量设置
- 定时器命令的处理时间会根据定时器服务任务的优先级而有所不同,但定时器的到期时间是相对于实际调用
pdFAIL
启动命令未成功发送到定时器命令队列,原因是队列已满。如果指定了阻塞时间,任务会被阻塞等待队列有空间,直到指定的阻塞时间过期,但仍未成功写入数据到队列
注意事项(一般不用动)
在 FreeRTOSConfig.h
中,configUSE_TIMERS
必须设置为 1,才能使用 xTimerStart()
函数
xTimerStop()
xTimerStop()
用于停止一个运行中的软件定时器
- 调用
xTimerStop()
可以停止一个正在运行的定时器。如果定时器已经停止或已过期,则调用xTimerStop()
不会产生影响。 xTimerStop()
向定时器命令队列发送停止命令,定时器服务任务会在稍后处理该命令。
#include “FreeRTOS.h”
#include “timers.h”
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
参数
xTimer
要停止的定时器句柄。xTicksToWait
指定任务在定时器命令队列已满的情况下,最大等待时间(单位为 ticks)
返回值
pdPASS
pdFAIL
pcTimerGetName()
pcTimerGetName()
用于返回在创建定时器时分配的可读文本名称
#include “FreeRTOS.h”
#include “timers.h”
const char * pcTimerGetName( TimerHandle_t xTimer );
返回值
- 返回值为一个指向定时器名称的指针。
- 定时器名称是一个标准的以
NULL
结尾的 C 字符串。
pvTimerGetTimerID()
pvTimerGetTimerID()
用于返回与定时器关联的标识符(ID)
- 返回在创建定时器时分配的标识符,该标识符可以通过
vTimerSetTimerID()
API 更新 - 在回调函数中可以使用该标识符区分哪个定时器到期,特别是在多个定时器共享相同的回调函数时
#include “FreeRTOS.h”
#include “timers.h”
void *pvTimerGetTimerID( TimerHandle_t xTimer );
返回值
- 返回与指定定时器关联的标识符(指针类型)
xTimerReset()
xTimerReset()
用于重置、启动或重新启动一个软件定时器,能够起到 Watch Dog 的作用
- 如果定时器正在运行,
xTimerReset()
会将定时器的到期时间重新计算为相对于调用时间的周期 - 如果定时器未运行,
xTimerReset()
会启动定时器,并将到期时间计算为相对于调用时间的周期。此时等效于xTimerStart()
- 无论定时器当前是否运行,调用
xTimerReset()
后,定时器都将开始运行
#include “FreeRTOS.h”
#include “timers.h”
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
返回值
pdPASS
pdFAIL
xTimerChangePeriod()
xTimerChangePeriod()
用于更改软件定时器的周期
- 更改运行中定时器的周期
- 如果定时器正在运行,则新周期将用于重新计算到期时间。
- 新的到期时间相对于调用
xTimerChangePeriod()
的时刻,而不是定时器最初启动的时刻。
- 启动未运行的定时器
- 如果定时器未运行,则定时器将使用新的周期值计算到期时间,并开始运行。
#include “FreeRTOS.h”
#include “timers.h”
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xTicksToWait );
参数
xTimer
需要更改周期的定时器句柄xNewPeriod
定时器的新周期(单位为 tick)。可使用pdMS_TO_TICKS()
将毫秒转换为 tickxTicksToWait
阻塞任务的最大时间(单位为 tick),如果定时器命令队列已满,则等待空间可用
返回值
pdPASS
pdFAIL
Example Code:Using Software Timers
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "freertos/timers.h" // 定时器头文件
void TimerCallback(TimerHandle_t xTimer)
{
const char *pcTimerName = pcTimerGetName(xTimer);// 获取定时器名称
uint32_t *uiTimerID = (uint32_t *)pvTimerGetTimerID(xTimer);// 获取定时器ID
printf("%s expired, ID: %lu.\n", pcTimerName, *uiTimerID);// 打印定时器名称和ID
}
int id1 = 0;
int id2 = 1;
void app_main(void)
{
TimerHandle_t TimerHandle1 = NULL;
TimerHandle1 = xTimerCreate("Timer1", pdMS_TO_TICKS(1000), pdTRUE, (void *)&id1, TimerCallback);// 创建一个周期为1000ms的定时器
xTimerStart(TimerHandle1, 0);// 启动定时器
TimerHandle_t TimerHandle2 = NULL;
TimerHandle2 = xTimerCreate("Timer2", pdMS_TO_TICKS(2000), pdTRUE, (void *)&id2, TimerCallback);// 与Timer1公用同一个回调函数,观察pcTimerGetName的输出
xTimerStart(TimerHandle2, 0);// 启动定时器
// for(int i = 0; i < 10; i++)
// { // vTaskDelay(pdMS_TO_TICKS(1000)); // xTimerReset(TimerHandle2, 0);// 重置定时器,观察pcTimerGetName的输出,此时Timer2不会被打印
// }
vTaskDelay(pdMS_TO_TICKS(5000));
xTimerChangePeriod(TimerHandle2, pdMS_TO_TICKS(1000), 0);// 修改Timer2的周期为1000ms
vTaskDelay(pdMS_TO_TICKS(5000));
xTimerStop(TimerHandle1, 0);// 停止定时器
xTimerStop(TimerHandle2, 0);// 停止定时器
}