FreeRTOS低功耗之tickless模式

来源:互联网 发布:有什么好的网游知乎 编辑:程序博客网 时间:2024/05/18 02:20

本章节为大家讲解FreeRTOS本身支持的低功耗模式tickless实现方法,tickless低功耗机制是当前小型RTOS所采用的通用低功耗方法,比如embOSRTXuCOS-III(类似方法)都有这种机制。

本章教程配套的例子含Cortex-M3内核的STM32F103Cortex-M4内核的STM32F407以及F429

33.1 tickless低功耗模式介绍

33.2 FreeRTOS的低功耗模式介绍

33.3 FreeRTOS的低功耗模式配置

33.4 FreeRTOS实现tickless模式的框架

33.5 实验例程说明

33.6       总结

 

 

33.1  tickless低功耗模式介绍

tickless低功耗机制是当前小型RTOS所采用的通用低功耗方法,比如embOSRTXuCOS-III(类似方法)都有这种机制。

FreeRTOS的低功耗也是采用的这种方式,那么tickless又是怎样一种模式呢?仅从字母上看tick是滴答时钟的意思,lesstick的后缀,表示较少的,这里的含义可以表示为无滴答时钟。整体看这个字母就是表示滴答时钟节拍停止运行的情况。

反映在FreeRTOS上,tickless又是怎样一种情况呢?我们都知道,当用户任务都被挂起或者阻塞时,最低优先级的空闲任务会得到执行。那么STM32支持的睡眠模式,停机模式就可以放在空闲任务里面实现。为了实现低功耗最优设计,我们还不能直接把睡眠或者停机模式直接放在空闲任务就可以了。进入空闲任务后,首先要计算可以执行低功耗的最大时间,也就是求出下一个要执行的高优先级任务还剩多少时间。然后就是把低功耗的唤醒时间设置为这个求出的时间,到时间后系统会从低功耗模式被唤醒,继续执行多任务。这个就是所谓的tickless模式。从上面的讲解中可以看出,实现tickless模式最麻烦是低功耗可以执行的时间如何获取。关于这个问题,FreeRTOS已经为我们做好了。

33.2 FreeRTOS的低功耗模式介绍

对于Cortex-M3M4内核来说,FreeRTOS已经提供了tickless低功耗代码的实现,通过调用指令WFI实现睡眠模式,具体代码的实现就在port.c文件中,用户只需在FreeRTOSConfig.h文件中配置

宏定义configUSE_TICKLESS_IDLE1即可。如果配置此参数为2,那么用户可以自定义tickless低功耗模式的实现。当用户将宏定义configUSE_TICKLESS_IDLE配置为1且系统运行满足以下两个条件时,系统内核会自动的调用低功耗宏定义函数portSUPPRESS_TICKS_AND_SLEEP()

-------------------------------

(1)当前空闲任务正在运行,所有其它的任务处在挂起状态或者阻塞状态。

(2)根据用户配置configEXPECTED_IDLE_TIME_BEFORE_SLEEP的大小,只有当系统可运行于低功耗模式的时钟节拍数大于等于这个参数时,系统才可以进入到低功耗模式。此参数默认已经在FreeRTOS.h文件进行定义了,下面是具体的定义内容,当然,用户也可以在FreeRTOSConfig.h文件中重新定义:

#ifndef configEXPECTED_IDLE_TIME_BEFORE_SLEEP

     #define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2

#endif

 

#if configEXPECTED_IDLE_TIME_BEFORE_SLEEP < 2

#error configEXPECTED_IDLE_TIME_BEFORE_SLEEP must not be less than 2

#endif

默认定义的大小是2个系统时钟节拍,且用户自定义的话,不可以小于2个系统时钟节拍。

---------------------------------

函数portSUPPRESS_TICKS_AND_SLEEPFreeRTOS实现tickles模式的关键,此函数被空闲任务调用,其定义是在portmacro.h文件中:

 

#ifndef portSUPPRESS_TICKS_AND_SLEEP

     extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime );

     #define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) vPortSuppressTicksAndSleep( xExpectedIdleTime )

#endif

其中函数vPortSuppressTicksAndSleep是实际的低功耗执行代码,在port.c文件中定义,参数xExpectedIdleTime就是系统可以处于低功耗模式的系统时钟节拍数。

FreeRTOS在线电子手册低功耗的说明http://www.freertos.org/low-power-tickless-rtos.html

33.3 FreeRTOS的低功耗模式配置

关于FreeRTOS低功耗方面的配置主要涉及到以下几个问题。

33.3.1滴答定时器频率与系统主频的关系

对于Cortex-M3M4内核的微控制器来说,实时操作系统一般都是采用滴答定时器做系统时钟,FreeRTOS也不例外。SysTick滴答定时器是一个24bit的递减计数器,有两种时钟源可选择,一个是系统主频,另一个是系统主频的八分频,默认的port.c移植文件中是用的系统主频。这里我们就根据这两种时钟源来说一说配置上的不同。

(1)SysTick滴答定时器时钟源选择系统主频

如果滴答定时器选择系统主频的话,那么需要配置configSYSTICK_CLOCK_HZ等于configCPU_CLOCK_HZ,这种关系已经在port.c文件中进行默认配置了:

#ifndef configSYSTICK_CLOCK_HZ

     #define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ

    

     #define portNVIC_SYSTICK_CLK_BIT ( 1UL << 2UL )

#else

    

     #define portNVIC_SYSTICK_CLK_BIT ( 0 )

#endif

其中系统主频configCPU_CLOCK_HZ是在FreeRTOSConfig.h文件中进行定义的。

(2)SysTick滴答定时器时钟源选择系统主频的八分频

这种情况的话,需要用户在FreeRTOSConfig.h文件中专门配置configSYSTICK_CLOCK_HZ为实际的频率,即系统主频的八分频大小。

33.3.2系统时钟节拍不使用滴答定时器

这种情况我们这里不做讨论,用户看FreeRTOS官网此处的说明即可:

http://www.freertos.org/low-power-ARM-cortex-rtos.html

33.3.3如何使用微控制器其它低功耗模式

前面我们说了,对Cortex-M3M4内核来说,FreeRTOS自带的低功耗模式是通过指令WFI让系统进入睡眠模式,如果想让系统进入停机模式,又该怎么修改呢?FreeRTOS为我们提供了两个函数:

configPRE_SLEEP_PROCESSING( xExpectedIdleTime )

configPOST_SLEEP_PROCESSING( xExpectedIdleTime )

这两个函数的定义是在FreeRTOS.h文件中定义的,什么都没有执行:

#ifndef configPRE_SLEEP_PROCESSING

     #define configPRE_SLEEP_PROCESSING( x )

#endif

 

#ifndef configPOST_SLEEP_PROCESSING

     #define configPOST_SLEEP_PROCESSING( x )

#endif

如果需要实际执行代码需要用户在FreeRTOSConfig.h文件中重新进行宏定义,将其映射到一个实际的函数中。另外,这两个函数是在port.C文件中被函数vPortSuppressTicksAndSleep调用,具体位置如下:

 

              xModifiableIdleTime = xExpectedIdleTime;

              configPRE_SLEEP_PROCESSING( xModifiableIdleTime );

              if( xModifiableIdleTime > 0 )

              {

                   __dsb( portSY_FULL_READ_WRITE );

                   __wfi();

                   __isb( portSY_FULL_READ_WRITE );

              }

              configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

这两个函数位于指令wfi的前面和后面,用户想实现其它低功耗方式的关键就在这两个函数里面:

(1)configPRE_SLEEP_PROCESSING( xExpectedIdleTime )

执行低功耗模式前,用户可以在这个函数里面关闭外设时钟来进一步降低系统功耗。设置其它低功耗方式也是在这个函数里面,用户只需设置参数xExpectedIdleTime=0即可屏蔽掉默认的wfi指令执行方式,因为退出这个函数后会通过if语句检测此参数是否大于0,即上面的代码所示。因此,如果用户想实现其它低功耗模式还是比较方便的,配置好其它低功耗模式后,设置参数 xExpectedIdleTime = 0即可,但切不可将此参数随意设置为0以外的其它数值。

(2)configPOST_SLEEP_PROCESSING ( xExpectedIdleTime )

退出低功耗模式后,此函数会得到调用,之前在configPRE_SLEEP_PROCESSING里面关闭的外设时钟,可以在此函数里面重新打开,让系统恢复到正常运行状态。

33.4FreeRTOS实现tickless模式的框架

Cortex-M3M4内核的微控制器来说,FreeRTOS已经提供了tickless低功耗模式的代码,对于没有支持的微控制器,用户可以在FreeRTOSConfig.h文件中配置portSUPPRESS_TICKS_AND_SLEEP宏定义,来映射实际执行函数。

如果用户不想使用FreeRTOS提供的的tickless也可以自定义,方法也是在FreeRTOSConfig.h文件中配置portSUPPRESS_TICKS_AND_SLEEP宏定义,来映射实际执行函数。

下面是FreeRTOS实现低功耗tickless模式的代码框架,方便用户对tickles模式有一个认识,同时也方便FreeRTOS没有支持的微控制器,用户可以参考实现。当然,不局限于这种方法,用户有更好的方法,也可以的。其中函数vTaskStepTickeTaskConfirmSleepModeStatusFreeRTOS提供的,其余的函数是需要用户实现的。

 

 

#define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )

 

 

void vApplicationSleep( TickType_t xExpectedIdleTime )

{

unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;

eSleepModeStatus eSleepStatus;

 

 

 

    ulLowPowerTimeBeforeSleep = ulGetExternalTime();

 

 

    prvStopTickInterruptTimer();

 

 

    disable_interrupts();

 

 

 

    eSleepStatus = eTaskConfirmSleepModeStatus();

 

    if( eSleepStatus == eAbortSleep )

    {

       

        prvStartTickInterruptTimer();

        enable_interrupts();

    }

    else

{

    

        if( eSleepStatus == eNoTasksWaitingTimeout )

        {

           

            prvSleep();

        }

        else

        {

            

            vSetWakeTimeInterrupt( xExpectedIdleTime );

 

            

            prvSleep();

 

            

            ulLowPowerTimeAfterSleep = ulGetExternalTime();

 

           

            vTaskStepTick( ulLowPowerTimeAfterSleep ulLowPowerTimeBeforeSleep );

        }

 

  

        

        enable_interrupts();

 

       

        prvStartTickInterruptTimer();

    }

}

33.5实验例程说明

33.5.1STM32F103开发板实验

配套例子:

V4-339_FreeRTOS实验_低功耗(tickless之睡眠模式)

实验目的:

1.     学习FreeRTOS的低功耗(tickless之睡眠模式)

2.     FreeRTOS自带的tickless模式使用比较简单,只需用户使能宏配置:

#define configUSE_TICKLESS_IDLE         1

3.     为了打印系统信息,前面的所有实验中初始化了一个定时器中断,精度高于滴答定时器中断,每50us进一次中断,本例子关闭了任务执行情况打印功能,因为高频率的定时器中断会影响低功耗tickless模式效率。

实验内容:

1.     K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。

2.     FreeRTOS自带的tickless模式是调用的指令__WFI进入睡眠模式。

3.     各个任务实现的功能如下:

              vTaskUserIF任务   :按键消息处理。

              vTaskLED任务     :LED闪烁。

              vTaskMsgPro任务 :消息处理,这里用作LED闪烁。

              vTaskStart任务    :启动任务,也是最高优先级任务,这里实现按键扫描。

4.     设计低功耗主要从以下几方面着手:

(1)  用户需要根据最低电源消耗、最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。可以使用的低功耗方式有睡眠模式,待机模式,停机模式。

(2)  选择了低功耗方式后就是关闭可以关闭的外设时钟。

(3)  降低系统主频。

(4)  注意I/O的状态。

如果此I/O口带上拉,请设置为高电平输出或者高阻态输入。

如果此I/O口带下拉,请设置为低电平输出或者高阻态输入。

a.     在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态。

b.    在停机模式下,所有的I/O引脚都保持它们在运行模式时的状态。

c.     在待机模式下,所有的I/O引脚处于高阻态,除了以下的引脚:

n  复位引脚(始终有效)

n  当被设置为防侵入或校准输出时的TAMPER引脚。

n  被使能的唤醒引脚。

(5)  注意I/O和外设IC的连接。

(6)  测低功耗的时候,一定不要连接调试器,更不能边调试边测电流。

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 configUSE_TICKLESS_IDLE      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

 

 

#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 configUSE_TICKLESS_IDLE     1

使能tickless低功耗模式。

u  #define configMAX_PRIORITIES          ( 5 )

定义可供用户使用的最大优先级数,如果这个定义的是5,那么用户可以使用的优先级号是0,1,2,3,4不包含5,对于这一点,初学者要特别的注意。

u  #define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

定义堆大小,FreeRTOS内核,用户动态内存申请,任务栈等都需要用这个空间。

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章。

程序设计:

u  任务栈大小分配:

vTaskUserIF任务   :2048字节

vTaskLED任务     :2048字节

vTaskMsgPro任务 :2048字节

vTaskStart任务    :2048字节

任务栈空间是在任务创建的时候从FreeRTOSConfig.h文件中定义的heap空间中申请的

#define configTOTAL_HEAP_SIZE        ( ( size_t ) ( 17 * 1024 ) )

u  系统栈大小分配:

【FreeRTOS操作系统教程】第33章 <wbr>FreeRTOS低功耗之tickless模式

 

u  FreeROTS初始化:

 

int main(void)

{

    

     __set_PRIMASK(1); 

    

    

     bsp_Init();

        

    

     AppTaskCreate();

    

   

    vTaskStartScheduler();

 

    

     while(1);

}

u  硬件外设初始化

硬件外设的初始化是在bsp.c文件实现:

 

void bsp_Init(void)

{

    

    

     DBGMCU_Config(DBGMCU_SLEEP, ENABLE);

    

          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 vTaskTaskUserIF(void *pvParameters)

{

     uint8_t ucKeyCode;

    

 

    while(1)

    {

         ucKeyCode = bsp_GetKey();

        

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                  

                   case KEY_DOWN_K1:          

                       printf("本例子关闭了任务执行情况打印功能,因为高频率的定时器中断影响低功耗tickless

模式效率\r\n");

                       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);

         bsp_LedToggle(3);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

 

static void vTaskMsgPro(void *pvParameters)

{

     TickType_t xLastWakeTime;

     const TickType_t xFrequency = 400;

 

    

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {

         bsp_LedToggle(1);

         bsp_LedToggle(4);

        

        

        vTaskDelayUntil(&xLastWakeTime, xFrequency);

    }

}

 

 

static void vTaskStart(void *pvParameters)

{

    while(1)

    {

        

         bsp_KeyScan();

        vTaskDelay(10);

    }

}

原创粉丝点击