引言
在嵌入式系统开发中,STM32微控制器与FreeRTOS操作系统的结合应用极为广泛。其中,件定时器作为FreeRTOS的重要功能模块,为实现精准任务调度与时间管理提供了有力支持,本文将对其作简要介绍。
软件定时器定义
软件定时器是 FreeRTOS 提供的一种功能模块,它允许用户在指定的时间间隔后执行特定的回调函数。与任务不同,软件定时器不具有独立的执行上下文,也不占用系统资源来维持运行状态。任务是 FreeRTOS 的核心调度单元,具有独立的栈空间和优先级,可以主动运行和被调度。而软件定时器仅在设定的时间点触发回调,依赖任务或其他机制来执行实际操作,更适合用于简单的定时需求,如周期性检查或延迟操作,相比任务更加轻量级。
简单来说软件定时器就是一个简单的定时任务,而且不需要占用大量内存。设置的优先级通常比较低,这样才不会打断正在运行的任务。
软件定时器
首先是定义软件定时器的句柄,并给其赋初值为NULL。这里我创建两个软件定时器。
TimerHandle_t Timer1_Handle =NULL; //软件定时器句柄1
TimerHandle_t Timer2_Handle =NULL; //软件定时器句柄2
xTimerCreate函数
该函数的原型为:
TimerHandle_t xTimerCreate(
const char * const pcTimerName, // 定时器名称(调试用)
const TickType_t xTimerPeriod, // 定时器周期(以 tick 为单位)
const UBaseType_t uxAutoReload, // 是否自动重载(pdTRUE/pdFALSE)
void * const pvTimerID, // 定时器 ID(回调函数中识别用)
TimerCallbackFunction_t pxCallbackFunction // 回调函数
);
可以看见,该函数是一个含参数且有返回值的函数。下面分析一下各个参数的作用:
pcTimerName:顾名思义,定时器名称,前面是const char*,表示常量字符串指针。
xTimerPeriod:定时周期,一般情况下都是以Tick系统节拍为单位。
uxAutoReload:是否自动重载,pdFALSE(0)表示的是单次定时器,到期后自动停止,不再触发,pdTRUE(1)表示的是周期定时器,到期后自动重置,持续触发。
pvTimerID:顾名思义,定时器ID,可自定义,主要是用来区分多个定时器
pxCallbackFunction:定时器到期是调用的函数。必要时应提前声明!
使用xTimerCreate函数
然后在临界区内创建软件定时器,并将创建返回的句柄赋值给刚刚创建的空句柄。
//创建定时器1
Timer1_Handle=xTimerCreate("AutoReloadTimer",1000,pdTRUE,(void*)1,Timer1_Callback);
//创建定时器2
Timer2_Handle=xTimerCreate("OneShotTimer",5000,pdFALSE,(void*)2,Timer2_Callback);
接着开启定时器:
if(Timer1_Handle != NULL)
{
xTimerStart(Timer1_Handle,0); //开启定时器1
}
if(Timer2_Handle != NULL)
{
xTimerStart(Timer2_Handle,0); //开启定时器2
}
实现回调函数
前面也说过了,必要时最好先声明该回调函数。
//定时器1回调函数
//软件定时器不要调用阻塞函数,也不要进行死循环,应快进快出
void Timer1_Callback(void* parameter)
{
LED=!LED;
}
//定时器2回调函数
//软件定时器不要调用阻塞函数,也不要进行死循环,应快进快出
void Timer2_Callback(void* parameter)
{
// LED=!LED;
}
当然,我这个是单独给每个定时器创建的函数,就用不到刚刚的定时器ID标识符了。如果需要更简便一点可以只用一个回调函数,然后在里面根据不同的ID标识符设置相应的逻辑。
void vUnifiedCallback(TimerHandle_t xTimer)
{
uint32_t id = (uint32_t)pvTimerGetTimerID(xTimer);
switch(id)
{
//在switch语句中根据不同的ID标识符做出对应的逻辑
}
}
示例代码
cs
#include "myfreertos.h"
#include "FreeRTOS.h"
#include "event_groups.h"
#include "Timers.h"
#include "semphr.h"
#include "Timers.h"
#include "queue.h"
#include "Usart.h"
#include "Task.h"
#include "led.h"
#include "key.h"
TaskHandle_t MyTaskHandler;//主任务句柄
TaskHandle_t LED_TASK_Handler;//LED任务句柄
TaskHandle_t KEY_TASK_Handler; //LED任务句柄
void MyTask(void *pvParameters); //声明启动函数
void LED_TASK(void *pvParameters); //声明LED任务函数
void KEY_TASK(void *pvParameters); //声明KEY任务函数
TimerHandle_t Timer1_Handle =NULL; //软件定时器句柄1
TimerHandle_t Timer2_Handle =NULL; //软件定时器句柄2
static void Timer1_Callback(void* parameter); //软件定时器1回调函数
static void Timer2_Callback(void* parameter); //软件定时器2回调函数
void Start_Task(void)
{
xTaskCreate(MyTask,"MyTask",128,NULL,1,&MyTaskHandler);//动态方法创建任务
vTaskStartScheduler();//启动任务调动
}
void MyTask(void *pvParameters) //开始创建任务函数
{
taskENTER_CRITICAL(); //进入临界区
//创建定时器1
Timer1_Handle=xTimerCreate("AutoReloadTimer",1000,pdTRUE,(void*)1,Timer1_Callback);
//创建定时器2
Timer2_Handle=xTimerCreate("OneShotTimer",5000,pdFALSE,(void*)2,Timer2_Callback);
xTaskCreate(LED_TASK,"LED_TASK",50,NULL,3,&LED_TASK_Handler);//动态方法创建LED任务
xTaskCreate(KEY_TASK,"KEY_TASK",50,NULL,4,&KEY_TASK_Handler);//动态方法创建LED任务
vTaskDelete(MyTaskHandler); //删除开始任务
if(Timer1_Handle != NULL)
{
xTimerStart(Timer1_Handle,0); //开启定时器1
}
if(Timer2_Handle != NULL)
{
xTimerStart(Timer2_Handle,0); //开启定时器2
}
taskEXIT_CRITICAL(); //退出临界区
}
void LED_TASK(void *pvParameters)
{
while(1)
{
vTaskDelay(200);
}
}
void KEY_TASK(void *pvParameters)
{
while(1)
{
vTaskDelay(200);
}
}
//定时器1回调函数
//软件定时器不要调用阻塞函数,也不要进行死循环,应快进快出
void Timer1_Callback(void* parameter)
{
LED=!LED;
}
//定时器2回调函数
//软件定时器不要调用阻塞函数,也不要进行死循环,应快进快出
void Timer2_Callback(void* parameter)
{
// LED=!LED;
}
这个代码就是创建两个软件定时器和两个任务。然后在任务阻塞的时候调用软件定时器,然后进入定时器的回调函数。
总结
以上仅是本人的观点,如有不足,欢迎指出!