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
#include "fsl_rtc.h"
Add local variables
uint64_t mLpmTotalSleepDuration;
uint8_t mPWR_DeepSleepTimeUpdated = 0;
Add private functions
uint32_t PWR_RTCGetMsTimeUntilNextTick (void);
void PWR_RTCSetWakeupTimeMs (uint32_t wakeupTimeMs);
void PWR_RTCWakeupStart (void);
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();
if(mPWR_DeepSleepTimeUpdated){
PWR_RTCSetWakeupTimeMs(mPWR_DeepSleepTimeMs);
mPWR_DeepSleepTimeUpdated = FALSE;
}
PWR_RTCWakeupStart();
PWRLib_ClearWakeupReason();
power_down_mode = BLE_sleep();
if (power_down_mode < kPmPowerDown0)
{
enterLowPower = false;
}
if(enterLowPower)
{
uint32_t freeRunningRtcPriority;
NVIC_ClearPendingIRQ(OSC_INT_LOW_IRQn);
NVIC_EnableIRQ(OSC_INT_LOW_IRQn);
while (SYSCON_SYS_STAT_OSC_EN_MASK & SYSCON->SYS_STAT)
{
POWER_EnterSleep();
}
NVIC_DisableIRQ(OSC_INT_LOW_IRQn);
if(gpfPWR_LowPowerEnterCb != NULL)
{
gpfPWR_LowPowerEnterCb();
}
sysTickCtrl = SysTick->CTRL & (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
ICSR |= (1 << 25);
NVIC_ClearPendingIRQ(RTC_FR_IRQn);
freeRunningRtcPriority = NVIC_GetPriority(RTC_FR_IRQn);
NVIC_SetPriority(RTC_FR_IRQn,0);
POWER_EnterPowerDown(0);
NVIC_SetPriority(RTC_FR_IRQn,freeRunningRtcPriority);
if(gpfPWR_LowPowerExitCb != NULL)
{
gpfPWR_LowPowerExitCb();
}
SysTick->CTRL |= sysTickCtrl;
PWRLib_UpdateWakeupReason();
}
__enable_irq();
#else
PWRLib_ClearWakeupReason();
#endif
}
#endif
void PWR_SetDeepSleepTimeInMs(uint32_t deepSleepTimeMs)
{
#if (cPWR_UsePowerDownMode)
if(deepSleepTimeMs == 0)
{
return;
}
mPWR_DeepSleepTimeMs = deepSleepTimeMs;
mPWR_DeepSleepTimeUpdated = TRUE;
#else
(void) deepSleepTimeMs;
#endif
}
Add/replace the following function definitions at the end of the file
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;
}
void PWR_ResetTotalSleepDuration(void)
{
OSA_InterruptDisable();
mLpmTotalSleepDuration = RTC_GetFreeRunningCount(RTC);
OSA_InterruptEnable();
}
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;
}
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);
}
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
uint32_t PWR_GetTotalSleepDurationMS(void);
void PWR_ResetTotalSleepDuration(void);
#ifdef __cplusplus
}
#endif
#endif
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"
#define portMAX_32_BIT_NUMBER (0xffffffffUL)
#define portRTC_CLK_HZ (0x8000UL)
#define portMISSED_COUNTS_FACTOR (45UL)
#if configUSE_TICKLESS_IDLE == 1
static uint32_t ulTimerCountsForOneTick;
#endif
void vPortSetupTimerInterrupt(void);
#ifdef __cplusplus
}
#endif
#endif
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
#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)
if (Ble_Initialize(App_GenericCallback) != gBleSuccess_c)
{
panic(0,0,0,0);
return;
}
#endif
#if (cPWR_UsePowerDownMode)
PWR_ChangeDeepSleepMode(3);
#endif
}
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.
#if (cPWR_UsePowerDownMode || gAppUseNvm_d)
#if (mAppIdleHook_c)
#define AppIdle_TaskInit()
#define App_Idle_Task()
#else
#if (!configUSE_TICKLESS_IDLE)
static osaStatus_t AppIdle_TaskInit(void);
static void App_Idle_Task(osaTaskParam_t argument);
#endif
#endif
#endif
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
#if (!mAppIdleHook_c)
OSA_TASK_DEFINE( App_Idle_Task, gAppIdleTaskPriority_c, 1, gAppIdleTaskStackSize_c, FALSE );
osaTaskId_t gAppIdleTaskId = 0;
#endif
#endif
#if !gUseHciTransportDownward_d
pfBLE_SignalFromISR = BLE_SignalFromISRCallback;
#endif
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
#if (!mAppIdleHook_c)
AppIdle_TaskInit();
#endif
#endif
#if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE)
static void App_Idle(void)
{
PWRLib_WakeupReason_t wakeupReason;
if( PWR_CheckIfDeviceCanGoToSleep() )
{
wakeupReason = PWR_EnterLowPower();
#if gFSCI_IncludeLpmCommands_c
FSCI_SendWakeUpIndication();
#endif
#if gKBD_KeysCount_c > 0
if(wakeupReason.Bits.FromKeyBoard)
{
KBD_SwitchPressedOnWakeUp();
PWR_DisallowDeviceToSleep();
}
#endif
}
else
{
PWR_EnterSleep();
}
}
#endif
#if (mAppIdleHook_c)
void vApplicationIdleHook(void)
{
#if (gAppUseNvm_d)
NvIdle();
#endif
#if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE)
App_Idle();
#endif
}
#else
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
static void App_Idle_Task(osaTaskParam_t argument)
{
while(1)
{
#if gAppUseNvm_d
NvIdle();
#endif
#if (cPWR_UsePowerDownMode)
App_Idle();
#endif
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.
#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;
#if (cPWR_UsePowerDownMode)
PWRLib_WakeupReason_t wakeupReason;
tmrMgrExpiryTimeMs = TMR_GetFirstExpireTime(gTmrAllTypes_c);
if(time_ms<tmrMgrExpiryTimeMs){
PWR_SetDeepSleepTimeInMs(time_ms);
}
PWR_ResetTotalSleepDuration();
if( PWR_CheckIfDeviceCanGoToSleep() )
{
wakeupReason = PWR_EnterLowPower();
xExpectedIdleTime = PWR_GetTotalSleepDurationMS() / portTICK_PERIOD_MS;
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
if(wakeupReason.Bits.FromKeyBoard)
{
KBD_SwitchPressedOnWakeUp();
PWR_DisallowDeviceToSleep();
}
#endif
}
else
{
PWR_EnterSleep();
}
#endif
}
#endif
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
#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);
OSA_TASK_DEFINE(vfnTaskLedBlinkTest, 1, 1, 500, FALSE );
osaTaskId_t gAppTestTask1Id = 0;
Create the new task inside the BleApp_Init function
void BleApp_Init(void)
{
PWR_AllowDeviceToSleep();
mPowerState = 0;
gAppTestTask1Id = OSA_TaskCreate(OSA_TASK(vfnTaskLedBlinkTest), NULL);
}
Finally, add the task function definition at the end of the file.
void vfnTaskLedBlinkTest(void* param)
{
uint16_t wTimeValue = 500;
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
