Connectivity Software: Implement tickless mode in FreeRTOS

Document created by Paola Yadira Soto Jauregui Employee on Jul 16, 2018Last modified by Santiago_Lopez on Aug 6, 2018
Version 4Show Document
  • View in full screen mode

FreeRTOS keeps track of the elapsed time in the system by counting ticks. The tick count increases inside a periodic interrupt routine generated by one of the timers available in the host MCU. When FreeRTOS is running the Idle task hook, the microcontroller can be placed into a low power mode. Depending on the low power mode, one or more peripherals can be disabled in order to save the maximum amount of energy possible. The FreeRTOS tickless idle mode allows stopping the tick interruption during the idle periods. Stopping the tick interrupt allows the microcontroller to remain in a deep power saving state until a wake-up event occurs.

 

The application needs to configure the module (timer, ADC, etc…) that will wake up the microcontroller before the next FreeRTOS task needs to be executed. For this purpose, during the execution of vPortSuppressTicksAndSleep, a function called by FreeRTOS when tickless idle is enabled, the maximum amount of time the MCU can remain asleep is passed as an input parameter in order to properly configure the wake-up module. Once the MCU wakes up and the FreeRTOS tick interrupt is restarted, the number of tick counts lost while the MCU was asleep must be restored.

 

Tickless mode is not enabled by default in the Connectivity Software FreeRTOS demos. In this post, we will show how to enable it. For this example, we will use QN9080x to demonstrate the implementation.

 

lowpower

freertos tickless

tickless

 

Changes where implemented in the following files:

 

\framework\LowPower\Source\QN908XC\PWR.c

\framework\LowPower\Interface\QN908XC\PWR_Interface.h

\freertos\fsl_tickless_generic.h

\source\common\ApplMain.c

 

The following file was removed from the project

fsl_tickless_qn_rtc.c

 

PWR.C and PWR_Interface.h

Changes in this files are intended to prepare the QN9080 for waking up using the RTC timer. Other parts, like MKW41Z, might enable other modules for this purpose (like LPTMR) and changes on this files might not be necessary.

 

*** PWR.c ***

Add the driver for RTC. This is the timer we will use to wake up the QN908x

/*Tickless: Add RTC driver for tickless support */
#include "fsl_rtc.h"

 

Add local variables

uint64_t mLpmTotalSleepDuration;        //Tickless
uint8_t mPWR_DeepSleepTimeUpdated = 0;  //Tickless: Coexistence with TMR manager

 

Add private functions

 uint32_t PWR_RTCGetMsTimeUntilNextTick (void);         //Tickless
void PWR_RTCSetWakeupTimeMs (uint32_t wakeupTimeMs);   //Tickless
void PWR_RTCWakeupStart (void);                        //Tickless

 

Make the following changes in PWR.C. All the required changes are marked as comments with "Start" where the change starts, and with "End where the change ends"

#if (cPWR_UsePowerDownMode && (cPWR_EnableDeepSleepMode_1 || cPWR_EnableDeepSleepMode_2 || cPWR_EnableDeepSleepMode_3 || cPWR_EnableDeepSleepMode_4))
static void PWR_HandleDeepSleepMode_1_2_3_4(void)
{
#if cPWR_BLE_LL_Enable
    uint8_t   power_down_mode = 0xff;
    bool_t    enterLowPower = TRUE;

    __disable_irq();

/****************START***********************************/
    /*Tickless: Configure wakeup timer */
    if(mPWR_DeepSleepTimeUpdated){
      PWR_RTCSetWakeupTimeMs(mPWR_DeepSleepTimeMs);
      mPWR_DeepSleepTimeUpdated = FALSE;        // Coexistence with TMR Manager
    }
   
    PWR_RTCWakeupStart();
/*****************END**************************************/

    PWRLib_ClearWakeupReason();

    //Try to put BLE in deep sleep mode
    power_down_mode = BLE_sleep();
    if (power_down_mode < kPmPowerDown0)
    {
        enterLowPower = false; // BLE doesn't allow deep sleep
    }
    //no else - enterLowPower is already true

    if(enterLowPower)
    {

/****************START**************************/
        uint32_t freeRunningRtcPriority;
/****************END****************************/

        NVIC_ClearPendingIRQ(OSC_INT_LOW_IRQn);
        NVIC_EnableIRQ(OSC_INT_LOW_IRQn);
        while (SYSCON_SYS_STAT_OSC_EN_MASK & SYSCON->SYS_STAT) //wait for BLE to enter sleep
        {
            POWER_EnterSleep();
        }
        NVIC_DisableIRQ(OSC_INT_LOW_IRQn);

        if(gpfPWR_LowPowerEnterCb != NULL)
        {
            gpfPWR_LowPowerEnterCb();
        }

/* Disable SysTick counter and interrupt */
        sysTickCtrl = SysTick->CTRL & (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
        SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
        ICSR |= (1 << 25); // clear PendSysTick bit in ICSR, if set

/************************START***********************************/
        NVIC_ClearPendingIRQ(RTC_FR_IRQn);
        freeRunningRtcPriority = NVIC_GetPriority(RTC_FR_IRQn);
        NVIC_SetPriority(RTC_FR_IRQn,0);
/***********************END***************************************/

        POWER_EnterPowerDown(0); //Nighty night!

/************************START**********************************/
        NVIC_SetPriority(RTC_FR_IRQn,freeRunningRtcPriority);
/************************END************************************/

        if(gpfPWR_LowPowerExitCb != NULL)
        {
            gpfPWR_LowPowerExitCb();
        }

        /* Restore the state of SysTick */
        SysTick->CTRL |= sysTickCtrl;

        PWRLib_UpdateWakeupReason();
    }

    __enable_irq();

#else
    PWRLib_ClearWakeupReason();
#endif /* cPWR_BLE_LL_Enable */
}
#endif /* (cPWR_UsePowerDownMode && cPWR_EnableDeepSleepMode_1) */

 

void PWR_SetDeepSleepTimeInMs(uint32_t deepSleepTimeMs)
{
#if (cPWR_UsePowerDownMode)
    if(deepSleepTimeMs == 0)
    {
        return;
    }

    mPWR_DeepSleepTimeMs = deepSleepTimeMs;
/****************START******************/
    mPWR_DeepSleepTimeUpdated = TRUE;
/****************END*********************/
#else
    (void) deepSleepTimeMs;
#endif /* (cPWR_UsePowerDownMode) */
}

 

Add the following function definitions at the end of the file

/*---------------------------------------------------------------------------
* Name: PWR_GetTotalSleepDurationMS
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/

uint32_t PWR_GetTotalSleepDurationMS(void)
{
    uint32_t time;
    uint32_t currentSleepTime;

    OSA_InterruptDisable();
    currentSleepTime = RTC_GetFreeRunningInterruptThreshold(RTC);
    if(currentSleepTime >= mLpmTotalSleepDuration){
    time = (currentSleepTime-mLpmTotalSleepDuration)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
    }
    else{
    time = ((0x100000000-mLpmTotalSleepDuration)+currentSleepTime)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
    }
    OSA_InterruptEnable();

    return time;
}

/*---------------------------------------------------------------------------
* Name: PWR_ResetTotalSleepDuration
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/

void PWR_ResetTotalSleepDuration(void)
{
    OSA_InterruptDisable();
    mLpmTotalSleepDuration = RTC_GetFreeRunningCount(RTC);
    OSA_InterruptEnable();
}

/*---------------------------------------------------------------------------
* Name: PWR_RTCGetMsTimeUntilNextTick
* Description: -
* Parameters: -
* Return: Time until next tick in mS
*---------------------------------------------------------------------------*/

uint32_t PWR_RTCGetMsTimeUntilNextTick (void)
{
    uint32_t time;
    uint32_t currentRtcCounts, thresholdRtcCounts;

    OSA_InterruptDisable();
    currentRtcCounts = RTC_GetFreeRunningCount(RTC);
    thresholdRtcCounts = RTC_GetFreeRunningResetThreshold(RTC);
    if(thresholdRtcCounts > currentRtcCounts){
    time = (thresholdRtcCounts-currentRtcCounts)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
    }
    else{
    time = ((0x100000000-currentRtcCounts)+thresholdRtcCounts)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
    }
    OSA_InterruptEnable();

    return time;
}

/*---------------------------------------------------------------------------
* Name: PWR_RTCSetWakeupTimeMs
* Description: -
* Parameters: wakeupTimeMs: New wakeup time in milliseconds
* Return: -
*---------------------------------------------------------------------------*/

void PWR_RTCSetWakeupTimeMs (uint32_t wakeupTimeMs){
    uint32_t wakeupTimeTicks;
    uint32_t thresholdValue;

    wakeupTimeTicks = (wakeupTimeMs*CLOCK_GetFreq(kCLOCK_32KClk))/1000;
    thresholdValue = RTC_GetFreeRunningCount(RTC);
    thresholdValue += wakeupTimeTicks;
    RTC_SetFreeRunningInterruptThreshold(RTC, thresholdValue);
}

/*---------------------------------------------------------------------------
* Name: PWR_RTCWakeupStart
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/

void PWR_RTCWakeupStart (void){
  if(!(RTC->CNT2_CTRL & RTC_CNT2_CTRL_CNT2_EN_MASK)){
    RTC->CNT2_CTRL |= 0x52850000 | RTC_CNT2_CTRL_CNT2_EN_MASK | RTC_CNT2_CTRL_CNT2_WAKEUP_MASK | RTC_CNT2_CTRL_CNT2_INT_EN_MASK;
  }
  else{
    RTC->CNT2_CTRL |= 0x52850000 | RTC_CNT2_CTRL_CNT2_WAKEUP_MASK | RTC_CNT2_CTRL_CNT2_INT_EN_MASK;
  }
}

 

 *** PWR_Interface.h ***

Add the following function declarations at the end of the file

/*---------------------------------------------------------------------------
* Name: PWR_GetTotalSleepDurationMS
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/

uint32_t PWR_GetTotalSleepDurationMS(void);

/*---------------------------------------------------------------------------
* Name: PWR_ResetTotalSleepDuration
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/

void PWR_ResetTotalSleepDuration(void);

#ifdef __cplusplus
}
#endif

#endif /* _PWR_INTERFACE_H_ */

 

FSL_TICKLESS_GENERIC

The following changes have the purpose of preparing the system for recovering the missed ticks during the low power period.

 

Make the following changes in fsl_tickless_generic.h. All the required changes are marked as comments with "Start" where the change starts, and with "End where the change ends"

/* QN_RTC: The RTC free running is a 32-bit counter. */
#define portMAX_32_BIT_NUMBER (0xffffffffUL)
#define portRTC_CLK_HZ (0x8000UL)

/* A fiddle factor to estimate the number of SysTick counts that would have
occurred while the SysTick counter is stopped during tickless idle
calculations. */

#define portMISSED_COUNTS_FACTOR (45UL)

/*
* The number of SysTick increments that make up one tick period.
*/

/****************************START**************************/
#if configUSE_TICKLESS_IDLE == 1
    static uint32_t ulTimerCountsForOneTick;
#endif /* configUSE_TICKLESS_IDLE */
/************************END*********************************/

/*
* Setup the timer to generate the tick interrupts.
*/

void vPortSetupTimerInterrupt(void);

#ifdef __cplusplus
}
#endif

#endif /* FSL_TICKLESS_GENERIC_H */

 

ApplMain.c

This is the main application file. Here is where we will call the proper APIs to enter the MCU in low power mode and perform the tick recovery sequence.

 

Include RTC and FreeRTOS header files needed

/*Tickless: Include RTC and FreeRTOS header files */
#include "fsl_rtc.h"
#include "fsl_tickless_generic.h"
#include "FreeRTOS.h"
#include "task.h"

 

QN9080 includes several low power modes. Sleep mode maintains most of the modules active. Power Down modes turn off most of the modules but allow the user to configure some modules to remain active to wake the MCU up when necessary. Using tickless FreeRTOS involves having to wake-up by some timer before the next ready task has to execute. For QN908x this timer will be the RTC which requires the 32.768kHz oscillator to remain active. We will change the Connectivity Software Power Lib to use Deep Sleep mode 3 (Power Down mode 0 for QN908x) which maintains the 32.768kHz oscillator on. This change is implemented in the main_task function.

#if !defined(MULTICORE_BLACKBOX)
        /* BLE Host Stack Init */
        if (Ble_Initialize(App_GenericCallback) != gBleSuccess_c)
        {
            panic(0,0,0,0);
            return;
        }
#endif /* MULTICORE_BLACKBOX */

/*************** Start ****************/
#if (cPWR_UsePowerDownMode)
    PWR_ChangeDeepSleepMode(3);
#endif
/*************** End ****************/
    }
   
    /* Call application task */
    App_Thread( param );
}

 

Also, tickless FreeRTOS requires a special Idle function which takes as an input parameter the amount of RTOS ticks the MCU can remain asleep before the next task needs to be executed. The following changes disable the default Idle function provided in the Connectivity Software demos when the tickless mode is enabled.

/************************************************************************************
*************************************************************************************
* Private prototypes
*************************************************************************************
************************************************************************************/


/***************************** Start ******************************/
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
/*****************************  End  ******************************/
#if (mAppIdleHook_c)
    #define AppIdle_TaskInit()
    #define App_Idle_Task()
#else
    static osaStatus_t AppIdle_TaskInit(void);
/************************************************************************************
*************************************************************************************
* Private memory declarations
*************************************************************************************
************************************************************************************/


/******************************** Start ******************************/
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
/******************************** End ******************************/
#if (!mAppIdleHook_c)
OSA_TASK_DEFINE( App_Idle_Task, gAppIdleTaskPriority_c, 1, gAppIdleTaskStackSize_c, FALSE );
osaTaskId_t gAppIdleTaskId = 0;
#endif
#endif  /* cPWR_UsePowerDownMode */
#if !gUseHciTransportDownward_d
        pfBLE_SignalFromISR = BLE_SignalFromISRCallback;
#endif /* !gUseHciTransportDownward_d */

/**************************** Start ************************/
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
/**************************** End ************************/
#if (!mAppIdleHook_c)
        AppIdle_TaskInit();
#endif
#endif
/***************************START**************************/
#if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE)
/******************************END***************************/
static void App_Idle(void)
{
    PWRLib_WakeupReason_t wakeupReason;

    if( PWR_CheckIfDeviceCanGoToSleep() )
    {
        /* Enter Low Power */
        wakeupReason = PWR_EnterLowPower();

#if gFSCI_IncludeLpmCommands_c
        /* Send Wake Up indication to FSCI */
        FSCI_SendWakeUpIndication();
#endif

#if gKBD_KeysCount_c > 0
        /* Woke up on Keyboard Press */
        if(wakeupReason.Bits.FromKeyBoard)
        {
            KBD_SwitchPressedOnWakeUp();
            PWR_DisallowDeviceToSleep();
        }
#endif
    }
    else
    {
        /* Enter MCU Sleep */
        PWR_EnterSleep();
    }
}
#endif /* cPWR_UsePowerDownMode */

#if (mAppIdleHook_c)

void vApplicationIdleHook(void)
{
#if (gAppUseNvm_d)
    NvIdle();
#endif

/*******************************START****************************/
#if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE)
/*********************************END*******************************/
    App_Idle();
#endif
}

#else /* mAppIdleHook_c */

/******************************* START ****************************/
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
/******************************* END ****************************/
static void App_Idle_Task(osaTaskParam_t argument)
{
    while(1)
    {
#if gAppUseNvm_d
        NvIdle();
#endif
/******************************* START ****************************/
#if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE)
/******************************* END ****************************/
        App_Idle();
#endif

        /* For BareMetal break the while(1) after 1 run */
        if (gUseRtos_c == 0)
        {
            break;
        }
    }
}

 

 

Once the default Idle function has been disabled, the special Idle function must be implemented. Add the following code at the end of the ApplMain.c file.

/*Tickless: Implement Tickless Idle */
#if (cPWR_UsePowerDownMode && configUSE_TICKLESS_IDLE)
extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{


    uint32_t time_ms = xExpectedIdleTime * portTICK_PERIOD_MS;
    uint32_t tmrMgrExpiryTimeMs;

    ulTimerCountsForOneTick = 160000;//VALUE OF THE SYSTICK 10 ms

#if (cPWR_UsePowerDownMode)
    PWRLib_WakeupReason_t wakeupReason;
   
    //TMR_MGR: Get next timer manager expiry time
    tmrMgrExpiryTimeMs = TMR_GetFirstExpireTime(gTmrAllTypes_c);

    // TMR_MGR: Update RTC Threshold only if RTOS needs to wakeup earlier
    if(time_ms<tmrMgrExpiryTimeMs){
      PWR_SetDeepSleepTimeInMs(time_ms);
    }
   
    PWR_ResetTotalSleepDuration();

    if( PWR_CheckIfDeviceCanGoToSleep() )
    {
        wakeupReason = PWR_EnterLowPower();
       
        //Fix: All the tick recovery stuff should only happen if device entered in DSM
        xExpectedIdleTime = PWR_GetTotalSleepDurationMS() / portTICK_PERIOD_MS;     // Fix: ticks = time in mS asleep / mS per each tick (portTICK_PERIOD_MS)

        /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
        again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
        value. The critical section is used to ensure the tick interrupt
        can only execute once in the case that the reload register is near
        zero. */

        portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
        portENTER_CRITICAL();

        portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
        vTaskStepTick( xExpectedIdleTime );
        portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;

        portEXIT_CRITICAL();

#if gKBD_KeysCount_c > 0
        /* Woke up on Keyboard Press */
        if(wakeupReason.Bits.FromKeyBoard)
        {
          KBD_SwitchPressedOnWakeUp();
          PWR_DisallowDeviceToSleep();
        }
#endif
    }
    else
    {
      /* Enter MCU Sleep */
      PWR_EnterSleep();
    }


#endif /* cPWR_UsePowerDownMode */

}

#endif  //cPWR_UsePowerDownMode && configUSE_TICKLESS_IDLE

 

From the previous function, the value of ulTimerCountsForOneTick is used to restore the count of the RTOS tick timer after waking up. This value depends on the RTOS Tick interval defined in FreeRTOSConfig.h and is calculated using the following formula:

 

SYST_RNR  =  F_Systick_CLK(Hz) * T_FreeRTOS_Ticks(ms)

 

Where:

  •       F_Systick_CLK(Hz) = AHB or 32KHz of the SYST_CSR selection
  •       T_FreeRTOS_Ticks(ms) = tick count value.

 

FreeRTOSConfig.h

Finally, on the FreeRTOSConfig.h file, make sure that configUSE_TICKLESS_IDLE is set to 1

 * See http://www.freertos.org/a00110.html.
*----------------------------------------------------------*/

#define configUSE_PREEMPTION                    1
#define configUSE_TICKLESS_IDLE                 1 //<--- /***** Start *****/
#define configCPU_CLOCK_HZ                      (SystemCoreClock)
#define configTICK_RATE_HZ                      ((TickType_t)100)
#define configMAX_PRIORITIES                    (18)
#define configMINIMAL_STACK_SIZE                ((unsigned short)90)

 

Testing Tickless RTOS

In order to test if tickless support was successfully added, an example application that toggles an LED is implemented. This application configures an RTOS timer to toggle the LED once every 500mS and enter the MCU in DSM3 during the idle time. The Power Profiling demo was used for this purpose.

 

power_profiling.c

Make sure you have included the following header files

#include "FreeRTOS.h"
#include "task.h"

 

Create an RTOS task for blinking the LED every 500mS. First, declare the task function, task ID and the task itself.

void vfnTaskLedBlinkTest(void* param); //New Task Definition
OSA_TASK_DEFINE(vfnTaskLedBlinkTest, 1, 1, 500, FALSE );
osaTaskId_t gAppTestTask1Id = 0; // TestTask1 Id

 

Create the new task inside the BleApp_Init function

void BleApp_Init(void)

{
    PWR_AllowDeviceToSleep();

    mPowerState = 0;   // Board starts with PD1 enabled
    /******************* Start *****************/
    gAppTestTask1Id = OSA_TaskCreate(OSA_TASK(vfnTaskLedBlinkTest), NULL); //Task Creation
    /*******************  End  *****************/

}

 

Finally, add the task function definition at the end of the file.

void vfnTaskLedBlinkTest(void* param)
{
    uint16_t wTimeValue = 500; //500ms

    while(1)
    {
        LED_BLUE_TOGGLE();
        vTaskDelay(pdMS_TO_TICKS(wTimeValue));
    }
}

 

We can monitor the power consumption in MCUXpresso IDE, with the Power Measurement Tool. With it, we can see the current that is been consumed and prove that the implementation is working as the expected.

 

Configure the Power Measurement Tool

Consumed current

Attachments

    Outcomes