软件定时器
MCU一般都自带定时器,属于硬件定时器,但是不同的MCU其硬件定时器数量不同,有时需要考虑成本的问题。在硬件定时器不够用的时候,FreeRTOS也提供了定时器功能,不过是属于软件定时器,其定时精度没有硬件定时器高,但是对于精度要求不高的周期性任务也足够了
1. 软件定时器介绍
软件定时器允许设置一段时间,当设置的时间到达之后就会执行回调函数。软件定时器的回调函数是在定时器服务任务中执行的,因此不能在回调函数中调用会阻塞任务的API函数
定时器是一个可选的、不属于FreeRTOS内核的功能,是由定时器服务任务来提供的。定时器相关API函数大多是使用定时器命令队列发送命令给定时器服务任务的,用户不能直接访问该命令队列
如上图示,定时器命令队列将用户应用任务和定时器服务任务连接在一起。用户应用程序调用了函数xTimerReset(),其结果是复位命令会被发送到定时器命令队列中,再由定时器服务任务来处理这个命令
软件定时器分为两种:单次定时器和周期定时器。单次定时器在定时时间到了后执行一次回调函数就会停止运行;周期定时器一旦启动就会周期性的执行回调函数
定时器的相关配置
- configUSE_TIMERS 宏置1:自动创建定时器服务任务
- configTIMER_TASK_PRIORITY:软件定时器服务任务的任务优先级
- configTIMER_QUEUE_LENGTH:设置定时器命令队列的队列长度
- configTIMER_TASK_STACK_DEPTH:设置定时器服务任务的任务堆栈大小
FreeRTOS启动调度器的时候会自动创建定时器服务任务,其源码如下所示:
BaseType_t xTimerCreateTimerTask( void ){
BaseType_t xReturn = pdFAIL;
prvCheckForValidListAndQueue();
if( xTimerQueue != NULL ){
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
}
#else
{
xReturn = xTaskCreate(prvTimerTask,
"Tmr Svc",
configTIMER_TASK_STACK_DEPTH,
NULL,
((UBaseType_t)configTIMER_TASK_PRIORITY)|portPRIVILEGE_BIT,
&xTimerTaskHandle);
}
#endif
}
else{
mtCOVERAGE_TEST_MARKER();
}
configASSERT( xReturn );
return xReturn;
}
static void prvCheckForValidListAndQueue( void ){
taskENTER_CRITICAL();
{
if( xTimerQueue == NULL ){
vListInitialise( &xActiveTimerList1 );
vListInitialise( &xActiveTimerList2 );
pxCurrentTimerList = &xActiveTimerList1;
pxOverflowTimerList = &xActiveTimerList2;
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
}
#else
{
xTimerQueue = xQueueCreate((UBaseType_t)configTIMER_QUEUE_LENGTH, sizeof(DaemonTaskMessage_t));
}
#endif
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
}
|
2. 软件定时器API函数
2.1 复位软件定时器
复位软件定时器,若软件定时器已经启动,则重新计算超时时间;若软件定时器没有启动,则启动软件定时器
BaseType_t xTimerReset(TimerHandle_t xTimer,
TickType_t xTicksToWait)
BaseType_t xTimerResetFromISR(TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken)
返回值:定时器复位成功返回pdPASS;失败返回pdFAIL
|
复位定时器函数是一个宏,最终调用xTimerGenericCommand()函数
#define xTimerReset(xTimer, xTicksToWait) \
xTimerGenericCommand((xTimer), \
tmrCOMMAND_RESET, \
(xTaskGetTickCount()), \
NULL, \
xTicksToWait)) \
|
2.2 创建软件定时器
创建一个软件定时器,并返回一个软件定时器句柄。创建软件定时器后,软件定时器并没有启动
TimerHandle_t xTimerCreate(char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction)
TimerHandle_t xTimerCreateStatic(char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
aseType_t uxAutoReload,
vid * const pvTimerID,
imerCallbackFunction_t pxCallbackFunction,
taticTimer_t * pxTimerBuffer)
返回值:创建成功返回软件定时器句柄;失败返回NULL
|
创建软件定时器函数xTimerCreate()的源码分析如下示:
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction){
Timer_t *pxNewTimer;
pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) );
if( pxNewTimer != NULL ){
prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );
}
return pxNewTimer;
}
static void prvInitialiseNewTimer(const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
Timer_t *pxNewTimer){
configASSERT( ( xTimerPeriodInTicks > 0 ) );
if( pxNewTimer != NULL ){
prvCheckForValidListAndQueue();
pxNewTimer->pcTimerName = pcTimerName;
pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
pxNewTimer->uxAutoReload = uxAutoReload;
pxNewTimer->pvTimerID = pvTimerID;
pxNewTimer->pxCallbackFunction = pxCallbackFunction;
vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );
traceTIMER_CREATE( pxNewTimer );
}
}
|
2.3 开启软件定时器
BaseType_t xTimerStart(TimerHandle_t xTimer,
TickType_t xTicksToWait)
BaseType_t xTimerStartFromISR(TimerHandle_t xTimer,
aseType_t * pxHigherPriorityTaskWoken)
返回值:定时器开启成功返回pdPASS;失败返回pdFAIL
|
开始软件定时器函数是一个宏,最终调用xTimerGenericCommand()函数
#define xTimerStart(xTimer, xTicksToWait)
TimerGenericCommand((xTimer),
COMMAND_START,
xTaskGetTickCount()),
ULNL,
(xTicksToWait))
|
2.4 停止软件定时器
BaseType_t xTimerStop(TimerHandle_t xTimer,
TickType_t xTicksToWait)
BaseType_t xTimerStopFromISR(TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken)
返回值:定时器停止成功返回pdPASS;失败返回pdFAIL
|
停止软件定时器函数是一个宏,最终也是调用xTimerGenericCommand()函数
#define xTimerStop(xTimer, xTicksToWait)
xTimerGenericCommand((xTimer),
XmrCOMMAND_STOP,
OU,
nULL,
(xTicksToWait))
|
函数xTimerGenericCommand()的源码如下:
BaseType_t xTimerGenericCommand(TimerHandle_t xTimer,
Const BaseType_t xCommandID,
Const TickType_t xOptionalValue,
baseType_t * const pxHigherPriorityTaskWoken,
const TickType_t xTicksToWait){
BaseType_t xReturn = pdFAIL;
DaemonTaskMessage_t xMessage;
if( xTimerQueue != NULL ){
xMessage.xMessageID = xCommandID;
xMessage.u.xTimerParameters.xMessageValue = xOptionalValue;
xMessage.u.xTimerParameters.pxTimer = ( Timer_t * ) xTimer;
if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ){
if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ){
xReturn = xQueueSendToBack(xTimerQueue, &xMessage, xTicksToWait);
}
else{
xReturn = xQueueSendToBack(xTimerQueue, &xMessage, tmrNO_DELAY);
}
}
else{
xReturn = xQueueSendToBackFromISR(xTimerQueue,&xMessage,pxHigherPriorityTaskWoken);
}
traceTIMER_COMMAND_SEND(xTimer, xCommandID, xOptionalValue, xReturn);
}
else{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
|
3. 软件定时器应用实例
本实例介绍FreeRTOS软件定时的的创建、开启和停止函数的操作与使用
使用STM32CubeMX将FreeRTOS移植到工程中,创建一个任务、两个软件定时器(单次和周期定时器)
- TimerControl_Task:控制两个软件定时器的开启和停止
- PeriodicTimer:周期定时器,定时周期为1000ms
- OnceTimer:单次定时器,定时周期为2000ms
3.1 STM32CubeMX设置
RCC设置外接HSE,时钟设置为72M
PC0/PC1设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
PA0设置为GPIO输入模式、下拉模式;PE2/PE3/PE4设置为GPIO输入模式、上拉模式
USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位;
激活FreeRTOS,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数
使能软件定时器,设置优先级等参数
添加单次定时器和周期定时器
使用FreeRTOS操作系统,需要将HAL库的Timebase Source从SysTick改为其他定时器,选好定时器后,系统会自动配置TIM
输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM软件编程
创建按键驱动文件key.c和key.h,参考按键输入例程
添加周期定时器和单次定时器的回调函数
void TimerControl_Task(void const * argument){
uint8_t key;
for(;;){
if((PeriodicTimerHandle != NULL)&&(OnceTimerHandle != NULL)){
key = KEY_Scan(0);
switch(key){
case KEY_UP_PRES :
xTimerChangePeriod(PeriodicTimerHandle,1000,0);
xTimerStart(PeriodicTimerHandle,0);
printf("PeriodicTimer Start......!\r\n");
break;
case KEY_DOWN_PRES :
xTimerStop(PeriodicTimerHandle,0);
printf("PeriodicTimer Stop******!\r\n");
break;
case KEY_LEFT_PRES :
xTimerChangePeriod(OnceTimerHandle,2000,0);
xTimerStart(OnceTimerHandle,0);
printf("OnceTimer Start......!\r\n");
break;
case KEY_RIGHT_PRES :
xTimerStop(OnceTimerHandle,0);
printf("OnceTimer Stop******!\r\n");
break;
}
}
osDelay(10);
}
}
|
3.3 下载验证
编译无误下载到开发板后,打开串口调试助手
按下KEY_UP按键,开启周期定时器,此时LED2指示灯每隔1s闪烁一次
按下KEY_DOWN按键,关闭周期定时器,LED2停止闪烁
按下KEY_LEFT按键,开启单次定时器,2s后LED1状态翻转,停止运行
按下KEY_RIGHT按键,关闭单次定时器
|