FreeRTOS学习笔记-------软件定时器

软件定时器理论

在FreeRTOS中,软件定时器基于系统滴答中断(Tick Interrupt)。

软件定时器具有两种状态:

  • 运行(Running、Active):时间到达后,回调函数可以执行
  • 冬眠(Dormant):回调函数不会被执行,可以通过句柄访问定时器

定时器的状态转换图如下所示:

另外,软件定时器有两种类型:

  • 一次性(One-shot timers):启动后回调函数只调用一次;可以手动再次启动,不能自动重启
  • 自动加载(Auto-reload timers):启动后回调函数被周期性调用

守护任务

虽然软件定时器基于tick中断,但FreeRTOS是实时操作系统,不允许在内核、中断中执行不确定的代码,所以回调函数并不由tick中断调用。定时器的回调函数在守护任务(RTOS Damemon Task)中被调用。守护任务的调度与普通任务没有区别。

在FreeRTOSConfig.h中将configUSE_TIMERS配置为1,启动调度器时,自动创建守护任务。官方源码如下:

void vTaskStartScheduler( void )
{
    ................

    #if ( configUSE_TIMERS == 1 )
        {
            if( xReturn == pdPASS )
            {
                xReturn = xTimerCreateTimerTask();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    #endif /* configUSE_TIMERS */

   ................
}

    BaseType_t xTimerCreateTimerTask( void )
    {
        BaseType_t xReturn = pdFAIL;

        /* This function is called when the scheduler is started if
         * configUSE_TIMERS is set to 1.  Check that the infrastructure used by the
         * timer service task has been created/initialised.  If timers have already
         * been created then the initialisation will already have been performed. */
        prvCheckForValidListAndQueue();

        if( xTimerQueue != NULL )
        {
            #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                {
                    StaticTask_t * pxTimerTaskTCBBuffer = NULL;
                    StackType_t * pxTimerTaskStackBuffer = NULL;
                    uint32_t ulTimerTaskStackSize;

                    vApplicationGetTimerTaskMemory( &pxTimerTaskTCBBuffer, &pxTimerTaskStackBuffer, &ulTimerTaskStackSize );
                    xTimerTaskHandle = xTaskCreateStatic( prvTimerTask,
                                                          configTIMER_SERVICE_TASK_NAME,
                                                          ulTimerTaskStackSize,
                                                          NULL,
                                                          ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
                                                          pxTimerTaskStackBuffer,
                                                          pxTimerTaskTCBBuffer );

                    if( xTimerTaskHandle != NULL )
                    {
                        xReturn = pdPASS;
                    }
                }
            #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
                {
                    xReturn = xTaskCreate( prvTimerTask,
                                           configTIMER_SERVICE_TASK_NAME,
                                           configTIMER_TASK_STACK_DEPTH,
                                           NULL,
                                           ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
                                           &xTimerTaskHandle );
                }
            #endif /* configSUPPORT_STATIC_ALLOCATION */
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        configASSERT( xReturn );
        return xReturn;
    }


    static portTASK_FUNCTION( prvTimerTask, pvParameters )
    {
        TickType_t xNextExpireTime;
        BaseType_t xListWasEmpty;

        /* Just to avoid compiler warnings. */
        ( void ) pvParameters;

        #if ( configUSE_DAEMON_TASK_STARTUP_HOOK == 1 )
            {
                extern void vApplicationDaemonTaskStartupHook( void );

                /* Allow the application writer to execute some code in the context of
                 * this task at the point the task starts executing.  This is useful if the
                 * application includes initialisation code that would benefit from
                 * executing after the scheduler has been started. */
                vApplicationDaemonTaskStartupHook();
            }
        #endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */

        for( ; ; )
        {
            /* Query the timers list to see if it contains any timers, and if so,
             * obtain the time at which the next timer will expire. */
            xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );

            /* If a timer has expired, process it.  Otherwise, block this task
             * until either a timer does expire, or a command is received. */
            prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );

            /* Empty the command queue. */
            prvProcessReceivedCommands();
        }
    }


    static void prvProcessReceivedCommands( void )
    {
        DaemonTaskMessage_t xMessage;
        Timer_t * pxTimer;
        BaseType_t xTimerListsWereSwitched;
        TickType_t xTimeNow;

        while( xQueueReceive( xTimerQueue, &xMessage, tmrNO_DELAY ) != pdFAIL ) /*lint !e603 xMessage does not have to be initialised as it is passed out, not in, and it is not used unless xQueueReceive() returns pdTRUE. */
        {
           ......................
        }
    }

    static void prvCheckForValidListAndQueue( void )
    {
        /* Check that the list from which active timers are referenced, and the
         * queue used to communicate with the timer service, have been
         * initialised. */
        taskENTER_CRITICAL();
        {
            if( xTimerQueue == NULL )
            {
                vListInitialise( &xActiveTimerList1 );
                vListInitialise( &xActiveTimerList2 );
                pxCurrentTimerList = &xActiveTimerList1;
                pxOverflowTimerList = &xActiveTimerList2;

                #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
                    {
                        /* The timer queue is allocated statically in case
                         * configSUPPORT_DYNAMIC_ALLOCATION is 0. */
                        PRIVILEGED_DATA static StaticQueue_t xStaticTimerQueue;                                                                          /*lint !e956 Ok to declare in this manner to prevent additional conditional compilation guards in other locations. */
                        PRIVILEGED_DATA static uint8_t ucStaticTimerQueueStorage[ ( size_t ) configTIMER_QUEUE_LENGTH * sizeof( DaemonTaskMessage_t ) ]; /*lint !e956 Ok to declare in this manner to prevent additional conditional compilation guards in other locations. */

                        xTimerQueue = xQueueCreateStatic( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, ( UBaseType_t ) sizeof( DaemonTaskMessage_t ), &( ucStaticTimerQueueStorage[ 0 ] ), &xStaticTimerQueue );
                    }
                #else
                    {
                        xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
                    }
                #endif /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */

                #if ( configQUEUE_REGISTRY_SIZE > 0 )
                    {
                        if( xTimerQueue != NULL )
                        {
                            vQueueAddToRegistry( xTimerQueue, "TmrQ" );
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* configQUEUE_REGISTRY_SIZE */
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        taskEXIT_CRITICAL();
    }

 由官方源码可知:

  • 守护任务优先级为configTIMER_TASK_PRIORITY
  • 定时器的控制命令,通过队列与守护进程交互
  • 队列的长度为configTIMER_QUEUE_LENGTH

软件定时器操作函数

/* 使用动态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction );

/* 使用静态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以Tick为单位
* uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器
* 返回值: 成功则返回TimerHandle_t, 否则返回NULL
*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                                 TickType_t xTimerPeriodInTicks,
                                 UBaseType_t uxAutoReload,
                                 void * pvTimerID,
                                 TimerCallbackFunction_t pxCallbackFunction,
                                 StaticTimer_t *pxTimerBuffer );

/* 删除定时器
* xTimer: 要删除哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"删除命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 启动定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 停止定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"停止命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 复位定时器
* xTimer: 哪个定时器
* xTicksToWait: 超时时间
* 返回值: pdFAIL表示"复位命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* xTicksToWait: 超时时间, 命令写入队列的超时时间
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
                               TickType_t xNewPeriod,
                               TickType_t xTicksToWait );

从定时器的状态转换图可以知道,使用xTimerReset() 函数可以让定时器的状态从冬眠态转换为运行态,相当于使用xTimerStart() 函数。 

操作函数中都有一个xTicksToWait参数,是因为这些操作函数通过队列和守护任务进行交互。

回调函数的定义如下,回调函数可以使用pvTimerID参数,比如分辨是哪个定时器。

void TimerCallbackFunction( TimerHandle_t xTimer );

注意事项

守护任务在这两种情况下会退出阻塞态切换为就绪态:命令队列中有数据、某个定时器超时了。
至于守护任务能否马上执行,取决于它的优先级。

使用守护任务优先级较低的情况进行举例:假设定时器在后续某个时刻tX超时了,超时时间是"tX-t2",而非"tX-t4",从xTimerStart() 函数被调用时算起。其中t2是定时器函数被调用的时刻,t4是守护任务执行的时刻。

下列示例代码对此结论进行了验证:

// FreeRTOSConfig.h配置如下
#define configUSE_TIMERS              1
#define configTIMER_TASK_PRIORITY     1
#define configTIMER_QUEUE_LENGTH      1
#define configTIMER_TASK_STACK_DEPTH  100


// 主要代码
static void prvSetupHardware( void );

/*-----------------------------------------------------------*/

void vmainTask1Function( void * param);
void myTimerCallbackFunction( TimerHandle_t xTimer );
BaseType_t timerFlag = 0, task1DelayStartFlag = 0;

/*-----------------------------------------------------------*/
int main( void )
{
	TimerHandle_t timerHandle;
	
	prvSetupHardware();
	
	printf("Hello world!\r\n");
	// 创建定时器
	timerHandle = xTimerCreate("timer", pdMS_TO_TICKS(110), pdTRUE, NULL, myTimerCallbackFunction);
	
	// 动态创建任务
	xTaskCreate(vmainTask1Function, "task1", 100, (void *)timerHandle, 2, NULL);

	/* Start the scheduler. */
	vTaskStartScheduler();

	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/

static void prvSetupHardware( void )
{
	/* Start with the clocks in their expected state. */
	RCC_DeInit();

	/* Enable HSE (high speed external clock). */
	RCC_HSEConfig( RCC_HSE_ON );

	/* Wait till HSE is ready. */
	while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
	{
	}

	/* 2 wait states required on the flash. */
	*( ( unsigned long * ) 0x40022000 ) = 0x02;

	/* HCLK = SYSCLK */
	RCC_HCLKConfig( RCC_SYSCLK_Div1 );

	/* PCLK2 = HCLK */
	RCC_PCLK2Config( RCC_HCLK_Div1 );

	/* PCLK1 = HCLK/2 */
	RCC_PCLK1Config( RCC_HCLK_Div2 );

	/* PLLCLK = 8MHz * 9 = 72 MHz. */
	RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );

	/* Enable PLL. */
	RCC_PLLCmd( ENABLE );

	/* Wait till PLL is ready. */
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
	{
	}

	/* Select PLL as system clock source. */
	RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );

	/* Wait till PLL is used as system clock source. */
	while( RCC_GetSYSCLKSource() != 0x08 )
	{
	}

	/* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
							| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );

	/* SPI2 Periph clock enable */
	RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );


	/* Set the Vector Table base address at 0x08000000 */
	NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );

	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );

	/* Configure HCLK clock as SysTick clock source. */
	SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
	
	serialPortInit();
}

/*-----------------------------------------------------------*/
void vmainTask1Function( void * param)
{
	  int i = 0;
	  xTimerStart((TimerHandle_t)param, 0);
	  while(1) {
			  timerFlag = 1;
			  printf("task1 running\r\n");
			  for(i = 0; i < 10000000; i ++){
					  ;
				}
				task1DelayStartFlag = 1;
				vTaskDelay(pdMS_TO_TICKS(110));
				task1DelayStartFlag = 0;
	  }
}

void myTimerCallbackFunction( TimerHandle_t xTimer )
{
	  timerFlag = 0;
	  printf("timer running\r\n");
}

定时器的周期为100ms,守护任务和普通任务的优先级分别为1和2。因为普通任务的优先级较高,先执行处于运行态,守护任务处于阻塞态。在普通任务中调用xTimerStart函数,此时守护任务处于就绪态,但优先级较低,无法得到执行(即下图中timerFlag处于1的时间段)。由于普通任务调用vTaskDelay函数,切换成阻塞态,此时守护任务进入运行态。

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值