Gerardo Catena

MKW41Z GENFSK Connectivity Framework: implementing tickless FreeRTOS

Discussion created by Gerardo Catena on Nov 26, 2018

Hello,

the Connectivity Framework doesn't support the FreeRTOS tickless mode. I want to enable it remaining in the Connectivity Framework flow and using the PWR Lib to manage the power modes. In this case I want to use the power mode 7 which makes the device to enter is LLS3 and which will wake-up the microcontroller through the GPIO, GENFSK wake-up interrupt or LPTMR timeout. I want to share my implementation hoping it will be useful for others and in order to improve it or correct it if something of unmanaged is found. The implementation is basically a porting of this article Connectivity Software: Implement tickless mode in FreeRTOS   into the MKW41Z GENFSK connectivity example.

 

Framework File Involved

  • source/app_preinclude.h
  • source/connectivity_test.c
  • source/FreeRTOSConfig.h
  • framework/LowPower/Interface/PWR_Interface.h
  • framework/LowPower/Source/MKW41Z/PWR.c

 

 

Preparing the framework to enable the tickless mode.

For first we need to delete the fsl_tickless_lptmr.c file from the freertos folder. This is done to avoid conflicts with the implementation of the vPortSuppressTicksAndSleep function: we need to implement it using the connectivity framework PWR Lib. This function will be implemented in the connectivity_test.c and will be automatically called by FreeRTOS within the idle task.

 

Now we introduce a way to switch on/off the tickless mode acting on the app_preinclude.h file.

Edit app_preinclude.h  (source folder) and add the following define to switch on/off the tickless mode:

 

#define cPWR_TicklessFreeRtos_Enable 1

This macro will be visible in all the framework without include extra files.

 

To enable the tickless mode we need to edit FreeRTOSConfig.h and modify the line

#define configUSE_TICKLESS_IDLE 0

in this way:

 

#define configUSE_PREEMPTION 1
#define configUSE_TICKLESS_IDLE cPWR_TicklessFreeRtos_Enable //<---enable tickless
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ ((TickType_t)100) //<--- FIX: not too fast
#define configMAX_PRIORITIES (18)
#define configMINIMAL_STACK_SIZE ((unsigned short)120) //<--FIX: give enough space to FreeRTOS IDLE task

Preparing the PWR Lib

For first we need to add two files: PWR_Tickless.c and PWR_Tickless.h within the LowPower/Source/MKW41Z folder.

The PWR_Tickless.c has a simple function which returns the value of the systick based on the clock and the tick rate configured in FreeRTOSConfig.h. Here the content of the PWR_Tickless.c file:

 

#include "PWR_Tickless.h"
#include "FreeRTOS.h"
#include "Task.h"
#include "fsl_tickless_generic.h"

extern uint32_t SystemCoreClock;

uint32_t PWR_Tickless_GetCountForOneTick(void){

return ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
}

 

The related PWR_Tickless.h header file will be:

 

#include "EmbeddedTypes.h"

uint32_t PWR_Tickless_GetCountForOneTick();

Now edit thePWR_Interface.h and add the following variable declaration:

 

#if (cPWR_UsePowerDownMode && cPWR_TicklessFreeRtos_Enable)
extern uint32_t ulTimerCountsForOneTick;
#endif

The next step is to modify the PWR.c file. All the parts introduced to integrate the tickless mode are surrounded by  checks like this:

#if (cPWR_UsePowerDownMode && cPWR_TicklessFreeRtos_Enable)
/*CODE*/
#endif

 

For first we need to include the PWR_Tickelss.h header file in PWR.c:

 

#if (cPWR_UsePowerDownMode && cPWR_TicklessFreeRtos_Enable)
#include "PWR_Tickless.h"
#endif

Now we need to declare this variable:

 

#if (cPWR_UsePowerDownMode && cPWR_TicklessFreeRtos_Enable)
uint32_t ulTimerCountsForOneTick;
#endif

 

The last step is to modify the PWR_HandleDeepSleepMode_7 to support the suppression of the systick counter and interrupt and restore it after the wake-up. All parts involved into the tickless implementation are surrounded by checks like this:

 

#if(cPWR_TicklessFreeRtos_Enable)
/*CODE*/
#endif

 

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

#if (cPWR_UsePowerDownMode && cPWR_EnableDeepSleepMode_7)

static void PWR_HandleDeepSleepMode_7(void)
{
#if (cPWR_GENFSK_LL_Enable)
#if (gTMR_EnableLowPowerTimers_d)
uint32_t notCountedTicksBeforeSleep= 0;
#endif
uint8_t clkMode;
uint32_t lptmrTicks;
uint32_t lptmrFreq;
uint32_t sleepTimeMs;
bool_t enterLowPower = TRUE;
#if(cPWR_TicklessFreeRtos_Enable)
uint32_t sysTickCtrl;
__disable_irq();
#endif
PWRLib_MCU_WakeupReason.AllBits = 0;

if(enterLowPower && (READ_REGISTER_FIELD(RSIM->DSM_CONTROL,RSIM_DSM_CONTROL_GEN_SLEEP_REQUEST) == 0))
{

enterLowPower &= PWR_HandleGenfskDsmEnter();
}

if(enterLowPower)
{
uint32_t rfOscEn;

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

#if (gTMR_EnableLowPowerTimers_d) && (cPWR_CheckLowPowerTimers)
/* Get the expire time of the first programmed Low Power Timer */
sleepTimeMs = TMR_GetFirstExpireTime(gTmrLowPowerTimer_c);

if(mPWR_DeepSleepTimeMs < sleepTimeMs)
#endif /* (gTMR_EnableLowPowerTimers_d) && (cPWR_CheckLowPowerTimers) */
{
sleepTimeMs = mPWR_DeepSleepTimeMs;
}

PWRLib_LPTMR_GetTimeSettings(sleepTimeMs ,&clkMode ,&lptmrTicks, &lptmrFreq);
#if ( cPWR_TicklessFreeRtos_Enable)
/*Get the count for one tick value*/
ulTimerCountsForOneTick=PWR_Tickless_GetCountForOneTick();
#endif
PWRLib_LPTMR_ClockStart(clkMode,lptmrTicks);

#if (gTMR_EnableLowPowerTimers_d)
/* If more low power timers are running, stop the hardware timer
* and save the spend time in ticks that wasn't counted.
*/

notCountedTicksBeforeSleep = TMR_NotCountedTicksBeforeSleep();
#endif



BOARD_BLPEtoBLPI();
rfOscEn = RSIM_CONTROL_READ_FIELD(RSIM_CONTROL_RF_OSC_EN);
RSIM->CONTROL = RSIM_CONTROL_MODIFY_FIELD(RSIM_CONTROL_RF_OSC_EN, 0);
#if ( cPWR_TicklessFreeRtos_Enable)
/*DISABLE SYSTICK*/
sysTickCtrl = SysTick->CTRL & (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
SCB->ICSR|=(1<<25);

#endif
PWRLib_MCU_Enter_LLS3();

RSIM->CONTROL = RSIM_CONTROL_MODIFY_FIELD(RSIM_CONTROL_RF_OSC_EN, rfOscEn);

BOARD_BLPItoBLPE();

if(gpfPWR_LowPowerExitCb != NULL)
{
gpfPWR_LowPowerExitCb();
}
#if ( cPWR_TicklessFreeRtos_Enable)
/*RESTORE SISTICK*/
SysTick->CTRL |= sysTickCtrl;
#endif
PWRLib_LLWU_UpdateWakeupReason();
PWR_HandleGenfskDsmExit();
PWRLib_LPTMR_ClockStop();


/* Sync. the low power timers */
#if (gTMR_EnableLowPowerTimers_d)
{
uint64_t timerTicks = PWRLib_LPTMR_ClockCheck();
mLpmTotalSleepDuration = timerTicks * 1000000 / lptmrFreq;
timerTicks = (timerTicks * TMR_GetTimerFreq()) / lptmrFreq;
timerTicks += notCountedTicksBeforeSleep;
TMR_SyncLpmTimers((uint32_t)timerTicks);
}
#endif
}
#if (cPWR_TicklessFreeRtos_Enable)
__enable_irq();
#endif
#else
PWRLib_MCU_WakeupReason.AllBits = 0;
#endif
}
#endif

 

Implementing  vPortSuppressTicksAndSleep

This function will be called by FreeRTOS to put the device in low power. Before to implement it we need to disable the default idle function used by the framework. The following changes are done into the connectivity_test.c file.

 

We need to include the FreeRTOS header to make some of its functions visible inside the connectivity_test.c file:

 

#ifdef FSL_RTOS_FREE_RTOS

#include "FreeRTOSConfig.h"
#if (cPWR_UsePowerDownMode && cPWR_TicklessFreeRtos_Enable)
#include "FreeRTOS.h"
#include "task.h"
#include "fsl_tickless_generic.h"
#endif
#endif

 

Now we need to disable the AppIdle_Task if the tickless mode is enabled but this part is the same explained in this article Connectivity Software: Implement tickless mode in FreeRTOS and is unuseful repeat it again:

 

Now we can add the implementation of the vPortSuppressTicksAndSleep function:

 

#if (cPWR_UsePowerDownMode && cPWR_TicklessFreeRtos_Enable)
void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{


#if (cPWR_UsePowerDownMode)
PWRLib_WakeupReason_t wakeupReason;


PWR_ResetTotalSleepDuration();

if( PWR_CheckIfDeviceCanGoToSleep() )
{

/* Enter Low Power */
wakeupReason = PWR_EnterLowPower();

xExpectedIdleTime= PWR_GetTotalSleepDurationMS()/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) |= portNVIC_SYSTICK_ENABLE_BIT;
vTaskStepTick( xExpectedIdleTime );
*(portNVIC_SYSTICK_LOAD) = ulTimerCountsForOneTick - 1UL;
portEXIT_CRITICAL();

#if gKBD_KeysCount_c > 0
/* Woke up on Keyboard Press */
if(wakeupReason.Bits.FromKeyBoard)
{
KBD_SwitchPressedOnWakeUp();
PWR_DisallowDeviceToSleep();
}

#endif
}else{
PWR_EnterSleep();

}
#endif

}
#endif

A simple test

 

To do an example we can modify the App_Thread to alternate between Rx event and DSM Event forever and logging the time which the device spent in deep sleep. The DSM timeout is 50ms, whilst the LPTMR timeout is 30s. The board used is the FRDMKW41Z configured in buck mode.

Declare a buffer to satisfy the GENFSK_StartRx signature, a variable in which store the amount of time spent in deep sleep and a list of states in which the app will be. Modify the main_task as follow :

 

static uint32_t timeAsleep;

typedef enum{
idle_c,
rx_c,
dsm_c,
dsm_over_c,
lptmr_c,
lptmr_over_c,
button_pressed_c //FIX: log when a button is pressed


}apk_status_t;

static apk_status_t apk_st;


static uint8_t rx_buffer[100];

void main_task(uint32_t param)
{
if (!platformInitialized)
{
uint8_t pseudoRNGSeed[20] = {0};

platformInitialized = 1;

hardware_init();

/* Framework init */
MEM_Init();
TMR_Init();
//initialize Serial Manager
SerialManager_Init();
LED_Init();
SecLib_Init();

RNG_Init();
RNG_GetRandomNo((uint32_t*)(&(pseudoRNGSeed[0])));
RNG_GetRandomNo((uint32_t*)(&(pseudoRNGSeed[4])));
RNG_GetRandomNo((uint32_t*)(&(pseudoRNGSeed[8])));
RNG_GetRandomNo((uint32_t*)(&(pseudoRNGSeed[12])));
RNG_GetRandomNo((uint32_t*)(&(pseudoRNGSeed[16])));
RNG_SetPseudoRandomNoSeed(pseudoRNGSeed);

#if gKeyBoardSupported_d && (gKBD_KeysCount_c > 0)
KBD_Init(App_KeyboardCallBack);
#endif

GENFSK_Init();

/* GENFSK LL Init with default register config */
GENFSK_AllocInstance(&mAppGenfskId, NULL, NULL, NULL);

/*create app thread event*/
mAppThreadEvt = OSA_EventCreate(TRUE);

#if (cPWR_UsePowerDownMode && !cPWR_TicklessFreeRtos_Enable)
#if (!mAppIdleHook_c)
AppIdle_TaskInit();
#endif
#endif
#if (cPWR_UsePowerDownMode)
PWR_Init();
PWR_DisallowDeviceToSleep();
#else
/*start serial flashing using all LEDs*/
LED_StartSerialFlash(LED1);
#endif

/*initialize the application interface id*/
Serial_InitInterface(&mAppSerId,
APP_SERIAL_INTERFACE_TYPE,
APP_SERIAL_INTERFACE_INSTANCE);
/*set baudrate to 115200*/
Serial_SetBaudRate(mAppSerId,
APP_SERIAL_INTERFACE_SPEED);
/*set Serial Manager receive callback*/
Serial_SetRxCallBack(mAppSerId, App_SerialCallback, NULL);

/*allocate a timer*/
mAppTmrId = TMR_AllocateTimer();
/*Prints the Welcome screens in the terminal*/
PrintMenu(cu8Logo, mAppSerId);

CT_GenFskInit(NULL, NULL); //called just to init the GENFSK module
apk_st=idle_c;
GENFSK_RegisterCallbacks(mAppGenfskId, App_GenFskReceiveCallback,App_GenFskEventNotificationCallback); //register the call back
#if (cPWR_UsePowerDownMode)
PWR_AllowDeviceToSleep();
#endif

}

/* Call application task */
App_Thread( param );
}

 

Modify the App_Thread as follow:

 

void App_Thread (uint32_t param)
{
osaEventFlags_t mAppThreadEvtFlags = 0;
timeAsleep=0;
while(1)
{
(void)OSA_EventWait(mAppThreadEvt, gCtEvtEventsAll_c, FALSE, osaWaitForever_c ,&mAppThreadEvtFlags);
if(mAppThreadEvtFlags)
{
if(mAppThreadEvtFlags&gCtEvtKBD_c)
{
Serial_Print(mAppSerId,"\r\nButton Pressed! Wake-up after:",gAllowToBlock_d);
Serial_PrintDec(mAppSerId, timeAsleep);
Serial_Print(mAppSerId,"ms\r\n",gAllowToBlock_d);
if(mAppPbData == PB1_PRESSED){

Serial_Print(mAppSerId,"\r\nRX!!!",gAllowToBlock_d);
apk_st=rx_c;
GENFSK_StartRx(mAppGenfskId, rx_buffer, 30, 0, 5000);
}else
{
apk_st=lptmr_c;
Serial_Print(mAppSerId,"Sleep Until LPTMR expires...ZZzzz\r\n",gAllowToBlock_d);
PWR_GENFSK_EnterDSM(cPWR_DeepSleepDurationMs+10);
PWR_AllowDeviceToSleep();
}

}else if(mAppThreadEvtFlags&gCtEvtSeqTimeout_c){
apk_st=dsm_c;
Serial_Print(mAppSerId,"Sleep ZZzzz\r\n",gAllowToBlock_d);
PWR_GENFSK_EnterDSM(50);
PWR_AllowDeviceToSleep();

}else if(mAppThreadEvtFlags&gCtEvtWakeUp_c){
apk_st=rx_c;
Serial_Print(mAppSerId,"\r\nGENFSK Wake-up after: ",gAllowToBlock_d);
Serial_PrintDec(mAppSerId,timeAsleep);
Serial_Print(mAppSerId,"ms\r\n",gAllowToBlock_d);
Serial_Print(mAppSerId,"Rx!!\r\n",gAllowToBlock_d);
timeAsleep=0;
GENFSK_StartRx(mAppGenfskId, rx_buffer, 30, 0, 5000);

}else if(mAppThreadEvtFlags&gCtEvtTimerExpired_c){

apk_st=rx_c;
Serial_Print(mAppSerId,"\r\nLPTMR Timeout after: ",gAllowToBlock_d);
Serial_PrintDec(mAppSerId,timeAsleep);
Serial_Print(mAppSerId,"ms\r\n",gAllowToBlock_d);
timeAsleep=0;


}
}
if(gUseRtos_c == 0) /*if bare-metal break while*/
{
break;
}
}
}

 

Modify the vPortSuppressTicksAndSleep as follow to log the timeAsleep variable.

void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{


#if (cPWR_UsePowerDownMode)
PWRLib_WakeupReason_t wakeupReason;


PWR_ResetTotalSleepDuration();

if( PWR_CheckIfDeviceCanGoToSleep() )
{


/* Enter Low Power */
wakeupReason = PWR_EnterLowPower();
timeAsleep=PWR_GetTotalSleepDurationMS(); //FIX: Log the time spent asleep
xExpectedIdleTime= PWR_GetTotalSleepDurationMS()/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) |= portNVIC_SYSTICK_ENABLE_BIT;
vTaskStepTick( xExpectedIdleTime );
*(portNVIC_SYSTICK_LOAD) = ulTimerCountsForOneTick - 1UL;
portEXIT_CRITICAL();

#if gKBD_KeysCount_c > 0
/* Woke up on Keyboard Press */
if(wakeupReason.Bits.FromKeyBoard)
{
apk_st=button_pressed_c; //FIX: to avoid duplicated disallow
KBD_SwitchPressedOnWakeUp();
PWR_DisallowDeviceToSleep();
}


#endif
}else{
PWR_EnterSleep();

}
#endif

}
#endif

 

Modify the App_GenFskEventNotificationCallback as follow:

static void App_GenFskEventNotificationCallback(genfskEvent_t event,
genfskEventStatus_t eventStatus)
{
if(event & gGenfskTxEvent)
{
mAppGenfskStatus = eventStatus;
/*send event done*/
OSA_EventSet(mAppThreadEvt, gCtEvtTxDone_c);
}
if(event & gGenfskRxEvent)
{
if(eventStatus == gGenfskTimeout)
{
OSA_EventSet(mAppThreadEvt, gCtEvtSeqTimeout_c);
}
else
{
OSA_EventSet(mAppThreadEvt, gCtEvtRxFailed_c);
}
}
#if (cPWR_UsePowerDownMode)
if(event & gGenfskWakeEvent )
{


if(apk_st==dsm_c){
PWR_DisallowDeviceToSleep(); //FIX: do not disallow if a button is pressed
apk_st=dsm_over_c;
OSA_EventSet(mAppThreadEvt, gCtEvtWakeUp_c);
} else if(apk_st==lptmr_c){
PWR_DisallowDeviceToSleep(); //FIX: do not disallow if a button is pressed
apk_st=lptmr_over_c;
OSA_EventSet(mAppThreadEvt, gCtEvtTimerExpired_c);
}
}
#endif /* (cPWR_UsePowerDownMode) */
/*not handling other events in this application*/
}

 

Connect to the terminal and start the application pressing the Button 1 (SW4). We will see this output, telling us that the wake-up happens after the DSM timeout specified into the code:

 

Wake-up after 50ms

 

 

If the Button 2 (SW3) is pressed, a DSM timeout greater than the LPTMR will be set, and then the device will be woken-up through the LPTMR.

 

Wake-up when LPTMR expires

 

Now Press again Button 2, wait a couple of seconds, and then press the Button 1. You will see the LPTR timeout interrupted by the button pressure:

 

Wake-up through a button pressure

 

Hoping that this is the correct way to implement it, and that it will be useful for others.

 

G.

Outcomes