软件定时器是FreeRTOS的一个可选功能,基于系统时钟节拍实现。它的实现不需要使用任何硬件定时器资源,并且不受数量的限制,只需系统资源许可。
软件定时器允许在设定时间到来之后执行指定功能,这个执行指定功能的函数被称为软件定时器回调函数。从软件定时器启动到执行软件定时器回调函数之间的时间被称为软件定时器周期。简单来说,软件定时器的定时时间到了,就会执行软件定时器回调函数,要实现的功能就放在软件定时器回调函数中。
1 软件定时器服务任务
软件定时器是FreeRTOS的一个可选功能,当configUSE_TIMERS为1时,在调度器启动的时候会自动创建一个软件定时器服务任务(软件定时器守护任务),软件定时器服务任务的堆栈大小、优先级、命令队列长度等也要在配置文件中进行配置。
操作软件定时器的 API函数通过队列发送命令给软件定时器服务任务,这个队列叫作软件定时器命令队列。软件定时器命令队列是FrecRTOS软件定时器私有的,用户不能直接访问,但它又是用户任务与软件定时器服务任务之间沟通的纽带。用户任务通过软件定时器命令队列发送命令给软件定时器服务任务示意图如图所示。
在图中,软件定时器命令队列将用户任务与软件定时器服务任务连接在一起。用户任务中的应用程序通过调用xTimerReset()函数将复位命令通过软件定时器命令队列发送给软件定时器服务任务处理,而不能调用类似xQueueSend()的队列操作函数来发送。
2 软件定时器操作
2.1 单次定时与周期定时
FreeRTOS软件定时器支持单次定时模式与周期定时模式。单次定时模式是指用户创建并开启软件定时器后,达到定时时间只执行一次软件定时器回调函数,然后就会停止。周期定时模式是指此软件定时器会按设置好的定时时间周期性地执行软件定时器回调函数,直到调用软件定时器停止函数为止。
注意:软件定时器回调函数是在软件定时器服务任务中执行的,软件定时器回调函数
千万不能使用会导致任务阻塞的API函数,如vTaskDelay()函数等是千万不能使用的。
2.2 创建软件定时器
在使用软件定时器之前要先创建软件定时器,有两个函数用于创建软件定时器:动态
创建函数xTimerCreate()和静态创建函数xTimerCreateStatic()。
2.2.1 xTimerCreate()
xTimerCreate()用于动态创建一个软件定时器,所需要的内存通过动态内存分配方法
获取,刚创建的软件定时器处于休眠未运行状态。该函数的原型如下。
TimerHandle_t xTimerCreate( const char * const pcTimerName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction )
参数说明:
pcTimerName: 软件定时器的名字,方便调试。
xTimerPeriodInTicks: 定时周期,单位为系统时钟节拍。
uxAutoReload: 软件定时器模式,传入pdTRUE参数创建周期软件定时器,传
入pdFALSE参数创建单次软件定时器。pvTimerID: 软件定时器ID,用于标识当多个软件定时器使用同一个回调函
数时是哪个软件定时器引起的回调。pxCallbackFunction: 软件定时器回调函数。
返回值: 创建成功返回所创建的软件定时器句柄,创建失败返回NULL。
2.2.2 xTimerCreateStatic()
xTimerCreateStatic()用于静态创建一个软件定时器,所需要的内存需要由用户自行分配,刚创建好的软件定时器处于休眠未运行状态。静态创建函数与动态创建函数相比,多了一个pxTimerBuffer形参,用于指向用户分配的软件定时器内存。该函数的原型如下。
TimerHandle_t xTimerCreateStatic( const char * const pcTimerName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
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 :指向一个StaticTimer_t类型的结构体
返回值: 创建成功返回所创建的软件定时器句柄,创建失败返回NULL。
2.3 启动软件定时器
刚创建好的软件定时器处于体眠未运行状态,有两个API可用于启动软件定时器:一个是用于任务的xTimerStart();另一个是用于中断服务函数的xTimerStartFromISR()。
2.3.1 xTimerStart()
xTimerStart()为软件定时器启动函数。如果软件定时器没有运行,那么调用此API就可计算软件定时器的到时时间并启动软件定时器;如果软件定时器已经运行,那么调用此API相当于复位软件定时器。此API是一个宏,实际实现功能的是xTimerGenericCommand()函数。该宏的定义如下。
#define xTimerStart( xTimer, xTicksToWait )
xTimerGenericCommand( ( xTimer ),
tmrCOMMAND_START,
( xTaskGetTickCount() ),
NULL,
( xTicksToWait ) )
参数说明:
xTimer : 要启动的软件定时器句柄。
xTicksToWait : 命令入队阻塞时间,软件定时器API通过给软件定时器队列发送命令。
返回值:软件定时器启动成功返回pdPASS,启动失败返回pdFAIL。
2.3.2 xTimerStartFromISR()
xTimerStartFromISR()为启动软件定时器的API的中断版本,用于中断服务函数。此
API是一个宏,实际实现功能的是xTimerGenericCommand()函数。该宏的定义如下。
#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken )
xTimerGenericCommand( ( xTimer ),
tmrCOMMAND_START_FROM_ISR,
( xTaskGetTickCountFromISR() ),
( pxHigherPriorityTaskWoken ),
0U )
参数说明:
xTimer : 要启动的软件定时器句柄。
pxHigherPriorityTaskWoken : 指向一个用于保存调用函数后是否进行任务切换的变量,若执行函数后值为pdTRUE,则要在退出中断服务函数后执行一次任务切换。
返回值:软件定时器启动成功返回pdPASS,启动失败返回pdFAIL。
2.4 停止软件定时器
周期软件定时器一旦启动就会不断重复运行,直到调用软件定时器停止API为止。有两个API用于停止软件定时器:一个是用于任务的xTimerStop();另一个是用于中断服务函数的xTimcrStopFromISR()
2.4.1 xTimerStop()
xTimerStop()用于停止软件定时器,此API是一个宏,实际实现功能的是
xTimcrGcnericCommand()函数。该宏的定义如下。
#define xTimerStop( xTimer, xTicksToWait )
xTimerGenericCommand( ( xTimer ),
tmrCOMMAND_STOP,
0U,
NULL,
( xTicksToWait ) )
参数说明:
xTimer :要启动的软件定时器句柄。
xTicksToWait :命令入队阻塞时间,软件定时器API通过给软件定时器队列发送命令。
返回值:软件定时器停止成功返回pdPASS,停止失败返回pdFAIL。
2.4.2 xTimcrStopFromISR()
xTimerStopFromISR()为停止软件定时器的AIP的中断版本,用于中断服务函数。此API 是一个宏,实际实现功能的是xTimerGenericCommand()函数。该宏的定义如下。
#define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken )
xTimerGenericCommand( ( xTimer ),
tmrCOMMAND_STOP_FROM_ISR,
0,
( pxHigherPriorityTaskWoken ),
0U )
参数说明:
xTimer :要启动的软件定时器句柄。
pxHigherPriorityTaskWoken :指向一个用于保存调用函数后是否进行任务切换的变量,若执行函数后值为pdTRUE,则要在退出中断服务函数后执行一次任务切换。
返回值:软件定时器停止成功返回pdPASS,停止失败返回pdFAIL。
3 软件定时器使用示例
本示例创建两个软件定时器:一个是单次软件定时器,周期为2000系统时钟节拍(2s);另一个是周期软件定时器,周期为1000系统时钟节拍(1s)。通过appStartTask()函数创建两个FrceRTOS任务。
单次软件定时器回调函数为singleTimerCallBack(),其功能是对单次软件定时器使用次数进行计数,并将信息通过串口发送,同时使LED0亮灭状态改变一次。
周期软件定时器回调函数为cycleTimerCallBack(),其功能是对周期软件定时器时间到达次数进行计数,并将信息通过串口发送,同时使LED1亮灭状态改变一次。
任务1是串口守护任务,优先级为3,任务函数为printTask(),其功能是将通过队列
性送过来的字符信息在串口上输出,任何时候只有该守护任务能访问串口。
任务2是按键扫描任务,优先级为4,任务函数为keyTask(),其功能是按键扫描,并根据返回的键值来启动或停止软件定时器。按键KEY1 用于启动单次软件定时器,按键KEY2用于启动周期软件定时器,按键KEY3用于停止单次软件定时器及周期软件定时器、在启动软件定时器前,要先检查软件定时器是否已经启动运行。
3.1 配置FreeRTOS
3.1.1 包含头文件
#ifndef __APPTASK_H
#define __APPTASK_H
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#include "queue.h"
#include "timer.h"
#include "key.h"
static void printTask(void *pvParameters);
static void keyTask(void *pvParameters);
void appStartTask(void);
#endif
3.1.2 配置宏
使用软件定时器,需要将宏configUSE_TIMERS 配置为1,与软件定时器相关的宏配置如下。
/*是否使用软件定时器功能:为1使用*/
#define configUSE_TIMERS 1
/*软件定时器服务任务的默认优先级*/
#define configTIMER_TASK_PRIORITY ( 2 )
/*软件定时器命令队列长度*/
#define configTIMER_QUEUE_LENGTH 10
/*软件定时器服务任务堆栈大小*/
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2)
3.2 软件定时器回调函数
/**********************************************************************
函 数 名:singleTimerCallBack
功能说明:单次软件定时器回调函数
形 参:xTimer, 用于标记引起回调函数的软件定时器
返 回 值:无
**********************************************************************/
void singleTimerCallBack(TimerHandle_t xTimer)
{
static uint16_t cnt=0;
xTimer = xTimer;
cnt++;
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4))); //LED0闪烁
sprintf(pcToPrint,"单次软件定时器,运行%3d次 \r\n",cnt);
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
/**********************************************************************
函 数 名:cycleTimerCallBac
功能说明:单次软件定时器回调函数
形 参:xTimer, 用于标记引起回调函数的软件定时器
返 回 值:无
**********************************************************************/
void cycleTimerCallBack(TimerHandle_t xTimer)
{
static uint16_t cnt=0;
xTimer = xTimer;
cnt++;
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4))); //LED0闪烁
sprintf(pcToPrint,"周期软件定时器,运行%3d次 \r\n",cnt);
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
3.3 任务函数
/**********************************************************************
函 数 名:printTask
功能说明:串口守护任务使用了一个FreeRTOS队列来对串口实现串行化访问,该守护任务是唯一能够直接访问串口的任务。
串口守护任务大部分时间都在阻塞态等待队列中有消息到来,当一个消息到达时,
串口守护任务仅简单地将接收到的消息发送到串口上,然后又返回阻塞态,继续等待下一条消息的到来。
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 3
**********************************************************************/
void printTask(void *pvParameters)
{
char pcTowrite[80]; //缓存从队列接受到的数据
while(1)
{
/*当队列为空,即没有字符需要输出时间,阻塞超时时间为portMAX_DELAY,任务将进入无期限等待
状态,可以不检测队列读取函数的返回值*/
xQueueReceive(xQueuePrint,pcTowrite,portMAX_DELAY);
printf("%s",pcTowrite);
}
}
/**********************************************************************
函 数 名:keyTask
功能说明:按键扫描任务,根据键值执行相应的操作
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 4
**********************************************************************/
static void keyTask(void *pvParameters)
{
uint8_t keyValue = 0; //存储键值
while(1)
{
keyValue =Key_getNum();
if(keyValue == 1)
{
if(xTimerIsTimerActive(tmrSingleHandler) == pdFAIL)
{
sprintf(pcToPrint,"启动单次软件定时器 .........\r\n\r\n");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
xTimerStart(tmrSingleHandler,0);//启动单次软件定时器
}
else
{
sprintf(pcToPrint,"单次软件定时器已启动 .........\r\n\r\n");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
}
if(keyValue == 2)
{
if(xTimerIsTimerActive(tmrCycleHandler) == pdFAIL)
{
sprintf(pcToPrint,"启动周期软件定时器 .........\r\n\r\n");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
xTimerStart(tmrCycleHandler,0);
}else
{
sprintf(pcToPrint,"周期软件定时器已启动 .........\r\n\r\n");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
}
}
else if(keyValue == 3)
{
sprintf(pcToPrint,"停止所以的定时器 .........\r\n\r\n");
xQueueSendToBack(xQueuePrint,pcToPrint,0);
xTimerStop(tmrSingleHandler,0);
xTimerStop(tmrCycleHandler,0);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
3.4 创建软件定时器和任务
static char pcToPrint[80]; //待打印内容缓冲区
xQueueHandle xQueuePrint; //消息队列句柄
TimerHandle_t tmrSingleHandler; //单次定时器句柄
TimerHandle_t tmrCycleHandler; //单次定时器句柄
static TaskHandle_t printTaskHandle = NULL;//任务printTask任务句柄
static TaskHandle_t keyTaskHandle = NULL;//按键扫描任务句柄
/**********************************************************************
函 数 名:appStartTask
功能说明:任务开始函数,用于创建其他函数并且开启调度器
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
**********************************************************************/
void appStartTask(void)
{
/*创建一个长度为2,队列项大小足够容纳待输出字符的队列*/
xQueuePrint = xQueueCreate(2,sizeof(pcToPrint));
/*创造2个软件定时器,一个单次软件定时器,一个周期软件定时器*/
tmrSingleHandler = xTimerCreate("singleTimer",2000,pdFALSE,(void *)1,singleTimerCallBack);
tmrCycleHandler = xTimerCreate("cycleTimer",1000,pdTRUE,(void *)2,cycleTimerCallBack);
if(xQueuePrint && tmrSingleHandler && tmrCycleHandler)//如果队列信号量创建成功
{
taskENTER_CRITICAL(); /*进入临界段,关中断*/
xTaskCreate(printTask,"printTask",128,NULL,3,&printTaskHandle);
xTaskCreate(keyTask,"keyTask",128,NULL,4,&keyTaskHandle);
taskEXIT_CRITICAL(); /*退出临界段,关中断*/
vTaskStartScheduler();/*开启调度器*/
}
}
4 下载测试
5 总结
FreeRTOS 软件定时器有单次定时模式和周期定时模式两种工作模式。软件定时器在使用前要先创建,刚创建好的软件定时器处于休眠未运行状态。可通过启动、复位、停止等API操作软件定时器,这些API通过软件定时器命令队列传递命令给软件定时器服务任务。软件定时器要的功能通过软件定时器回调函数实现,在软件定时器回调函数中不能调用会导致任务阻塞的API函数。