FreeRTOS 软件定时器
定时器可以说是每个 MCU 都有的外设,有的 MCU 自带的定时器有着十分强大的功能,能提供 PWM、输入捕获等高级功能,但是最常用的还是定时器的基础功能——定时,通过定时器的定时功能,能够完成一些需要周期性处理的事务。MCU 自带的定时器为硬件定时器。FreeRTOS 提供的软件定时器,软件定时器在定时器精度上肯定是不如硬件定时器的,但是软件定时器的误差范围在对于对定时器精度要求不高的周期性任务而言,都是可以接受的。并且软件定时器也有使用简单、成本低等优点。软件定时器
软件定时器是指具有定时功能的软件,FreeRTOS 提供的软件定时器允许在创建前设置一个软件定时器定时超时时间,在软件定时器成功创建并启动后,软件定时器开始定时,当软件定时器的定时时间达到或超过先前设置好的软件定时器定时器超时时间时,软件定时器就处于超时状态,此时软件定时器就会调用相应的回调函数,一般这个回调函数的处理的事务就是需要周期处理的事务。
FreeRTOS 提供的软件定时器还能够根据需要设置成单次定时器和周期定时器。当单次定时器定时超时后,不会自动启动下一个周期的定时,而周期定时器在定时超时后,会自动地启动下一个周期的定时。
FreeRTOS 提供的软件定时器功能,属于 FreeRTOS 的中可裁剪可配置的功能,如果要使能软件定时器功能,那需要在 FreeRTOSConfig.h 文件中将configUSE_TIMERS 配置项配置成 1。
要注意的是,软件定时器的超时回调函数是由软件定时器服务任务调用的,软件定定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的 API 函数,例如 vTaskDelay()、vTaskDelayUntil()和一些会到时任务阻塞的等到事件函数,这些函数将会导致软件定时器服务任务阻塞,这是不可以出现的。
软件定时器服务任务
使能了软件定时器功能后,在调用函数 vTaskStartScheduler()开启任务调度器的时候,会创建一个用于管理软件定时器的任务,这个任务就叫做软件定时器服务任务。软件定时器服务任务,主要负责软件定时器超时的逻辑判断、调用超时软件定时器的超时回调函数以及处理软件定时器命令队列。
软件定时器命令队列
FreeRTOS 提供了许多软件定时器相关的 API 函数,这些 API 函数,大部分都是往定时器的队列中写入消息(发送命令),这个队列叫做软件定时器命令队列,是提供给 FreeRTOS 中的软件定时器使用的,用户是不能直接访问的。软件定时器命令队列的操作过程如下图所示:
左侧的代码为应用程序中用户任务的代码,而右侧的代码为软件定时器服务任务的代码。当用户任务需要操作软件定时器时,就需要调用软件定时器相关的 API 函数,例如图中调用了函数 vTaskStart()启动软件定时器的定时,而函数 vTaskStart()实际上会往软件定时器命令队列写入一条消息(发送命令),这条消息就包含了待操作的定时器对象以及操作的命令(启动软件定时器),软件定时器服务任务就会去读取软件定时器命令队列中的消息(接收命令),并处理这些消息(处理命令)。可以看出,用户任务并不会直接操作软件定时器对象,而是发送命令给软件定时器服务任务,软件定时器服务任务接收到命令后,根据命令内容去操作软件定时器。
软件定时器的状态
软件定时器可以处于一下两种状态中一种:
1. 休眠态
休眠态软件定时器可以通过其句柄被引用,但是因为没有运行,所以其定时超时回调函数
不会被执行。
2. 运行态
处于运行态或在上次定时超时后再次定时超时的软件定时器,会执行其定时超时回调函数。
单次定时器和周期定时器
FreeRTOS 提供了两种软件定时器,如下:
1. 单次定时器
单次定时器的一旦定时超时,只会执行一次其软件定时器超时回调函数,超时后可以被手动重新开启,但单次定时器不会自动重新开启定时。
2. 周期定时器
周期定时器的一旦被开启,会在每次超时时,自动地重新启动定时器,从而周期地执行其软件定时器回调函数。
单次定时器和周期定时器之间的差异如下图所示:
图展示了单次定时器和周期定时器之间的差异,图中的垂直虚线的间隔时间为一个单位时间,可以理解为一个系统时钟节拍。其中 Timer1 为周期定时器,定时超时时间为 2 个单位时间,Timer2 为单次定时器,定时超时时间为 1 个单位时间。可以看到,Timer1 在开启后,一直以 2 个时间单位的时间间隔重复执行,为 Timer2 则在第一个超时后就不在执行了。
软件定时器的状态转换图
周期定时器的状态转换图,如下图所示:
复位软件定时器
除了开启和停止软件定时器的定时,还可以对软件定时器进行复位。复位软件定时器会使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时,软件定时器的复位示意图如下图所示:
软件定时器相关配置
FreeRTOSConfig.h 文件中软件定时器相关的配置项说明如下:
1. configUSE_TIMERS
此宏用于使能软件定时器功能,如果要使用软件定时器功能,则需要将该宏定义定义为 1。开启软件定时器功能后,系统会系统创建软件定时器服务任务。
2. configTIMER_TASK_PRIORITY
此宏用于配置软件定时器服务任务的任务优先级,当使能了软件定时器功能后,需要配置该宏定义,此宏定义可以配置为 0~(configMAX_PRIORITY-1)的任意值。
3. configTIMER_QUEUE_LENGTH
此宏用于配置软件定时器命令队列的队列长度,当使能了软件定时器功能后,需要配置该宏定义,若要正常使用软件定时器功能,此宏定义需定义成一个大于 0 的值。
4. configTIMER_TASK_STACK_DEPTH
此宏用于配置软件定时器服务任务的栈大小,当使能了软件定时器功能后,需要配置该宏定义,由于所有软件定时器的定时器超时回调函数都是由软件定时器服务任务调用的,因此这些软件定时器超时回调函数运行时使用的都是软件定时器服务任务的栈。
软件定时器相关 API 函数
1. 创建软件定时器
FreeRTOS 提供了两种创建软件定时器的方式,分别为动态方式创建软件定时器和静态方式创建软件定时器,两者的区别在于静态方式创建软件定时器时,需要用户提供创建软件定时器所需的内存空间,而使用动态方式创建软件定时器时,FreeRTOS 会自动从 FreeRTOS 管理的堆中分配创建软件定时器所需的内存空间。
动态方式创建软件定时器
TimerHandle_t xTimerCreate(
const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction);
pcTimerName:软件定时器名
xTimerPeriodInTicks:定时超时时间,单位:系统时钟节拍
uxAutoReload:定时器模式,pdTRUE:周期定时器,pdFALSE:单次定时器
pvTimerID:软件定时器 ID,用于多个软件定时器公用一个超时回调函数
pxCallbackFunction:软件定时器超时回调函数
返回值
NULL:软件定时器创建失败
其他值:软件定时器创建成功,返回其句柄
静态方式创建软件定时器
TimerHandle_t xTimerCreateStatic(
const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t * pxTimerBuffer);
pcTimerName:软件定时器名
xTimerPeriodInTicks:定时超时时间,单位:系统时钟节拍
uxAutoReload:定时器模式,pdTRUE:周期定时器,pdFALSE:单次定时器
pvTimerID:软件定时器 ID,用于多个软件定时器公用一个超时回调函数
pxCallbackFunction:软件定时器超时回调函数
pxTimerBuffer:创建软件定时器所需的内存空间
返回值
NULL:软件定时器创建失败
其他值:软件定时器创建成功,返回其句柄
2. 开启软件定时器定时
BaseType_t xTimerStart( TimerHandle_t xTimer,
const TickType_t xTicksToWait);
xTimer:待开启的软件定时器的句柄
xTickToWait:发送命令到软件定时器命令队列的最大等待时间
返回值
pdPASS:软件定时器开启成功
pdFAIL:软件定时器开启失败
3. 停止软件定时器定时
FreeRTOS 提供了两个用于停止软件定时器定时的 API 函数,这个两个函数分别用于在任
务和在中断中停止软件定时器定时。
在任务中停止软件定时器
BaseType_t xTimerStop( TimerHandle_t xTimer,
const TickType_t xTicksToWait);
xTimer:待停止的软件定时器的句柄
xTickToWait:发送命令到软件定时器命令队列的最大等待时间
返回值
pdPASS:软件定时器停止成功
pdFAIL:软件定时器停止失败
在中断中停止软件定时器
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
BaseType_t * const pxHigherPriorityTaskWoken);
xTimer:待停止的软件定时器的句柄
pxHigherPriorityTaskWoken:用于标记函数退出后是否需要进行任务切换
返回值
pdPASS:软件定时器停止成功
pdFAIL:软件定时器停止失败
4.复位软件定时器定时
FreeRTOS 提供了两个用于复位软件定时器定时的 API 函数,这个两个函数分别用于在任
务和在中断中复位软件定时器定时。
在任务中复位软件定时器
BaseType_t xTimerReset( TimerHandle_t xTimer,
const TickType_t xTicksToWait);
xTimer:待复位的软件定时器的句柄
xTickToWait:发送命令到软件定时器命令队列的最大等待时间
返回值
pdPASS:软件定时器复位成功
pdFAIL:软件定时器复位失败
在中断中复位软件定时器
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,
BaseType_t * const pxHigherPriorityTaskWoken);
xTimer:待复位的软件定时器的句柄
pxHigherPriorityTaskWoken:用于标记函数退出后是否需要进行任务切换
返回值
pdPASS:软件定时器复位成功
pdFAIL:软件定时器复位失败
5. 更改软件定时器的定时超时时间
FreeRTOS 提供了两个分别用于任务和中断的更改软件定时器的定时超时时间的 API 函数。
在任务中更改软件定时器的定时超时时间
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
const TickType_t xNewPeriod,
const TickType_t xTicksToWait);
xTimer:待更改定时超时时间的软件定时器的句柄
xNewPeriod:新的定时超时时间,单位:系统时钟节拍
xTickToWait:发送命令到软件定时器命令队列的最大等待时间
返回值
pdPASS:软件定时器定时超时时间更改成功
pdFAIL:软件定时器定时超时时间更改失败
在中断中更改软件定时器的定时超时时间
BaseType_t xTimerChangePeriodFromISR(
TimerHandle_t xTimer,
const TickType_t xNewPeriod,
BaseType_t * const pxHigherPriorityTaskWoken);
xTimer:待更改定时超时时间的软件定时器的句柄
xNewPeriod:新的定时超时时间,单位:系统时钟节拍
pxHigherPriorityTaskWoken:用于标记函数退出后是否需要进行任务切换
返回值
pdPASS:软件定时器定时超时时间更改成功
pdFAIL:软件定时器定时超时时间更改失败
6. 删除软件定时器
aseType_t xTimerDelete( TimerHandle_t xTimer,
const TickType_t xTicksToWait);
xTimer:待删除的软件定时器的句柄
xTickToWait:发送命令到软件定时器命令队列的最大等待时间
返回值
pdPASS:软件定时器删除成功
pdFAIL:软件定时器删除失败
软件定时器实验
本实验设计两个任务:start_task 和 timercontrol_task 这两个任务的任务功能如下:
start_task:用来创建任务 timercontrol_task 和两个软件定时器。
timercontrol_task:控制两个软件定时器的开启和停止。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "stm32f10x_conf.h"
#include "key.h"
#include "lcd.h"
#include "chinese.h"
#include "chfont.h"
#include "malloc.h"
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TIMERCONTROL_TASK_PRIO 2
//任务堆栈大小
#define TIMERCONTROL_STK_SIZE 256
//任务句柄
TaskHandle_t TimerControlTask_Handler;
//任务函数
void timercontrol_task(void *pvParameters);
TimerHandle_t AutoReloadTimer_Handle; //周期定时器句柄
TimerHandle_t OneShotTimer_Handle; //单次定时器句柄
void AutoReloadCallback(TimerHandle_t xTimer); //周期定时器回调函数
void OneShotCallback(TimerHandle_t xTimer); //单次定时器回调函数
//LCD刷屏时使用的颜色
int lcd_discolor={ WHITE, BLACK, BLUE,BRED,
GRED,GBLUE, RED, MAGENTA,
GREEN, CYAN,YELLOW,BROWN,
BRRED, GRAY };
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
KEY_Init(); //初始化按键
LCD_Init(); //初始化LCD
mem_init(); //初始化内部内存池
POINT_COLOR = RED;
LCD_ShowString(30,10,"STM32F103RC");
LCD_ShowString(30,30,"FreeRTOS Examp");
LCD_ShowString(30,50,"KEY_UP:Start Tmr1");
LCD_ShowString(30,70,"KEY0:Start Tmr2");
LCD_ShowString(30,90,"KEY1:Stop Tmr1 and Tmr2");
LCD_DrawLine(0,108,239,108); //画线
LCD_DrawLine(119,108,119,319); //画线
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111, "AutoTim:000");
LCD_ShowString(126,111,"OneTim: 000");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t*)&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建软件周期定时器
AutoReloadTimer_Handle=xTimerCreate((const char* )"AutoReloadTimer",
(TickType_t )1000,
(UBaseType_t )pdTRUE,
(void* )1,
(TimerCallbackFunction_t)AutoReloadCallback); //周期定时器,周期1s(1000个时钟节拍),周期模式
//创建单次定时器
OneShotTimer_Handle=xTimerCreate((const char* )"OneShotTimer",
(TickType_t )2000,
(UBaseType_t )pdFALSE,
(void* )2,
(TimerCallbackFunction_t)OneShotCallback); //单次定时器,周期2s(2000个时钟节拍),单次模式
//创建定时器控制任务
xTaskCreate((TaskFunction_t )timercontrol_task,
(const char* )"timercontrol_task",
(uint16_t )TIMERCONTROL_STK_SIZE,
(void* )NULL,
(UBaseType_t )TIMERCONTROL_TASK_PRIO,
(TaskHandle_t*)&TimerControlTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//TimerControl的任务函数
void timercontrol_task(void *pvParameters)
{
u8 key,num=0;
while(1)
{
//只有两个定时器都创建成功了才能对其进行操作
if((AutoReloadTimer_Handle!=NULL)&&(OneShotTimer_Handle!=NULL))
{
key = KEY_Scan(0);
switch(key)
{
case WKUP_PRES: //当key_up按下的话打开周期定时器
xTimerStart(AutoReloadTimer_Handle,0); //开启周期定时器
printf("open Timer 1\r\n");
break;
case KEY0_PRES: //当key0按下的话打开单次定时器
xTimerStart(OneShotTimer_Handle,0); //开启单次定时器
printf("open Timer 2\r\n");
break;
case KEY1_PRES: //当key1按下话就关闭定时器
xTimerStop(AutoReloadTimer_Handle,0); //关闭周期定时器
xTimerStop(OneShotTimer_Handle,0); //关闭单次定时器
printf("close Timer 1 and 2\r\n");
break;
}
}
num++;
if(num==50) //每500msLED0闪烁一次
{
num=0;
LED0=!LED0;
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
//周期定时器的回调函数
void AutoReloadCallback(TimerHandle_t xTimer)
{
static u8 tmr1_num=0;
tmr1_num++; //周期定时器执行次数加1
LCD_Fill(6,131,114,313,lcd_discolor);//填充区域
LCD_Show2Num(70,111,tmr1_num,3,16,0x80); //显示周期定时器的执行次数
}
//单次定时器的回调函数
void OneShotCallback(TimerHandle_t xTimer)
{
static u8 tmr2_num = 0;
tmr2_num++; //周期定时器执行次数加1
LCD_Fill(126,131,233,313,lcd_discolor); //填充区域
LCD_Show2Num(190,111,tmr2_num,3,16,0x80);//显示单次定时器执行次数
LED1=!LED1;
printf("Timer 2 runs finish\r\n");
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m0_55389449/article/details/147578566
FreeRTOS中的软件定时器还是非常好用的。
页:
[1]