FreeRTOS与软件定时器(七)
一、软件定时器介绍
1.1 介绍
软件定时器分两种类型:一次性定时器和周期性定时器
一次性定时器:启动后,到达设定时间触发一次回调,之后自动停止。(如定时器按键)
周期性定时器:启动后,每隔设定时间触发一次回调,循环执行。(如周期性采集数据)
软件定时器和硬件定时器的功能一样,都是定时器,只不过软件定时器是基于FreeRTOS心跳基准(比如滴答定时器)软件编写的定时器功能
软件定时器实际工程场景:周期性数据采集、设备初始化延时、通信超时检测等典型应用等。
软件定时器需要用到回调函数,本质上软件定时器回调函数也是一个系统性任务,他和Task任务稍有不同,软件定时器更注重周期性的运行。与Task任务相比,软件定时器比Task任务更适合传感器周期性采集数据。且回调函数不能使用阻塞 API(如vTaskDelay
、queueReceive
),否则会阻塞整个定时器服务。
1.2 在实际开发中,什么时候用"软件定时器"方案,什么时候用"任务 +vTaskDelay"方案好?
简单的、周期性的事情用软件定时器方案
当定时触发的操作逻辑复杂、耗时较长,或需要灵活调整周期时,用“任务 +vTaskDelay
” 方案
并且资源占用上:
软件定时器方案:一个定时器句柄(约几十字节)+ 定时器服务任务栈(固定)。
任务 +vTaskDelay
”方案:一个任务的独立栈空间(每个通常几百字节,总和更大)
1.3 在实际开发中,工程师们是如何协调使用软件定时器和任务之间的运行的?
核心原则:分离 “触发” 与 “处理”
软件定时器的核心职责是按时间触发事件(轻量操作),而复杂逻辑(如数据处理、外设控制、网络交互等)必须交给独立任务处理。两者通过 IPC 机制(队列、信号量、事件标志组等)衔接,形成 “定时器回调发通知→任务等通知并处理” 的协作模式。
实际开发中的 “混合用法”:
多数工程中两者是配合使用的,例如:
用软件定时器(1 秒周期)触发 “传感器原始数据读取”(轻量操作),回调中通过消息队列将数据发给 “数据处理任务”;
“数据处理任务”(用
vTaskDelay
控制 10 秒周期)从队列中取数据,执行滤波、校准、计算等复杂操作,再上传到云端。
由于内容并不复杂,接下来直接讲软件定时器的例程,通过看例程学习软件定时器相关的API函数
二、软件定时器例程
定义结构体和变量
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>// 全局变量:存储传感器数据
float g_temperature = 0.0f; // 温度
float g_humidity = 0.0f; // 湿度
uint8_t g_device_status = 0; // 设备状态(0-正常,1-异常)// 定时器句柄(用于操作定时器)
TimerHandle_t xSensorTimer; // 传感器采集定时器
TimerHandle_t xReportTimer; // 数据上报定时器
TimerHandle_t xCheckTimer; // 状态检查定时器
创建任务
创建三个软件定时器,并使用了3个定时器回调函数
/*** @brief 传感器采集定时器回调函数(每2秒执行一次)* 功能:模拟读取温湿度传感器数据*/
void vSensorTimerCallback(TimerHandle_t xTimer)
{g_temperature = Read_TempVal();//读取传感器温度值g_humidity = Read_HumiVal();//读取传感器湿度值printf("[传感器采集] 温度: %.1f℃, 湿度: %.1f%%\r\n", g_temperature, g_humidity);
}/*** @brief 数据上报定时器回调函数(每10秒执行一次)* 功能:将采集的温湿度数据上报到服务器*/
void vReportTimerCallback(TimerHandle_t xTimer)
{// 实际工程中这里会通过网络(WiFi/4G等)发送数据printf("\r\n[数据上报] 开始向服务器上报数据...\r\n");printf("[数据上报] 上报内容:温度=%.1f℃, 湿度=%.1f%%, 设备状态=%s\r\n",g_temperature, g_humidity, (g_device_status == 0) ? "正常" : "异常");printf("[数据上报] 上报完成!\r\n\r\n");
}/*** @brief 状态检查定时器回调函数(每5秒执行一次)* 功能:检查设备运行状态(如电源、网络)*/
void vCheckTimerCallback(TimerHandle_t xTimer)
{// 这里随机模拟偶尔出现异常(10%概率)if(rand() % 10 < 1){g_device_status = 1; // 异常printf("[状态检查] 警告:设备状态异常!\r\n");}else{g_device_status = 0; // 正常printf("[状态检查] 设备运行正常\r\n");}
}
main函数
int main(void)
{BSP_Init();//环境监测设备初始化// 2. 创建软件定时器// 参数说明:定时器名称、周期(ms)、是否自动重载、定时器ID、回调函数xTimerCreate("SensorTimer", // 定时器名称(调试用)pdMS_TO_TICKS(2000), // 周期2000mspdTRUE, // 自动重载(周期性执行)NULL, // 不需要IDvSensorTimerCallback // 回调函数);xTimerCreate("ReportTimer",pdMS_TO_TICKS(10000), // 周期10000mspdTRUE,NULL,vReportTimerCallback);xTimerCreate("CheckTimer",pdMS_TO_TICKS(5000), // 周期5000mspdTRUE,NULL,vCheckTimerCallback);// 3.启动定时器(从现在开始计时)// 参数:定时器句柄、阻塞时间(0表示不阻塞)xTimerStart(xSensorTimer, 0);xTimerStart(xReportTimer, 0);xTimerStart(xCheckTimer, 0);}// 启动调度器vTaskStartScheduler();return 0;
}
三、总结
看了一遍例程和例程注释,对软件定时器的API函数有一定的认识了
这个例程是不是很像我们之前刚学创建Task任务时的写法?没错,这俩玩意是一样的逻辑。
和我开篇说的一样:
软件定时器回调函数与任务中间的"桥梁"用IPC机制联系通信
简单的、周期性的事情用软件定时器方案
当定时触发的操作逻辑复杂、耗时较长,或需要灵活调整周期时,用“任务 +vTaskDelay
” 方案