Summary

This article walks you through the steps to add DeepSleep Tickless mode to your PSoC 6 FreeRTOS project.  It includes an example project that demonstrates the use of the Cypress HAL and DeepSleep to acheive power savings.

This article is part of the "PSoC 6 Low Power Techniques" Series which covers a range of tools you have to lower the power of your system.  The following articles are (or will be) available:

Articles
PSoC 6 Low Power
PSoC 6 & Using the MCWDT as a Deep Sleep Timer
PSoC 6 Deep Sleep Wakeup Time
PSoC 6 & FreeRTOS Tickless
Managing the PSoC 6 Clock Frequency
Using the PSoC 6 LDO and SIMO Buck Regulators for Low Power
Using the PSoC 6 Always on Backup Domain
PSoC 6 Turning off block of RAM

The following resources are available

The Story

I recently taught the Modus Toolbox-101 class to a group of Cypress Field Applications Engineers.  In that class we have a chapter called “PSoC 6 Low Power” which shows you how to use the Low Power Assistant to improve your system power.  This chapter was built around the ARM MBED OS Software Development Kit inside of Modus Toolbox.  Every time I show people the low power assistant I am blown away by how easy it is to do.  Cypress is currently working on a major upgrade to our FreeRTOS implementation, but I thought that in advance of that I would take some of the steps myself.  Specifically, using FreeRTOS Tickless Mode.

What is Tickless mode?  In order to understand this you should understand that FreeRTOS runs a scheduler – and its not just FreeRTOS but every RTOS.  The scheduler is responsible for pausing, starting, stopping etc. the tasks based on your configuration (i.e. priorities).  The scheduler task needs to run on a regular basis, like every 1ms, or the system will fall apart.  This is done for you automatically by hooking up a system tick interrupt which interrupts whatever user task is happening and running the scheduler.

The next thing that you need to know is that RTOSs have a task called “idle” which runs when all of the real tasks are blocked either by a delay, semaphore, queue etc.  All this idle task does is burn CPU cycles until the next SysTick interrupt.  The idle task is implemented with something like while(1);  This isn’t really very constructive because the CPU is active, the clocks are active, etc. and this whole thing typically burns mA’s of current (at least on a PSoC 6).

So, what should you do?  The answer is that instead of “idling” you should put the CPU to Sleep, preferably DeepSleep.  But where should you do that?  The perfect place is in the idle task.  If you setup the idle task to just put the CPU to Sleep/DeepSleep then you save power.

But if the CPU is asleep how do you wakeup and when?  There are two answers to those questions:

  1. If nothing is pending, there are no timers running, you should go to sleep and wait for an interrupt (from an MCU peripheral … like a pin)
  2. If you know that everything is blocked, but a task is supposed to wakeup at some specified time, then you should go to sleep until that time.

But how do you set a wakeup time?  Simple.  Use the PSoC 6 MCWDT as a DeepSleep Low Power Timer.

In this article I will show you how to setup the FreeRTOS configuration files plus the HAL lptimer to achieve goal.  The sections are:

  • PSoC Configurator
  • FreeRTOSConfig.h
  • LowPower.c
  • A Demo Program

PSoC 6 Configurator

If you look at the PSoC 6 device configurator you will find a section called “RTOS”.  This was put by the Low Power Assistant team to enable stuff for MBEDOS.  However, it can/could/will be used for FreeRTOS as well.   All this configuration does is create a section of #defines which can then be used to setup your low power code.

The first parameter is “System Idle Power Mode” which can be set as “Active, CPU Sleep or System Deep Sleep”.

The second parameter is “Deep Sleep Latency” which is an integer which can set the minimum sleep time.  In other words if you are going to sleep less than this number, don’t bother.

When you configure these parameters, it will create the following #defines in the cycfg_system.h

The first three are used because the c-preprocessor can only evaluate integer expressions.  These defines enable you to do compares with the power mode in your code.  The “CY_CFG_PWR_SYS_IDLE_MODE” it set to whatever you specified in the configurator.  Here is a clip from the cycfg_system.h

#define CY_CFG_PWR_MODE_ACTIVE 0x04UL
#define CY_CFG_PWR_MODE_SLEEP 0x08UL
#define CY_CFG_PWR_MODE_DEEPSLEEP 0x10UL
#define CY_CFG_PWR_SYS_IDLE_MODE CY_CFG_PWR_MODE_DEEPSLEEP
#define CY_CFG_PWR_DEEPSLEEP_LATENCY 8UL

FreeRTOSConfig.h

So now what?  Lets use those defines to get FreeRTOS going.  The first thing you need to do is tell FreeRTOS that you want to use TICKLESS IDLE by setting the #define configUSE_TICKLESS_IDLE.  There are three possible settings:

  • 0 or undefined – no tickless idle
  • 1 – use the built-in port (which we did’t supply)
  • 2 – use a application developer supplied low power configuration (this is what we are going to do)

I add this block of code to my FreeRTOSConfig.h.  It says:

63: If the user has selected Sleep or DeepSleep

64: Give me access to the vApplicationSleep function (which I will write)

65: Tell FreeRTOS to call the vApplicationSleep function in the idle task

66: And tell FreeRTOS that I want to use MY OWN implementation of Tickless

69-71: If the user has specified a minimum time, then tell FreeRTOS to use that minimum.

#if CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_SLEEP || CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_DEEPSLEEP
extern void vApplicationSleep( uint32_t xExpectedIdleTime );
#define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )
#define configUSE_TICKLESS_IDLE  2
#endif

#if CY_CFG_PWR_DEEPSLEEP_LATENCY>0
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP CY_CFG_PWR_DEEPSLEEP_LATENCY
#endif

LowPower.c

Now I need to implement the function “vApplicationSleep” which will look at the state of affairs and “do the needful”.  I decided to put all of the low power stuff a file called “lowPower.c”.   It goes like this:

24-25: Create a low power timer (if I haven’t already)

27-28: Disable the SysTick and disable the ARM global interrupts

31: Ask the RTOS if we REALLY want to go to sleep, which could have changed if there was an interrupt or a task became active before the interrupts were disabled

32: Reset the timer to 0 (so that we can get a maximum delay)

34: If we are really going to go to sleep (in other words we shouldn’t abort)

Remember from above there are two Sleep/DeepSleep cases:

  1. There are no tasks waiting
  2. There is a task waiting

36-40: If there is a task waiting then setup the timer to wakeup in that amount of time

42: Remember when you went to sleep, so you can fix the system timer after you wake backup

43-46: Sleep or DeepSleep (depending on what the user said in the configurator)

48: Or bail if the developer didn’t turn on this code

51: When you wake up find out what time it is

54: Fix the tick count in the RTOS based on how long you slept

56: Disable the lptimer interrupt

61-62: Enable the interrupts and turn the SysTick back on.

#include "FreeRTOS.h"
#include "task.h"
#include "cyhal.h"
#include "cybsp.h"

static inline uint32_t msToTicks(uint32_t ms)
{
	uint64_t val = (CY_SYSCLK_WCO_FREQ*(uint64_t)ms/1000);
	val = (val>UINT32_MAX)?UINT32_MAX:val;
	return (uint32_t)val;
}

static inline uint32_t ticksToMs(uint32_t ticks)
{
	return (ticks * 1000) / CY_SYSCLK_WCO_FREQ;
}

/* Define the function that is called by portSUPPRESS_TICKS_AND_SLEEP(). */
void vApplicationSleep( TickType_t xExpectedIdleTime )
{
	static cyhal_lptimer_t myTimer={0};
	unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;

	if(myTimer.base == 0)
		cyhal_lptimer_init(&myTimer);

    Cy_SysTick_Disable();
    uint8_t interruptState = Cy_SysLib_EnterCriticalSection();

    /* Ensure it is still ok to enter the sleep mode. */
    eSleepModeStatus eSleepStatus = eTaskConfirmSleepModeStatus();
	cyhal_lptimer_reload(&myTimer);

    if( eSleepStatus != eAbortSleep )
    {
    	if( eSleepStatus != eNoTasksWaitingTimeout )
    	{
    		cyhal_lptimer_set_delay	(&myTimer,msToTicks(xExpectedIdleTime));
    	    cyhal_lptimer_enable_event (&myTimer, CYHAL_LPTIMER_COMPARE_MATCH, 7, true);
    	}
    	/* Enter the low power state. */
        ulLowPowerTimeBeforeSleep = cyhal_lptimer_read(&myTimer);
#if CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_DEEPSLEEP
    	cyhal_system_deepsleep();
#elif CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_SLEEP
    	cyhal_system_sleep();
#else
    	goto exitPoint;
#endif
    	// How long did it sleep
    	ulLowPowerTimeAfterSleep = cyhal_lptimer_read(&myTimer);

    	/* Correct the kernels tick count to account for the time the microcontroller spent in its low power state. */
    	vTaskStepTick( ticksToMs(ulLowPowerTimeAfterSleep - ulLowPowerTimeBeforeSleep));
    }
    cyhal_lptimer_enable_event (&myTimer, CYHAL_LPTIMER_COMPARE_MATCH, 4, false);

#if !(CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_DEEPSLEEP || CY_CFG_PWR_SYS_IDLE_MODE == CY_CFG_PWR_MODE_SLEEP)
exitPoint:
#endif
    Cy_SysLib_ExitCriticalSection(interruptState);
    Cy_SysTick_Enable();
}

A Demo Program

The last thing to wrap this up is to build an example demo program.  This one will do two things

  1. Blink the LED 3 times using an RTOS Delay
  2. When the USER button is pressed it will wakeup and do it again.

This will demonstrate that you can do both DeepSleep modes.

  1. DeepSleep between LED toggles (i.e. while it it waiting for the vTaskDelay)
  2. DeepSleep indefinitely until the Switch is pressed

Start this example with a main that sets up the Switch, then starts the blinking task

int main(void)
{
    uxTopUsedPriority = configMAX_PRIORITIES - 1 ; // enable OpenOCD Thread Debugging

    cybsp_init() ;

    cyhal_gpio_init(CYBSP_SW2,CYHAL_GPIO_DIR_INPUT,CYHAL_GPIO_DRIVE_PULLUP,1);
    cyhal_gpio_enable_event(CYBSP_SW2, CYHAL_GPIO_IRQ_FALL, 7, true);
    cyhal_gpio_register_callback(CYBSP_SW2, buttonEvent, 0);
     __enable_irq();

    // Stack size in WORDs Idle task = priority 0
    xTaskCreate(blinkTask, "blinkTask", configMINIMAL_STACK_SIZE,0 /* args */ ,2 /* priority */, &blinkTaskHandle);
    vTaskStartScheduler();
}

The event handler sets a semaphore when the button is pressed.

void buttonEvent(void *callback_arg, cyhal_gpio_event_t event)
{
	BaseType_t xHigherPriorityTaskWoken;
	xHigherPriorityTaskWoken = pdFALSE;
	xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

The blinking LED task, initializes the LED and the sempahore.

Then blinks until the count is 0, waits for the semaphore, then resets the count back to 6 (aka 3 blinks)

void blinkTask(void *arg)
{
    cyhal_gpio_init(CYBSP_USER_LED,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,0);

    int count=BLINK_COUNT;
    xSemaphore = xSemaphoreCreateBinary(  );
    for(;;)
    {
    	while(count--)
    	{
    		cyhal_gpio_toggle(CYBSP_USER_LED);
    		vTaskDelay(1000);
    	}
    	xSemaphoreTake( xSemaphore,0xFFFFFFFF );
    	count = BLINK_COUNT;
    }
}

 

Recommended Posts

No comment yet, add your voice below!


Add a Comment

Your email address will not be published. Required fields are marked *