FreeRTOS软件定时器概述
FreeRTOS的软件定时器(Software Timer)提供了一种基于系统节拍(Tick)的定时功能,允许创建周期性或单次触发的定时任务。软件定时器由内核调度,通过回调函数实现定时逻辑,适用于低精度定时场景(如秒级或毫秒级)。
软件定时器是基于 FreeRTOS 内核提供的时间管理功能实现的,允许开发者创建、启动、停止、删除和管理定时器,从而实现在任务中对时间的灵活控制。
软件定时器与硬件定时器的主要区别如下:
软件定时器 |
硬件定时器 |
FreeRTOS提供的功能来模拟定时器,依赖系统的任务调度器来进行计时和任务调度 |
由芯片或微控制器提供,独立于 CPU,可以在后台运行,不受任务调度器的影响 |
精度和分辨率可能受到任务调度的影响 |
具有更高的精度和分辨率 |
不需要额外的硬件资源,但可能会增加系统的负载 |
占用硬件资源,不会增加 CPU 的负载 |
软件定时器能够让函数在未来的设定时间执行。由定时器执行的函数称为定时器的回调函数。从定时器启动到其回调函数执行之间的时间被称为定时器的周期。简而言之,当定时器的周期到期时,定时器的回调函数会被执行。
软件定时器特性:
- 单次模式(One-shot):定时器触发一次后自动停止。
- 自动重载模式(Auto-reload):定时器周期性触发直到手动停止。
- 回调函数:定时器触发时执行用户定义的函数。
- 动态/静态创建:支持从堆内存或用户指定内存创建定时器。
软件定时器的状态:
FreeRTOS 中的软件定时器有三种状态,分别是:
- 未创建(Uncreated):软件定时器被创建之前的状态。在这个状态下,定时器的数据结构已经被定义,但尚未通过 xTimerCreate() 函数创建。
- 已创建(Created):软件定时器已被成功创建,但尚未启动。在这个状态下,可以对定时器进行配置,如设置定时器的周期、回调函数等,但定时器并未开始计时。
- 已运行(Running):软件定时器已经被启动,正在运行中。在这个状态下,定时器会按照预定的周期定时触发超时事件,执行注册的回调函数。
单次定时器和周期定时器
在 FreeRTOS 中,软件定时器主要有两种类型:一次性定时器和周期性定时器。
- 一次性定时器(One-shot Timer): 这种定时器在触发一次超时后就会停止,不再执行。适用于只需在特定时间执行一次任务或动作的场景。
- 周期性定时器(Periodic Timer): 这种定时器会在每个超时周期都触发一次,循环执行。适用于需要在固定的时间间隔内重复执行任务或动作的场景。
软件定时器实现步骤
配置FreeRTOS启用软件定时器
在FreeRTOSConfig.h
中启用相关宏:
#define configUSE_TIMERS 1 // 启用软件定时器
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) // 定时器任务优先级
#define configTIMER_QUEUE_LENGTH 10 // 定时器命令队列长度
软件定时器相关API:
xTimerCreate() |
动态方式创建软件定时器 |
xTimerCreateStatic() |
静态方式创建软件定时器 |
xTimerStart() |
开启软件定时器定时 |
xTimerStartFromISR() |
在中断中开启软件定时器定时 |
xTimerStop() |
停止软件定时器定时 |
xTimerStopFromISR() |
在中断中停止软件定时器定时 |
xTimerReset() |
复位软件定时器定时 |
xTimerResetFromISR() |
在中断中复位软件定时器定时 |
xTimerChangePeriod() |
更改软件定时器的定时超时时间 |
xTimerChangePeriodFromISR() |
在中断中更改定时超时时间 |
使用xTimerCreate()
函数动态创建定时器:
TimerHandle_t xTimerCreate(
const char * const pcTimerName, // 定时器名称(调试用)
const TickType_t xTimerPeriod, // 周期(Tick数)
const UBaseType_t uxAutoReload, // 自动重载模式(TRUE为周期性,FALSE为单次)
void * const pvTimerID, // 定时器ID(标识用)
TimerCallbackFunction_t pxCallbackFunction // 回调函数
);
启动/停止定时器
- 启动定时器:
xTimerStart(xTimer, xTicksToWait)
xTicksToWait
:命令发送到定时器任务队列的最大等待时间(若队列满)。
- 停止定时器:
xTimerStop(xTimer, xTicksToWait)
修改定时器周期
使用xTimerChangePeriod()
动态调整周期:
xTimerChangePeriod(
xTimer,
pdMS_TO_TICKS(2000), // 新周期2000ms
0
);
示例一:创建,开启,关闭周期和单次定时器
实验一:
- start_task:用来创建task1任务,
- task1:用于按键扫描,根据按键检测的键值创建/关闭一次性定时器/周期性定时器。
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
TimerHandle_t timer1_handle = 0; /* 单次定时器 */
TimerHandle_t timer2_handle = 0; /* 周期定时器 */
/*******************启动任务重创建两个定时器************************/
timer1_handle = xTimerCreate("timer1",
500,
pdFALSE,
(void *)1,
timer1_callback);
timer2_handle = xTimerCreate("timer2",
2000,
pdTRUE,
(void *)2,
timer2_callback);
/*****************************task1*******************************/
void task1(void *p)
{
my_printf("task1 running............\r\n");
HAL_Delay(3);
uint8_t key;
while (1)
{
Key_scan();
key = Key_GetValue();
if(key) My_OLED_ShowNum(2, 1, key, 2);
// HAL_Delay(1);
if (key == 1)
{
xTimerStart(timer1_handle, portMAX_DELAY); //如果链表满了,就一直阻塞
}
else if(key == 3)
{
xTimerStart(timer2_handle, portMAX_DELAY);
}
else if(key == 2)
{
xTimerStop(timer1_handle, portMAX_DELAY);
}
else if( key == 4)
{
xTimerStop(timer2_handle, portMAX_DELAY);
}
vTaskDelay(1);
}
}
void timer1_callback(TimerHandle_t pxTimer)
{
static uint32_t timer = 0;
printf("timer1的运行次数=%d\r\n", ++timer);
HAL_GPIO_TogglePin(LED_RED_GPIO_Port,LED_RED_Pin);
HAL_Delay(2);
}
void timer2_callback(TimerHandle_t pxTimer)
{
static uint32_t timer = 0;
printf("timer2的运行次数=%d\r\n", ++timer);
HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port,LED_BLUE_Pin);
HAL_Delay(2);
}
实验现象:
示例二:中断中开启/关闭周期定时器
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
uint8_t key_num = 0;
BaseType_t pxHigherPriorityTaskWoken;
if (GPIO_Pin == KEY_2_Pin) // 按键2 产生的中断信号:KEY_2、PA1、闲时上拉、按下时低电平、下降沿触发
{
// HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); // 反转红色LED灯
key_num =2;
printf("KEY_2 按下\r\n"); // 通过串口助手输出提示
My_OLED_ShowNum(2, 1, key_num, 2);
xTimerStartFromISR(timer2_handle, &pxHigherPriorityTaskWoken);
}
if (GPIO_Pin == KEY_3_Pin) // 按键3 产生的中断信号:KEY_3、PA4、闲时上拉、按下低高电平、下降沿触发
{
// HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); // 反转红色LED灯
key_num =3;
printf("KEY_3 按下\r"); // 通过串口助手输出提示
My_OLED_ShowNum(2, 1, key_num, 2);
xTimerStopFromISR(timer2_handle, &pxHigherPriorityTaskWoken);
}
}
注意事项
- 定时器任务优先级:软件定时器的回调函数在独立的定时器任务中执行,优先级需合理设置(通常较高)。
- 回调函数限制:避免在回调函数中使用阻塞操作(如
vTaskDelay
)。 - 资源竞争:若回调函数访问共享资源,需添加互斥机制(如信号量)。
- 静态创建:使用
xTimerCreateStatic()
时需预先分配内存。
通过合理配置和API调用,FreeRTOS软件定时器可以高效管理定时任务,适用于事件触发、状态轮询等场景。