新浪博客

【FreeRTOS操作系统教程】第19章 FreeRTOS定时器组

2016-08-30 14:39阅读:

第19章 FreeRTOS定时器组

本文完整版地址:http://bbs.armfly.com/read.php?tid=21263
本章节为大家讲解FreeRTOS支持的定时器组,或者叫软件定时器,又或者叫用户定时器均可。软件定时器的功能比较简单,也容易掌握。被称为定时器组是因为用户可以创建多个定时器,创建的个数是可配置的。
本章教程配套的例子含Cortex-M3内核的STM32F103Cortex-M4内核的STM32F407以及F429
19.1 定时器组介绍
19.2 定时器任务(Daemon任务)
19.3 使用软件定时器组注意事项
19.4 定时器组API函数
19.5 实验例程说明
19.6 总结

19.1 定时器组介绍

FreeRTOS
软件定时器组的时基是基于系统时钟节拍实现的,之所以叫软件定时器是因为它的实现不需要使用任何硬件定时器,而且可以创建很多个,综合这些因素,这个功能就被称之为软件定时器组。
既然是定时器,那么它实现的功能与硬件定时器也是类似的。在硬件定时器中,我们是在定时器中断中实现需要的功能,而使用软件定时器时,我们是在创建软件定时器时指定软件定时器的回调函数,在回调函数中实现相应的功能。

19.1.1 单次模式和周期模式

FreeRTOS提供的软件定时器支持单次模式和周期性模式,单次模式就是用户创建了定时器并启动了定时器后,定时时间到将不再重新执行,这就是单次模式软件定时器的含义。周期模式就是此定时器会按照设置的时间周期重复去执行,这就是周期模式软件定时器的含义。另外就是单次模式或者周期模式的定时时间到后会调用定时器的回调函数,用户可以回调函数中加入需要执行的工程代码。

19.2 定时器任务(Daemon任务)

为了更好的管理FreeRTOS的定时器组件,专门创建了一个定时器任务,或者称之为Daemon任务。关于这个任务,我们上章节在讲解事件标志组的时候有用到。
FreeRTOS定时器组的大部分API函数都是通过消息队列给定时器任务发消息,在定时器任务里面执行实际的操作。为了更好的说明这个问题,我们将官方在线版手册中的这个截图贴出来进行说明:
【FreeRTOS操作系统教程】第19章 <wbr>FreeRTOS定时器组

左侧图是用户应用程序,右侧是定时器任务。在用户应用程序里面调用了定时器组API函数xTimerReset,这个函数会通过消息队列给定时器任务发消息,在定时器任务里面执行实际操作。消息队列在此处的作用有一个专门的名字:Timer command queue,即专门发送定时器组命令的队列。

19.3使用软件定时器组注意事项

定时器回调函数是在定时器任务中执行的,实际应用中切不可在定时器回调函数中调用任何将定时器任务挂起的函数,比如vTaskDelay(), vTaskDelayUntil()以及非零延迟的消息队列和信号量相关的函数。将定时器任务挂起,会导致定时器任务负责的相关功能都不能正确执行了。

19.4定时器组API函数

使用如下20个函数可以实现FreeRTOS的定时器组:
(1) xTimerCreate()
(2) xTimerCreateStatic()
(3) xTimerIsTimerActive()
(4) xTimerStart()
(5) xTimerStop()
(6) xTimerChangePeriod()
(7) xTimerDelete()
(8) xTimerReset()
(9) xTimerStartFromISR()
(10) xTimerStopFromISR()
(11) xTimerChangePeriodFromISR()
(12) xTimerResetFromISR()
(13) pvTimerGetTimerID()
(14) vTimerSetTimerID()
(15) xTimerGetTimerDaemonTaskHandle()
(16) xTimerPendFunctionCall()
(17) xTimerPendFunctionCallFromISR()
(18) pcTimerGetName()
(19) xTimerGetPeriod()
(20) xTimerGetExpiryTime()
关于这20个函数的讲解及其使用方法可以看FreeRTOS在线版手册:
【FreeRTOS操作系统教程】第19章 <wbr>FreeRTOS定时器组

这里我们重点的说以下3个函数:
(1) xTimerCreate()
(2) xTimerStart ()
(3) pvTimerGetTimerID ()
因为本章节配套的例子使用的是这3个函数。

19.4.1 xTimerCreate

函数原型:
TimerHandle_t xTimerCreate
( const char * const pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
函数描述:
函数xTimerCreate用于创建软件定时器。
u 1个参数是定时器名字,用于调试目的,方便识别不同的定时器。
u 2个参数是定时器周期,单位系统时钟节拍。
u 3个参数是选择周期模式还是单次模式,若参数为pdTRUE,则表示选择周期模式,若参数为pdFALSE,则表示选择单次模式。
u 4个参数是定时器ID,当创建不同的定时器,但使用相同的回调函数时,在回调函数中通过不同的ID号来区分不同的定时器。
u 5个参数是定时器回调函数。
u 返回值,创建成功返回定时器的句柄,由于FreeRTOSCongfig.h文件中heap空间不足,或者定时器周期设置为0,会返回NULL
使用这个函数要注意以下问题:
1. FreeRTOSConfig.h文件中使能宏定义:
#define configUSE_TIMERS 1
使用举例:
static TimerHandle_t xTimers = NULL;


static void AppObjCreate (void)
{
const TickType_t xTimerPer = 100;


xTimers = xTimerCreate('Timer',
xTimerPer,
pdTRUE,
(void *) i,
vTimerCallback);

if(xTimers == NULL)
{

}
else
{

if(xTimerStart(xTimers, 100) != pdPASS)
{

}
}
}

19.4.2函数xTimerStart

函数原型:
BaseType_t xTimerStart( TimerHandle_t xTimer,
TickType_t xBlockTime );
函数描述:
函数xTimerStart用于启动软件定时器。
u 1个参数是定时器句柄。
u 2个参数是成功启动定时器前的最大等待时间设置,单位系统时钟节拍,定时器组的大部分API函数不是直接运行的(见19.2小节说明),而是通过消息队列给定时器任务发消息来实现的,此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。
u 返回值,返回pdFAIL表示此函数向消息队列发送消息失败,返回pdPASS表示此函数向消息队列发送消息成功。定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放CPU权才可以得到执行。
使用这个函数要注意以下问题:
1. 使用前一定要保证定时器组已经通过函数xTimerCreate创建了。
2. FreeRTOSConfig.h文件中使能宏定义:
#define configUSE_TIMERS 1
3. 对于已经被激活的定时器,即调用过函数xTimerStart进行启动,再次调用此函数相当于调用了函数xTimerReset对定时器时间进行了复位。
4. 如果在启动FreeRTOS调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了FreeRTOS调度器才会得到执行,即从此刻开始计时,达到xTimerCreate中设置的单次或者周期性延迟时间才会执行相应的回调函数。
使用举例:
static TimerHandle_t xTimers = NULL;


static void AppObjCreate (void)
{
const TickType_t xTimerPer = 100;


xTimers = xTimerCreate('Timer',
xTimerPer,
pdTRUE,
(void *) i,
vTimerCallback);

if(xTimers == NULL)
{

}
else
{

if(xTimerStart(xTimers, 100) != pdPASS)
{

}
}
}

19.4.3函数pvTimerGetTimerID

函数原型:
void *pvTimerGetTimerID( TimerHandle_t xTimer );
函数描述:
函数pvTimerGetTimerID用于返回使用函数xTimerCreate创建的软件定时器ID
u 1个参数是定时器句柄。
u 返回值,返回定时器ID
使用这个函数要注意以下问题:
1. 使用前一定要保证定时器组已经通过函数xTimerCreate创建了。
2. FreeRTOSConfig.h文件中使能宏定义:
#define configUSE_TIMERS 1
3. 创建不同的定时器时,可以对定时器使用相同的回调函数,在回调函数中通过此函数获取是哪个定时器的时间到了,这个功能就是此函数的主要作用。
使用举例:

static void vTimerCallback(xTimerHandle pxTimer)
{
uint32_t ulTimerID;

configASSERT(pxTimer);


ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);


if(ulTimerID == 0)
{
bsp_LedToggle(1);
}


if(ulTimerID == 1)
{
bsp_LedToggle(2);
}
}

19.5实验例程说明

19.5.1STM32F103开发板实验

配套例子:
V4-314_FreeRTOS实验_定时器组
实验目的:
1. 学习FreeRTOS的定时器组。
实验内容:
1. K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。
2. 创建两个软件定时器。
3. 各个任务实现的功能如下:
vTaskUserIF任务 :按键消息处理。
vTaskLED任务 LED闪烁。
vTaskMsgPro任务 :消息处理,这里用作LED闪烁。
vTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
FreeRTOS的配置:
FreeRTOSConfig.h文件中的配置如下:

#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include
extern volatile uint32_t ulHighFrequencyTimerTicks;
#endif

#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1


#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (ulHighFrequencyTimerTicks = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks
//#define portALT_GET_RUN_TIME_COUNTER_VALUE 1


#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )



#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1


#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )


#ifdef __NVIC_PRIO_BITS

#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif


#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x0f


#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01
几个重要选项说明:
u #define configUSE_PREEMPTION 1
使能抢占式调度器
u #define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )
系统主频72MHz
u #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
系统时钟节拍1KHz,即1ms
u #define configMAX_PRIORITIES ( 5 )
定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4
不包含5,对于这一点,初学者要特别的注意。
u #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。
u #define configUSE_TIMERS 1
使能定时器组。
u #define configTIMER_TASK_PRIORITY ( 2 )
配置定时器任务的优先级。
u #define configTIMER_QUEUE_LENGTH 10
配置定时器任务用到的消息队列大小,即能够存储的消息个数。
u #define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
配置定时器任务的任务栈大小。
u configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01
定义受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOSAPI的最高优先级。为了进一步说明这个宏定义的的作用,解释如下:
l 使用CM内核的MCU,官方强烈建议将NVIC的优先级分组配置为全抢占式优先级,全部配置为抢占式优先级的好处就是方便管理。
l 对于STM32来说,设置NVIC的优先级分组为4时,NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)就是全部配置为抢占式优先级。又因为STM32的优先级设置仅使用CM内核8bit中的高4bit,即只能区分2^4 = 16种优先级。因此当优先级分组设置为4的时候可供用户选择抢占式优先级为015,共16个优先级,配置为0表示最高优先级,配置为15表示最低优先级,不存在子优先级。
l 这里配置configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY0x01表示用户可以在抢占式优先级为115的中断里面调用FreeRTOSAPI函数,抢占式优先级为0的中断里面是不允许调用的。
更多关于这个参数说明请参看第12章。
FreeRTOS任务调试信息(按K1按键,串口打印):
【FreeRTOS操作系统教程】第19章 <wbr>FreeRTOS定时器组

上面截图中打印出来的任务状态字母B, R, D, S对应如下含义:
#define tskBLOCKED_CHAR ( 'B' ) 任务阻塞
#define tskREADY_CHAR ( 'R' ) 任务就绪
#define tskDELETED_CHAR ( 'D' ) 任务删除
#define tskSUSPENDED_CHAR ( 'S' ) 任务挂起
程序设计:
u 任务栈大小分配:
vTaskUserIF任务 2048字节
vTaskLED任务 2048字节
vTaskMsgPro任务 :2048字节
vTaskStart任务 2048字节
任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
u 系统栈大小分配:
【FreeRTOS操作系统教程】第19章 <wbr>FreeRTOS定时器组

u FreeROTS初始化:

int main(void)
{

__set_PRIMASK(1);


bsp_Init();


vSetupSysInfoTest();


AppTaskCreate();


AppObjCreate();


vTaskStartScheduler();


while(1);
}
u 硬件外设初始化
硬件外设的初始化是在bsp.c文件实现:

void bsp_Init(void)
{


NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

bsp_InitUart();
bsp_InitLed();
bsp_InitKey();
}
u FreeRTOS任务创建:

static void AppTaskCreate (void)
{
xTaskCreate( vTaskTaskUserIF,
'vTaskUserIF',
512,
NULL,
1,
&xHandleTaskUserIF ); /* 任务句柄 */


xTaskCreate( vTaskLED,
'vTaskLED',
512,
NULL,
2,
&xHandleTaskLED );

xTaskCreate( vTaskMsgPro,
'vTaskMsgPro',
512,
NULL,
3,
&xHandleTaskMsgPro ); /* 任务句柄 */


xTaskCreate( vTaskStart,
'vTaskStart',
512,
NULL,
4,
&xHandleTaskStart );
}
u FreeRTOS定时器组创建:

static void AppObjCreate (void)
{
uint8_t i;
const TickType_t xTimerPer[2] = {100, 100};


for(i = 0; i < 2; i++)
{
xTimers[i] = xTimerCreate('Timer',
xTimerPer[i],
pdTRUE,
(void *) i,
vTimerCallback);

if(xTimers[i] == NULL)
{

}
else
{

if(xTimerStart(xTimers[i], 100) != pdPASS)
{

}
}
}
}
u 四个FreeRTOS任务的实现:

static void vTaskTaskUserIF(void *pvParameters)
{
uint8_t ucKeyCode;
uint8_t pcWriteBuffer[500];
EventBits_t uxBits;

while(1)
{
ucKeyCode = bsp_GetKey();

if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{

case KEY_DOWN_K1:
printf('=================================================\r');
printf('任务名 任务状态 优先级 剩余栈 任务序号\r');
vTaskList((char *)&pcWriteBuffer);
printf('%s\r', pcWriteBuffer);

printf('\r任务名 运行计数 使用率\r');
vTaskGetRunTimeStats((char *)&pcWriteBuffer);
printf('%s\r', pcWriteBuffer);
break;


default:
break;
}
}

vTaskDelay(20);
}
}


static void vTaskLED(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 200;


xLastWakeTime = xTaskGetTickCount();

while(1)
{
bsp_LedToggle(2);


vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}


static void vTaskMsgPro(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 200;


xLastWakeTime = xTaskGetTickCount();

while(1)
{
bsp_LedToggle(4);


vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}


static void vTaskStart(void *pvParameters)
{
while(1)
{

bsp_KeyScan();
vTaskDelay(10);
}
}
u 定时器组回调函数的实现:

static void vTimerCallback(xTimerHandle pxTimer)
{
uint32_t ulTimerID;

configASSERT(pxTimer);


ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);


if(ulTimerID == 0)
{
bsp_LedToggle(1);
}


if(ulTimerID == 1)
{
bsp_LedToggle(2);
}
}

我的更多文章

下载客户端阅读体验更佳

APP专享