IoT AdvantEdge AnyCloud Webinar

Summary

This Article contains the exact flow and screen shots that I will follow for the IoT AdvantEdge Webinar.  There will be 7 sections:

  1. Modus Toolbox 2.1 & AnyCloud SDK
  2. Basic Eclipse Flow
  3. Command Line Flow
  4. Visual Studio Code Flow
  5. Connectivity Core Middleware
  6. Low Power Assistant
  7. AnyCloud Libraries

Modus Toolbox 2.1 & AnyCloud SDK

What is Modus Toolbox 2.1 and the AnyCloud SDK?

  • Integrated platform for compute and connectivity
  • Windows, Linux and Mac
  • IAR, Eclipse, ARM MDK, Visual Studio Code, MBED Studio and Command line
  • We have complete integration at the company level for both IAR and the ARM MDK – Cypress is native in their tools
  • Amazon FreeRTOS, FreeRTOS, MBED OS
  • The entire PSoC 6 family
  • The WiFi Bluetooth Combo’s 4343 and 43012
  • All the Cypress 20xxx Family Bluetooth Chips (a huge step forward in making WICED and PSoC 6 look the same)
  • Low Power Assistant (to build combination solutions with PSoC6 and WiFI/BT)
  • A generic new project creator
  • All of our configurators run on all of the platforms
  • A bunch of new code examples.
  • Offline support (so you don’t have to be connected to the internet)
  • Major updates to the documentation
  • A bunch of improvements in testing
  • The AnyCloud SDK which contains Wireless Connection Manager, LWIP, MBEDTLS, SecureSockets, Low Power Assistant, Secure OTA ToolKit, New Host Driver

Basic Flow

Startup the Modus Toolbox Elipse IDE…

Press “New Application” in the Quick Panel

Make a new project.  The development kit that I happen to be using today is the “CY8CPROTO-062-4343W”.  Press “Next”

Start with the Empty project starter, notice that it goes to the GitHub on the internet because we UNCOUPLED the IDE from the SDKs and BSP (but you can do offline mode).  It means we can make updates to libraries without having to change everything.  In addition you can extend Modus Toolbox.

You can also start from your own template (using the import button)

Pick the “Empty PSoC6 App” and press Create.  Look at the output window … What is it doing?  Simple, loading all of our code into your project as libraries.

Close and notice that it takes you back to Eclipse and imports the project.

Look at the dependencies directory… then in the libraries directory.  Notice that the libraries directory can get re-generated anytime by the files in the dependencies directory.

Run a build.

Press Program on the Quick Panel.  Notice that we support JLINK and KitProg out-of-the-box.

Run the library manager by clicking in the Quick Panel.  This is a utility to let you manipulate the libraries in your project.

The Library Manager gives you the ability to add a new BSP to the project and change to the new BSP.  Notice below that I switched to CY8CKIT-062S2-43012

You can also upgrade latest to the latest BSP or lock to a specific version

Notice that it added a file TARGET_CYKIT-062S2-43012.lib to the project.  And downloaded that library into your libs directory.

Open the Makefile.  Notice the target is now the CY8CKIT-062S2-43012.  You can have multiple BSPs active in your project, but only one active at a time.  The inactive BSPs will be ignored by the build process.

And when you build it you will get a new Hex & Elf for that target.

Command Line

Start the command line interface, just a terminal in Mac or Linux.  On Windows you can run the program “<user>/ModusToolbox/tools_2.1/modus-shell/Cygwin.bat”  Change directory to your Workspace / Project folder.  Then run “make modlibs”

This starts up the library manager (the exact same one you used from inside of Eclipse.  This is a standalone GUI tool which runs on Mac, Linux and Windows.  Click on the Libraries tab, then add FreeRTOS

Look at the deps and libs directory

Now you can run make build

You might have noticed that there were some warning messages during the build.  This is because the FreeRTOSConfig.h we provide has a warning in it saying that you should customize it.

Lets fix that by moving the template into our project with “cp libs/freertos/Source/portable/FreeRTOSConfig.h .”  Then I will run emacs and fix the #warning line

When I run “make build” everything is better

Now I can run “fw-loader –device-list” (on your computer you can find it in the Modus Toolbox directory) and it will show me that I have the development kit attached to this computer

Then I can program with “make program”

Visual Studio Code

You can also use Visual Studio Code with your project.  To do this run “make vscode” which sets up the files needed to make Visual Studio Code work.  Then you can run Visual Studio Code with the command “code .”  or you can just open the directory from the Visual Studio Code file menu.

And in Visual Studio Code you can…

… build with “Run Build Task…”

Pick out the build that you want (in this case “Debug”)

The make build system will “do the needful”

Then you can actually Program the project and start the debugger with “Run -> Start Debugging”

Which will program the chip and start up the debugger.  Then it will halt at main.

Connectivity Core Middleware

Now run “make modlibs” so we can start to add connectivity to the project.


Notice that on the library tab you can see a bunch of “WiFi middleware libraries”. Choose “wifi-mw-core” and press apply

Notice that it adds lwip, mbedTLS, secure-sockets.  The lwip, mbedTLS come from GitHub.  We aren’t hosting them, but we do the configuration for you.

Look in mbed_tls_user_config.h

And lwip_opts.h

Go back to the library manager and add the WiFi Connection Manager. [edit: I also should have checked LPA here]

Find the doc directory for the connection manager directory.  Then open the file “api_reference.html”

Double click on it and open it up in the web browser. 

Low Power Assistant

The key to low power is to STAY ASLEEP, but when you wake up,  get after it!  The pairing of PSoC 6 & 43xxx is key to making super lower power projects.  We give you a tool called the “Low Power Assitant”.  To run it start up “make config”.  Then go to the “System->Power” tab.

Now click on the “CYW4…” tab.  Go to the “Power” section.  Then enable “wi-fi”

We give you a set of default filters which you can enable with “Add a Minimal Set of Keep Filters”

Look at the Preview tab and you will see a preview of the code that will be generated for you.

Hit save, now you can look at the actual generated code in your project.  It is just normal C-Code.

AnyCloud Libraries

Quit Visual Studio Code and go back to Eclipse.  This project has all of the stuff that you changed and is still a perfectly functional Eclipse project.

Now lets create a new project.  Click on “New Application” in the Quick Panel.

Choose the “CY8CPROTO-062-4343W” BSP

And pick the “AnyCloud MQTT Client” starter project.

After a flash you will get a message like this in your New Project Wizard window.  Click “Close”

And you will have the project

Notice that you now have some new libraries.  Specifically:

  • aws-iot-device-sdk-embeded-c
  • mqtt
  • secure-sockets

In order to make a connection to the Amazon Cloud you need to have some cryptography keys.  When you create “things” on Amazon you will be given the chance to download them.  I did this earlier, so I will just copy them into my project.

Now you can see the file in the project.  Notice that I also defined “AWS_BROKER” to be the DNS name of my Amazon WebService IoT MQTT broker.

Edit the “mqtt_client_config.h” to include your keys.  And to define the MQTT broker address. 

 

 

Get rid of the unused keys definitions:

Edit the WiFI SSID and Password in “wifi_config.h”

Finally program it.  Once it is programmed you can see the output on a serial terminal

And on the AWS Web Console I can subscribe to the topic.

And I will see the button press messages:

Keithley DAQ6510 Unboxing

Summary

A bunch of pictures of the unboxing of my new Keithley DAQ6510.  DAQ stands for Data Acquisition.  This box is essentially a 6.5 digit multimeter with a 20+ channel multiplexor attached.

The Story

My lab assistant, Nicholas, says that Unboxing is big in social media and that I really need to do an unboxing for IoT Expert.  Well here it is.  As I am sure you know, I have been working on a power supply design for a new IoT device.  As part of this I needed the ability to measure voltage and current from several different places and my trusty Fluke wasn’t going to be able to do that.

I bought this from my good friends at Mouser.  Here is the box.

When you open it here is what you see.

If I take everything out there is

  1. The meter
  2. Some test leads
  3. The manual
  4. a USB cable
  5. And a calibration certificate

On the back there is two places to plug in multiplexers + a LXI ethernet connection, power, USB, two BNC triggers, and a communication port.

It is always fun to tear off the film over the screen.

On the top there is a label with QR codes to documentation and software

When I turn it on, the system boots…

And then drops into DC voltage measurements.  Good new is that it measures 0

Unfortunately I very quickly ended up with the Blue Screen of Death (BSOD).

So I downloaded and installed the latest firmware (and it now seems to be stable).

When I turned on the current measurement with a PSoC 6 (in LP Active Mode)… I get numbers that make sense.

 

And this is cool… when I side swipe the screen it get a graph of the last 5ish minutes.

 

PSoC 6 & FreeRTOS Tickless

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

 

PSoC 6 Deep Sleep Wakeup Timer

Summary

In this article I build several different programs to measure the DeepSleep to Active wakeup time of the PSoC 6 which ranges from 15uS to 120uS.  I will discuss the data sheet max of 25uS and the useful limit of about 60uS.  I will include an analysis of the system issues that will influence your wakeup time.

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

As I was working on implementing the FreeRTOS tickless mode I noticed (incorrectly) that the wakeup time from DeepSleep was around 5ms.  When I saw this number I thought “wow, that is a long time”, then I looked at the data sheet and discovered that it was really “25 uS Guaranteed by design”.  Given that Cypress is very careful about our data sheet numbers I thought “wow… 25uS is a long way from 5ms.  Did we really make that bad an error in the data sheet?”

I decided to dig in to figure out what was really happening.  The real answer is that the wakeup time isn’t anywhere near 5ms. That turned out to be an IoT Expert Alan bug (shhhh don’t tell anyone), but it did turn into an interesting investigation of the PSoC 6 chip.

In order to measure the wakeup time I needed a way to measure between a wakeup trigger and the system being awake.  The best way seemed to use a pin to trigger the wakeup and pin to indicate the system being awake. Then measure using an oscilloscope.  In the PSoC 6, in order to wakeup the system you need to send an interrupt to the Wakeup Interrupt Controller, here is a picture from the TRM.  These interrupts also serve as interrupts for the ARM Cores.

The basic flow of all of these examples is:

  1. Enable interrupts on the input pin which is attached to SW2 aka P04 or Arduino D0
  2. Write a 0 to the P50/D0 output pin
  3. DeepSleep
  4. Write a 1 to the P50/D0 output pin
  5. Go back to 2

Here is a picture of my CY8CKIT-062-WiFi-BT development kit.  Notice that I soldered a wire to the Switch (SW2) which is connected to P04.  The green switch wire is barely attached because I didn’t want to delaminate the switch from the board.  The yellow wire is attached to P50/D0.

What follows is a discussion of 7 different configurations.  As much as possible I try to use the Cypress Hardware Abstraction Layer (HAL) but as I dig, I get down to writing registers directly.

  1. Basic Pin Event and HAL Write
  2. Register Custom ISR Instead of HAL ISR
  3. Disable ARM Interrupts (no ISR)
  4. Write the Output Pin Register Directly (no HAL)
  5. Try Different Clock Frequencies
  6. Modify the Cypress PDL Function Cy_SysPm_EnterDeepSleep
  7. Write the ARM DeepSleep Register Directly and Call __WIFI

Basic Pin Event and HAL Write

I started with this very simple example.  The steps are:

  • Use the HAL to enable two output pins (one attached to the LED) and one attached to the Oscilliscope.
  • Use the HAL to configure the Switch as an input and then enable interrupts on that switch.

Go into the main loop and:

  • Write the LED to On (aka 0) to indicate DeepSleep
  • Write the D0 to 0
  • DeepSleep
  • Write the D0 to 1
  • Write the LED to Off (to indicate Active)
  • Do a little delay… then do it all over again
int main(void)
{
    /* Initialize the device and board peripherals */
    cybsp_init();

    cyhal_gpio_init(CYBSP_D0,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,0);
    cyhal_gpio_init(CYBSP_SW2,CYHAL_GPIO_DIR_INPUT,CYHAL_GPIO_DRIVE_PULLUP,1);
    cyhal_gpio_init(CYBSP_USER_LED,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);

    cyhal_gpio_enable_event(CYBSP_SW2, CYHAL_GPIO_IRQ_FALL, 4, true);

	__enable_irq();

    while(1)
    {
    	cyhal_gpio_write(CYBSP_USER_LED,0);
    	cyhal_gpio_write(CYBSP_D0,0);
    	cyhal_system_deepsleep();
    	cyhal_gpio_write(CYBSP_D0,1);
    	cyhal_gpio_write(CYBSP_USER_LED,1);
    	CyDelay(2000);
    }
}

When I measure this on the Oscilloscope I get 57uS

Register Custom ISR Instead of HAL ISR

Well, 57uS is definitely more than 25uS (though it is way way less than 5ms).  I wondered why isn’t it meeting spec.  And I thought, maybe it is burning time in the HAL pin interrupt service routine.  So, I decided to attach my own ISR.

First there is an interrupt service routine function called “buttonHandler” which toggles the D0 pin to 1, then clears the port interrupt.

In the main function instead of enabling the event, I setup the interrupt directly by:

  • Configuring the Port for interrupts
  • Setting the edge to falling (because there is a resistive pullup on the switch)
  • Loading my own ISR
  • Enabling the interrupt

Then in the main loop I remove the D0 pin write to 1.

#include "cybsp.h"
#include "cyhal.h"
#include "cycfg.h"

void buttonHandler()
{
	cyhal_gpio_write(CYBSP_D0,1);
	Cy_GPIO_ClearInterrupt(GPIO_PRT0,4);
}


int main(void)
{
    /* Initialize the device and board peripherals */
    cybsp_init();


    cyhal_gpio_init(CYBSP_D0,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,0);
    cyhal_gpio_init(CYBSP_SW2,CYHAL_GPIO_DIR_INPUT,CYHAL_GPIO_DRIVE_PULLUP,1);
    cyhal_gpio_init(CYBSP_USER_LED,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);



	Cy_GPIO_SetInterruptMask(GPIO_PRT0,4,1);
	Cy_GPIO_SetInterruptEdge(GPIO_PRT0,4,CY_GPIO_INTR_FALLING);

	Cy_SysInt_SetVector(ioss_interrupts_gpio_0_IRQn, buttonHandler);

	NVIC_EnableIRQ(ioss_interrupts_gpio_0_IRQn);

    __enable_irq();

    while(1)
    {
    	cyhal_gpio_write(CYBSP_USER_LED,0);
    	cyhal_gpio_write(CYBSP_D0,0);
    	cyhal_system_deepsleep();
    	cyhal_gpio_write(CYBSP_USER_LED,1);
    	CyDelay(2000);
    }
}

When I measure this, I find that it is 54uS instead of 57uS (notice I left the cursors from the previous measurement)

Disable ARM Interrupts (no ISR)

Then I think maybe the problem is the jump to the ISR.  Inside the ARM there are two enable controls over interrupts

  1. In the NVIC
  2. A global ARM interrupt control

So, I use the cyhal_gpio_enable_event to enable the NVIC.  Then I use the CMSIS function __disable_irq to turn off the ARM interrupts.

int main(void)
{
    /* Initialize the device and board peripherals */
    cybsp_init();

    cyhal_gpio_init(CYBSP_D0,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,0);
    cyhal_gpio_init(CYBSP_SW2,CYHAL_GPIO_DIR_INPUT,CYHAL_GPIO_DRIVE_PULLUP,1);
    cyhal_gpio_init(CYBSP_USER_LED,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);

    cyhal_gpio_enable_event(CYBSP_SW2, CYHAL_GPIO_IRQ_FALL, 4, true);

	__disable_irq();

    while(1)
    {
    	cyhal_gpio_write(CYBSP_USER_LED,0);
    	cyhal_gpio_write(CYBSP_D0,0);
    	cyhal_system_deepsleep();
    	cyhal_gpio_write(CYBSP_D0,1);
    	cyhal_gpio_write(CYBSP_USER_LED,1);
    	CyDelay(2000);
    }
}

Now it is 53uS or basically the same as before.  So this doesn’t explain the missing 30uS (or whatever is required to get blow the data sheet spec)

Write the Output Pin Register Directly (no HAL)

Then I think to myself, maybe the HAL functions are slow.  Look at the pin write function:

cyhal_gpio_write(CYBSP_D0,0);

When you look at the function you see that it is really a MACRO for an inline call to the PDL function

__STATIC_INLINE void cyhal_gpio_write_internal(cyhal_gpio_t pin, bool value)
{
    Cy_GPIO_Write(CYHAL_GET_PORTADDR(pin), CYHAL_GET_PIN(pin), value);
}

#define cyhal_gpio_write(pin, value) cyhal_gpio_write_internal(pin, value)

The inline PDL function turns a pin number into a Port, Pin combination with a call to some other PDL functions.

#define CYHAL_GET_PORTADDR(pin)    (Cy_GPIO_PortToAddr(CYHAL_GET_PORT(pin)))  /**< Macro to get the port address from pin */

Those functions basically lookup the bit mask and base address of the Port,Pin.  Plus they have some error checking.

__STATIC_INLINE GPIO_PRT_Type* Cy_GPIO_PortToAddr(uint32_t portNum)
{
    GPIO_PRT_Type* portBase;
    
    if(portNum < (uint32_t)IOSS_GPIO_GPIO_PORT_NR)
    {
        portBase = (GPIO_PRT_Type *)(CY_GPIO_BASE + (GPIO_PRT_SECTION_SIZE * portNum));
    }
    else
    {
        /* Error: Return default base address */
        portBase = (GPIO_PRT_Type *)(CY_GPIO_BASE);
    }

    return (portBase);
}

Then they call this macro:

GPIO_PRT_OUT_SET(GPIO_PRT5) = 0x01;

Which is just a direct register write.

(((GPIO_PRT_V1_Type*)(base))->OUT_SET)

So I change over my program to write directly to the port register and is make ZERO difference.  On the range where I can see the pulse between the interrupt and the pin write, the difference is too small to register.

Try Different Clock Frequencies

The next thing that I wonder is if the CPU frequency matters.  On my board there are three possible sources of CM4 clock.

  • The 8MHz IMO
  • The FLL (also known as CLK_PATH0)
  • The PLL (also known as CLK_PATH1)

To try the different possibilities, I start by selecting CLK_PATH1 (the PLL) as the source clock for CLK_HF0

Then configure the PLL to 100 MHz

Then I tell the “PATH_MUX1” to use the IMO

Then I start running tests.  Here is the table of results for a bunch of different combinations.

Freq PLL FLL IMO
8 MHz - - 119 uS
12.5 MHz 102 uS - -
25 MHz 73uS 102 uS -
50 MHz 60uS 102 uS -
100 MHz 53uS 102 uS -
150 MHz 53 uS - -

OK.  The “wakeup” time seems to depend on the clock source and frequency.  First, notice that if you use the PLL that it typically takes 16uS to lock … and it could take as much as 35uS.  That explains part of the difference.

The FLL consistently takes 7.uS to lock.  In fact that is the main reason it exists on this chip.

We know that the FLL and PLL explain some of the difference in the startup time.  But where is the rest?

Modify the Cypress PDL Function Cy_SysPm_EnterDeepSleep

The answer is that there are a bunch of things that happen AFTER the chip wakes up inside of the Cy_SysPm_EnterDeepSleep.  These things are part of the house keeping that it takes to make everything really work.

First, look at the cyhal_system_deepsleep() function, which is really just a #define for the PDL DeepSleep function.

#define cyhal_system_deepsleep()                Cy_SysPm_CpuEnterDeepSleep(CY_SYSPM_WAIT_FOR_INTERRUPT)

If you dig through that function you will find yourself down in another function named “EnterDeepSleepRam.  If you look on line 2965 you will find that the code sets the bit in the ARM System Control Register which tells it to DeepSleep.  Then on line 2969 it executes the ARM assembly language instruction “WFI” also known as Wait For interrupt.  The WFI puts the CPU to sleep, or deep sleep depending on bit in the SCR register.  On lines 2970, 2993 and 3030 you can see that I instrumented the code to toggle the D0 GPIO so I can measure time.

/*******************************************************************************
* Function Name: EnterDeepSleepRam
****************************************************************************//**
*
* The internal function that prepares the system for Deep Sleep and 
* restores the system after a wakeup from Deep Sleep.
*
* \param waitFor
* Selects wait for action. See \ref cy_en_syspm_waitfor_t.
*
* \return
* - true - System Deep Sleep was occurred. 
* - false - System Deep Sleep was not occurred.
*
*******************************************************************************/
#if defined (__ICCARM__)
#pragma diag_suppress=Ta023
__ramfunc
#else
CY_SECTION(".cy_ramfunc") CY_NOINLINE
#endif
static void EnterDeepSleepRam(cy_en_syspm_waitfor_t waitFor)
{
/* Store the address of the Deep Sleep indicator into the RAM */
volatile uint32_t *delayDoneFlag = &FLASHC_BIST_DATA_0;
#if (CY_CPU_CORTEX_M4)
/* Store the address of the CM4 power status register */
volatile uint32_t *cpussCm4PwrCtlAddr = &CPUSS_CM4_PWR_CTL;
/* Repeat the WFI/WFE instruction if a wake up was not intended. 
*  Cypress ID #272909
*/
do
{
#endif /* (CY_CPU_CORTEX_M4) */
/* The CPU enters Deep Sleep mode upon execution of WFI/WFE */
SCB_SCR |= SCB_SCR_SLEEPDEEP_Msk;
if(waitFor != CY_SYSPM_WAIT_FOR_EVENT)
{
__WFI();
GPIO_PRT_OUT_SET(GPIO_PRT5) = 0x01;
}
else
{
__WFE();
#if (CY_CPU_CORTEX_M4)
/* Call the WFE instruction twice to clear the Event register 
*  of the CM4 CPU. Cypress ID #279077
*/
if(wasEventSent)
{
__WFE();
}
wasEventSent = true;
#endif /* (CY_CPU_CORTEX_M4) */
}
#if (CY_CPU_CORTEX_M4)
} while (_FLD2VAL(CPUSS_CM4_PWR_CTL_PWR_MODE, (*cpussCm4PwrCtlAddr)) == CM4_PWR_STS_RETAINED);
#endif /* (CY_CPU_CORTEX_M4) */
GPIO_PRT_OUT_CLR(GPIO_PRT5) = 0x01;
/* Set 10 uS delay only under condition that the FLASHC_BIST_DATA[0] is 
*  cleared. Cypress ID #288510
*/
if (*delayDoneFlag == NEED_DELAY)
{
uint32_t ddftSlowCtl;
uint32_t clkOutputSlow;
uint32_t ddftFastCtl;
/* Save timer configuration */
ddftSlowCtl   = SRSS_TST_DDFT_SLOW_CTL_REG;
clkOutputSlow = SRSS_CLK_OUTPUT_SLOW;
ddftFastCtl   = SRSS_TST_DDFT_FAST_CTL_REG;
/* Configure the counter to be sourced by IMO */
SRSS_TST_DDFT_SLOW_CTL_REG = SRSS_TST_DDFT_SLOW_CTL_MASK;
SRSS_CLK_OUTPUT_SLOW       = CLK_OUTPUT_SLOW_MASK;
SRSS_TST_DDFT_FAST_CTL_REG = TST_DDFT_FAST_CTL_MASK;
/* Load the down-counter to count the 10 us */
SRSS_CLK_CAL_CNT1 = IMO_10US_DELAY;
while (0U == (SRSS_CLK_CAL_CNT1 & SRSS_CLK_CAL_CNT1_CAL_COUNTER_DONE_Msk))
{
/* Wait until the counter stops counting */
}
/* Indicate that delay was done */
*delayDoneFlag = DELAY_DONE;
/* Restore timer configuration */
SRSS_TST_DDFT_SLOW_CTL_REG = ddftSlowCtl;
SRSS_CLK_OUTPUT_SLOW       = clkOutputSlow;
SRSS_TST_DDFT_FAST_CTL_REG = ddftFastCtl;
}
GPIO_PRT_OUT_SET(GPIO_PRT5) = 0x01;
}
#if defined (__ICCARM__)

Here is the SCR register documentation where you can see the bit “SLEEPDEEP” bit[2]

And later in the documentation the Wait For Interrupt (WFI) instruction.

When I ran the code I got:

  • From the falling edge of the to the rising edge is 17.12uS (deep sleep to first instruction on line 2970)
  • From the rising to falling edge is 6.25uS (line 2970 to line 2993)
  • From the falling to riding edge is 30uS (line 2993 to 3030)
  • From the rising to falling edge is 3.5uS (line 3030 to the first line in the main function)

Here is the scope trace.

What does it all mean?  There are basically three things going on from the Wakeup until the application developer has control.

  1. Cypress implementation of work arounds for chip issues
  2. Synchronization between the two MCUs in the PSoC 6
  3. Unwinding the DeepSleep preparations (user callbacks)

Write the ARM DeepSleep Register Directly and Call __WIFI

So this gives us a hint.  We could implement just the DeepSleep instructions.  If you did, the code would look like this:

#include "cybsp.h"
#include "cyhal.h"
#include "cycfg.h"
int main(void)
{
/* Initialize the device and board peripherals */
cybsp_init();
cyhal_gpio_init(CYBSP_D0,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,0);
cyhal_gpio_init(CYBSP_SW2,CYHAL_GPIO_DIR_INPUT,CYHAL_GPIO_DRIVE_PULLUP,1);
cyhal_gpio_init(CYBSP_USER_LED,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);
cyhal_gpio_enable_event(CYBSP_SW2, CYHAL_GPIO_IRQ_FALL, 4, true);
__disable_irq();
while(1)
{
cyhal_gpio_write(CYBSP_D0,0);
SCB_SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
GPIO_PRT_OUT_SET(GPIO_PRT5) = 0x01;
Cy_GPIO_ClearInterrupt(GPIO_PRT0,4);
NVIC_ClearPendingIRQ	(	ioss_interrupts_gpio_0_IRQn	)	;
cyhal_gpio_write(CYBSP_USER_LED,0);
CyDelay(2000);
cyhal_gpio_write(CYBSP_USER_LED,1);
}
}

Well there it is 12uS.  That is for sure below the data sheet limit of 25uS.

But is it a good idea?  No, almost certainly not.  If you don’t call the Cypress functions you will

  1. Not be protected from the dual core interactions
  2. Not call our functions to work around silicon bugs
  3. Potentially not manage the clocks correctly

So unless you have some really compelling reason you should just use the Cypress functions and accept the 50ish uS to get back to Active.

PSoC 6 & Using the MCWDT as a Deep Sleep Timer

Summary

This article walks you through the steps to use the PSoC 6 MultiCounter Watch Dog Timer (MCWDT) as a DeepSleep Timer.  This includes using the Cypress HAL (lptimer), PDL as well as the configurators to achieve this goal.

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

If you want to build a low power PSoC 6 project you need a method to wake up the chip from DeepSleep.  This is particularly true of projects that use an RTOS in Tickless mode where you need a mechanism to go to DeepSleep and wakeup at a specific time.  Why DeepSleep?  Because it burns way-way less power than the active modes.  Way less means something like 3 orders of magnitude less. 

In this article I will

  • Describe the MultiCounter Watch Dog Timer (MCWDT)
  • Configure the MCWDT using the PSoC 6 Configurator
  • Configure the MCWDT using PDL
  • Configure the MCWDT using the HAL – AKA the lptimer
  • Show the LP Timer Deep Sleep Current vs. Active Current
  • Measure the Sleep Time & Show Interactions with DeepSleep and rest of the PSoC6

The Multi Counter Watch Dog Timer (MWCDT)

Inside of the PSoC 6 there are two blocks that have some variant of the name Watch Dog Timer.  Specifically the:

  • Multi Counter Watch Dog Timer (MCWDT)
  • Watch Dog Timer (WDT)

Here is a screenshot from the PSoC 6 block diagram.

As I was working on this article I spoke with the original architect of the PSoC 6 and he told me that you should use the WDT as a WDT (but that it could be used as a periodic timer) and you should use the MCWDT as a periodic timer (but that it could be used as a WDT).  This is a pretty common example of the Cypress design aesthetic of maximizing flexibility.

Bottom line:  You should use the MCWDT as a deep sleep timer.

What is the MCWDT?  It is actually three counters which can be used individually or cascaded.  The counters each up count to a specific match value (aka period) and reset or they optionally count continuously.  And each counter can generate an interrupt when the period is reached.  The one “weird” thing is that counter 2 doesn’t have a match value, it has a match BIT position meaning it will match every 2,4,8,16…  times depending on the bit position.

The counters are clocked by one of the 32kHz clocks in the PSoC 6 called “LFCLK” which can either be the WCO, the ILO or the PILO.  This means that each “count” is 1/32768 = 30uS-ish

This means you can have

  • 2×16-bit counters and 1×32 bit
  • 2×32-bit counters
  • 1×16-bit and 1×48-bit counter
  • 1×64-bit counter

Here is a picture of the block diagram from the TRM.

PSoC 6 Configurator

For the first project I will use the PSoC 6 Configurators to setup the PDL configuration structures.   I will make a project that will blink an LED at 1Hz.  To do this I will configure counter 0 to turn 30.5uS into milliseconds and counter 1 to count 500 milliseconds.  When counter 1 hits 500 it will toggle the LED.

Start by making a new project:

Give it a name and choose the Empty PSoC6 App as a template

From the quick panel choose “Device Configurator (new configuration)”

Then click on Peripherals –> System and enable Multi-Counter Watchdog Timer (MCWDT) 0.

Notice that you can configure it to count up to the max then rollover (also known as Free running) or you can have it reset the counters back to 0 when it hits the match value (also known as Clear on match)

And when it his the end what do you want it to do?  Interrupt, Watchdog reset, 3xWatchdog reset or nothing?

Here is the configuration we want for this example:

When you hit “save” the configurator will update the files in “libs->TARGET_CY8CKIT-062-WiFi-BT->COMPONENT_BSP_DESIGN_MODUS->GeneratedSource”.  Specifically cycfg_peripherals.h/.c.

In the cycfg_peripherals.h it makes an #define alias called “mycounter_” which is sets you up for the MCWDT0

And in the cycfg_peripherals.c it sets up the configuration structure

Now you can write this simple program in main.c

  • Lines 5-9 provide an ISR that toggles the LED and clears the MCWDT interrupt.  Notice that I assumed the interrupt was counter 1
  • Lines 18-27 setup interrupts for the MCWDT0
  • Lines 30-36 use the PDL configuration structure form the PSoC 6 configurator to setup the MCWDT

The main loop just goes into DeepSleep to save power.

#include "cybsp.h"
#include "cyhal.h"
#include <stdio.h>
void mcwdtISR()
{
Cy_MCWDT_ClearInterrupt	(mycounter_HW,CY_MCWDT_CTR1);
cyhal_gpio_toggle(CYBSP_USER_LED1);
}
int main(void)
{
cybsp_init() ;
cyhal_gpio_init(CYBSP_USER_LED1,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);
cy_stc_sysint_t intrCfg =
{
.intrSrc = mycounter_IRQ,
.intrPriority = 4UL
};
Cy_SysInt_Init(&intrCfg, mcwdtISR);
NVIC_EnableIRQ(mycounter_IRQ);
__enable_irq();
Cy_MCWDT_Unlock(mycounter_HW);
Cy_MCWDT_Disable(mycounter_HW, CY_MCWDT_CTR0 | CY_MCWDT_CTR1, 100);
Cy_MCWDT_Init(mycounter_HW,&mycounter_config);
Cy_MCWDT_SetInterruptMask(	MCWDT_STRUCT0,	CY_MCWDT_CTR1	);
Cy_MCWDT_Enable(mycounter_HW, CY_MCWDT_CTR0 | CY_MCWDT_CTR1 , 100);
for(;;)
{
Cy_SysPm_DeepSleep(CY_SYSPM_WAIT_FOR_INTERRUPT);
}
}

Basic HAL LP Timer

Obviously you can do all of the PDL stuff… but we also provide a higher level abstraction called the “LPTIMER”  which does all of the configuration for you.   To demonstrate this I create the same program except using the HAL.  This program will use the LPTIMER and an event to blink the LED at 1hz (toggle every 500ms).

The main.c has:

  • Lines 9-12 a simple function to turn milliseconds into ticks of the MCWDT (remember it is clocked with 32KHz)
  • Lines 14-18 is the event handler function which is called when the LPTIMER expires.  All it does is toggle the LED and reset the timer to MSDELAY
  • Lines 26-29 setup the LPTimer, enable the interrupt event, register the callback function and then set the timer

The main loop just goes to DeepSleep.

#include "cy_pdl.h"
#include "cyhal.h"
#include "cybsp.h"
#define MSDELAY 500
cyhal_lptimer_t myTimer;
static inline uint32_t msToTicks(uint32_t ms)
{
return (uint32_t)(CY_SYSCLK_WCO_FREQ*(ms/1000));
}
void lptimer_event(void *callback_arg, cyhal_lptimer_event_t event)
{
cyhal_gpio_toggle(CYBSP_LED8);
cyhal_lptimer_set_delay(&myTimer, msToTicks(MSDELAY));
}
int main(void)
{
cybsp_init() ;
cyhal_gpio_init(CYBSP_LED8,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);
cyhal_lptimer_init(&myTimer);
cyhal_lptimer_enable_event (&myTimer, CYHAL_LPTIMER_COMPARE_MATCH, 4, true);
cyhal_lptimer_register_callback(&myTimer, lptimer_event, 0);
cyhal_lptimer_set_delay(&myTimer, msToTicks(MSDELAY));
__enable_irq();
for(;;)
{
cyhal_system_deepsleep();
}
}

LP Timer Deep Sleep Current vs. Active Current

For the next project I will use the LPTIMER to demonstrate the DeepSleep current versus the Active Current.  This project:

  • Will use LED8 to indicate DeepSleep and LED9 to indicate Active
  • Go to DeepSleep for 2 seconds (so you can read the power)
  • Wait in a busy-wait loop for 2 seconds
  • Loop again

The main.c has:

  • Lines 8-12 a simple function to turn milliseconds into ticks of the MCWDT (remember it is clocked with 32KHz)
  • Lines 17-18 Setup the two LEDs to indicate the power state
  • Lines 20-21 initialize the low power timer.  Notice that you can enable the event (which will wakeup the chip) but you don’t have to provide an event handler.  The LPTIMER ISR will handle clearing the interrupts for you
  • Lines 27-29 will setup the LPTIMER delay, turn on LED8 and off LED9 and go to DeepSleep
  • Lines 31-32 will turn off LED8 and on LED9 then go into a busy wait loop.

Notice that I did two three things weren’t aren’t awesome:

  1. I hardcoded the 0/1 for the LED state (they are active low)… this is too bad since the BSP actually provides CYBSP_LED_STATE_ON
  2. I put two LED set on the same line (two statements on the same line is super dangerous)
  3. No comments (yes Hassane will make commentary on this)
#include "cyhal.h"
#include "cybsp.h"
cyhal_lptimer_t myTimer;
#define MSDELAY 2000
static inline uint32_t msToTicks(uint32_t ms)
{
return (uint32_t)(CY_SYSCLK_WCO_FREQ*(ms/1000));
}
int main(void)
{
cybsp_init() ;
cyhal_gpio_init(CYBSP_LED8,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);
cyhal_gpio_init(CYBSP_LED9,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);
cyhal_lptimer_init(&myTimer);
cyhal_lptimer_enable_event (&myTimer, CYHAL_LPTIMER_COMPARE_MATCH, 4, true);
__enable_irq();
for(;;)
{
cyhal_lptimer_set_delay(&myTimer,msToTicks(MSDELAY));
cyhal_gpio_write(CYBSP_LED8,0);	cyhal_gpio_write(CYBSP_LED9,1);
cyhal_system_deepsleep();
cyhal_gpio_write(CYBSP_LED8,1);	cyhal_gpio_write(CYBSP_LED9,0);
Cy_SysLib_Delay(MSDELAY);
}
}

On the back of my development kit I setup the multimeter to be in series with the PSoC 6 power.  Notice that I keep from loosing the jumper by attaching it to only 1 of the pins.

When I measure the power I get Active power of about 10mA

And DeepSleep power of 15uA… that’s cool almost 1000x difference.

The one things that took me a LONG time to figure out is that I was having problems with the PSoC 6 resetting.  My multimeter has a bunch of different ranges for current measurement.  Each of these different ranges have a different shunt-resistor to increase the accuracy of the meter.  The interesting part is that the meter actually uses a mechanical relay to switch between the ranges.  You can hear it clicking between the ranges.  It turns out that sometime the relay switching took long enough that the PSoC6 would loose power and reset.

Measure the Sleep Time?

The next thing that I was curious about was how long it actually slept.  So I created a new project which would print out the counter values before and after the sleeps using printf which was added to my project with “Retarget I/O”.  You can do this in the library manager.

Once the library is added to your project you can enable it by adding the

#include "cy_retarget_io.h"

And

cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);

In this project I add prints to look at the sleep time… on lines 42 & 48

#include "cy_pdl.h"
#include "cyhal.h"
#include "cybsp.h"
#include "cy_retarget_io.h"
cyhal_lptimer_t myTimer;
#define MSDELAY 2000
static inline uint32_t msToTicks(uint32_t ms)
{
return (uint32_t)(CY_SYSCLK_WCO_FREQ*(ms/1000));
}
int main(void)
{
uint32_t sleepTime=0,wakeTime=0,totalTime=0;
cybsp_init() ;
cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);
printf("Started Project\n");
cyhal_gpio_init(CYBSP_LED8,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);
cyhal_gpio_init(CYBSP_LED9,CYHAL_GPIO_DIR_OUTPUT,CYHAL_GPIO_DRIVE_STRONG,1);
cyhal_lptimer_init(&myTimer);
cyhal_lptimer_enable_event (&myTimer, CYHAL_LPTIMER_COMPARE_MATCH, 4, true);
__enable_irq();
for(;;)
{
cyhal_lptimer_set_delay(&myTimer,msToTicks(MSDELAY));
cyhal_gpio_write(CYBSP_LED8,0);	cyhal_gpio_write(CYBSP_LED9,1);
sleepTime = cyhal_lptimer_read(&myTimer);
cyhal_system_deepsleep();
wakeTime = cyhal_lptimer_read(&myTimer);
printf("Deep Sleep Time = %ld Wake=%ld Sleep=%ld\n",wakeTime-sleepTime,wakeTime,sleepTime);
cyhal_gpio_write(CYBSP_LED8,1);	cyhal_gpio_write(CYBSP_LED9,0);
sleepTime = cyhal_lptimer_read(&myTimer);
Cy_SysLib_Delay(MSDELAY);
wakeTime = cyhal_lptimer_read(&myTimer);
totalTime = (wakeTime<sleepTime)?wakeTime+UINT32_MAX-sleepTime:wakeTime-sleepTime;
printf("Sleep Time = %ld %ld %ld\n",totalTime,wakeTime,sleepTime);
}
}

I immediately see that something is wrong.  Now my project never DeepSleeps.  But why?

In order to figure this out I add the code to look at the return code from the cyhal_deep_sleep function.

		cy_rslt_t failedCode = cyhal_system_deepsleep();
wakeTime = cyhal_lptimer_read(&myTimer);
printf("Deep Sleep Time = %ld Wake=%ld Sleep=%ld\n",wakeTime-sleepTime,wakeTime,sleepTime);
uint8_t type = CY_RSLT_GET_TYPE(failedCode);
uint16_t module_id = CY_RSLT_GET_MODULE(failedCode);
uint16_t error_code = CY_RSLT_GET_CODE(failedCode);
printf("Type = %02X Module=%02X Code=%02X\n",type,module_id,error_code);

When I run this you see that I am getting error code 0xFF.  But where is that coming from?

Now is a good time to examine the lower power callbacks.  When you initialize the UART in the HAL it will add a low power callback.  The low power call back is called in one of four situations:

When you look a the hal uart initialization function in cy

 

cy_rslt_t cyhal_uart_init(cyhal_uart_t *obj, cyhal_gpio_t tx, cyhal_gpio_t rx, const cyhal_clock_divider_t *clk, const cyhal_uart_cfg_t *cfg)
{
CY_ASSERT(NULL != obj);

On line 278 it sets up the callback function “cyhal_uart_pm_callback”

        obj->pm_params.base = obj->base;
obj->pm_params.context = obj;
obj->pm_callback.callback = &cyhal_uart_pm_callback;
obj->pm_callback.type = CY_SYSPM_DEEPSLEEP;
obj->pm_callback.skipMode = 0;
obj->pm_callback.callbackParams = &(obj->pm_params);
obj->pm_callback.prevItm = NULL;
obj->pm_callback.nextItm = NULL;
if (!Cy_SysPm_RegisterCallback(&(obj->pm_callback)))
result = CYHAL_UART_RSLT_ERR_PM_CALLBACK;

Then when you look in cy_hal_uart.c you see that the function will call the function Cy_SCB_UART_DeepSleepCallback (line 103)

static cy_en_syspm_status_t cyhal_uart_pm_callback(cy_stc_syspm_callback_params_t *params, cy_en_syspm_callback_mode_t mode)
{
cyhal_uart_t *obj = params->context;
cy_stc_syspm_callback_params_t pdl_params = { .base = obj->base, .context = &(obj->context) };
cy_en_syspm_status_t rslt = Cy_SCB_UART_DeepSleepCallback(&pdl_params, mode);
GPIO_PRT_Type *txport = obj->pin_tx != NC ? CYHAL_GET_PORTADDR(obj->pin_tx) : NULL,
*rtsport = obj->pin_rts != NC ? CYHAL_GET_PORTADDR(obj->pin_rts) : NULL;
uint8_t txpin = (uint8_t)CYHAL_GET_PIN(obj->pin_tx), rtspin = (uint8_t)CYHAL_GET_PIN(obj->pin_rts);
switch (mode)
{
case CY_SYSPM_CHECK_READY:
if (rslt == CY_SYSPM_SUCCESS)
{
if (NULL != txport)
{
obj->saved_tx_hsiom = Cy_GPIO_GetHSIOM(txport, txpin);
Cy_GPIO_Set(txport, txpin);
Cy_GPIO_SetHSIOM(txport, txpin, HSIOM_SEL_GPIO);
}
if (NULL != rtsport)
{
obj->saved_rts_hsiom = Cy_GPIO_GetHSIOM(rtsport, rtspin);
Cy_GPIO_Set(rtsport, rtspin);
Cy_GPIO_SetHSIOM(rtsport, rtspin, HSIOM_SEL_GPIO);
}
}
break;
case CY_SYSPM_CHECK_FAIL: // fallthrough
case CY_SYSPM_AFTER_TRANSITION:
if (NULL != txport)
{
Cy_GPIO_SetHSIOM(txport, txpin, obj->saved_tx_hsiom);
}
if (NULL != rtsport)
{
Cy_GPIO_SetHSIOM(rtsport, rtspin, obj->saved_rts_hsiom);
}
break;
default:
break;
}
return rslt;
}

And in the PDL file cy_scb_uart.c you can see that this function will fail if there is data currently being transmitted (line 315)

cy_en_syspm_status_t Cy_SCB_UART_DeepSleepCallback(cy_stc_syspm_callback_params_t *callbackParams, cy_en_syspm_callback_mode_t mode)
{
cy_en_syspm_status_t retStatus = CY_SYSPM_FAIL;
CySCB_Type *locBase = (CySCB_Type *) callbackParams->base;
cy_stc_scb_uart_context_t *locContext = (cy_stc_scb_uart_context_t *) callbackParams->context;
switch(mode)
{
case CY_SYSPM_CHECK_READY:
{
/* Check whether the High-level API is not busy executing the transmit
* or receive operation.
*/
if ((0UL == (CY_SCB_UART_TRANSMIT_ACTIVE & Cy_SCB_UART_GetTransmitStatus(locBase, locContext))) &&
(0UL == (CY_SCB_UART_RECEIVE_ACTIVE  & Cy_SCB_UART_GetReceiveStatus (locBase, locContext))))
{
/* If all data elements are transmitted from the TX FIFO and
* shifter and the RX FIFO is empty: the UART is ready to enter
* Deep Sleep mode.
*/
if (Cy_SCB_UART_IsTxComplete(locBase))
{
if (0UL == Cy_SCB_UART_GetNumInRxFifo(locBase))
{
/* Disable the UART. The transmitter stops driving the
* lines and the receiver stops receiving data until
* the UART is enabled.
* This happens when the device failed to enter Deep
* Sleep or it is awaken from Deep Sleep mode.
*/
Cy_SCB_UART_Disable(locBase, locContext);
retStatus = CY_SYSPM_SUCCESS;
}
}
}
}
break;
case CY_SYSPM_CHECK_FAIL:
{
/* The other driver is not ready for Deep Sleep mode. Restore the
* Active mode configuration.
*/
/* Enable the UART to operate */
Cy_SCB_UART_Enable(locBase);
retStatus = CY_SYSPM_SUCCESS;
}
break;
case CY_SYSPM_BEFORE_TRANSITION:
/* Do noting: the UART is not capable of waking up from
* Deep Sleep mode.
*/
break;
case CY_SYSPM_AFTER_TRANSITION:
{
/* Enable the UART to operate */
Cy_SCB_UART_Enable(locBase);
retStatus = CY_SYSPM_SUCCESS;
}
break;
default:
break;
}
return (retStatus);
}

But, where does the error code come from?  When you click on it, you find yourself on this enumeration:

typedef enum
{
CY_SYSPM_SUCCESS         = 0x0U,                                         /**< Successful. */
CY_SYSPM_BAD_PARAM       = CY_SYSPM_ID | CY_PDL_STATUS_ERROR | 0x01U,    /**< One or more invalid parameters. */
CY_SYSPM_TIMEOUT         = CY_SYSPM_ID | CY_PDL_STATUS_ERROR | 0x02U,    /**< A time-out occurred. */
CY_SYSPM_INVALID_STATE   = CY_SYSPM_ID | CY_PDL_STATUS_ERROR | 0x03U,    /**< The operation is not setup or is in an
improper state. */
CY_SYSPM_CANCELED        = CY_SYSPM_ID | CY_PDL_STATUS_ERROR | 0x04U,    /**< Operation canceled. */
CY_SYSPM_SYSCALL_PENDING = CY_SYSPM_ID | CY_PDL_STATUS_ERROR | 0x05U,    /**< Canceled due syscall operation pending. */
CY_SYSPM_FAIL            = CY_SYSPM_ID | CY_PDL_STATUS_ERROR | 0xFFU     /**< Unknown failure. */
} cy_en_syspm_status_t;

And when you look at CY_SYSPM_ID your find:

/** SysPm driver identifier */
#define CY_SYSPM_ID                      (CY_PDL_DRV_ID(0x10U))

OK… so the reason in our project that it doesn’t go into deep sleep is that the printf from earlier has not finished.  To fix this you update your project to keep trying until the DeepSleep call succeeds (on lines 43-45)

	for(;;)
{
cyhal_lptimer_set_delay(&myTimer,msToTicks(MSDELAY));
cyhal_gpio_write(CYBSP_LED8,0);	cyhal_gpio_write(CYBSP_LED9,1);
sleepTime = cyhal_lptimer_read(&myTimer);
cy_rslt_t failedCode;
do {
failedCode = cyhal_system_deepsleep();
} while(failedCode != CY_SYSPM_SUCCESS);
wakeTime = cyhal_lptimer_read(&myTimer);
printf("Deep Sleep Time = %ld Wake=%ld Sleep=%ld\n",wakeTime-sleepTime,wakeTime,sleepTime);
cyhal_gpio_write(CYBSP_LED8,1);	cyhal_gpio_write(CYBSP_LED9,0);
sleepTime = cyhal_lptimer_read(&myTimer);
Cy_SysLib_Delay(MSDELAY);
wakeTime = cyhal_lptimer_read(&myTimer);
totalTime = (wakeTime<sleepTime)?wakeTime+UINT32_MAX-sleepTime:wakeTime-sleepTime;
printf("Sleep Time = %ld %ld %ld\n",totalTime,wakeTime,sleepTime);
}

Now this makes a lot more sense

 

Modus Toolbox 2.1 Released

Summary

Yesterday, the Cypress software team released an update to Modus Toolbox, specifically to the “tools package”.  I am super excited about a bunch of the new features and I thought that I would should show you a few of the updates.  Specifically:

  1. The New Project Wizard
  2. Updated Directory Structure for Projects
  3. Visual Studio Code Integration
  4. Eclipse Integration
  5. Updates to the HAL

New Project Wizard

The first interesting thing about Modus Toolbox is that we designed it to be independent of IDE.  If you want to use Eclipse, OK.  If you want to use Visual Studio Code or IAR or MDK or … that is cool with us as well.  We did tons of work to make sure that our tools are completely independent of the operating system and the IDE.

You can still create your projects from inside of Eclipse from the Quick Panel.

The update in Modus Toolbox 2.1 is that the new project will launch an external project creation tool.

That will look like this:

However, if you want, you can totally ditch Eclipse and start the new project wizard from outside (via the Start menu or the Launchpad).  The new project creation wizard is a stand alone program that is written in C++ program and Qt that works on Mac, Windows, and Linux.  When you run the new project creation tool you will see this (at least on a Mac).  Look, it is exactly the same as the one you get from inside Eclipse (no surprise since it is exactly the same thing)

Notice that there is NO requirement that you run this tool from Eclipse.  On Windows you can find it from the Start menu and on Mac you can find it on the Launchpad (as project-creator).

First pick out a “Kit name” which will select the board support package for your project.  Ill Pick “CY8CKIT-062-WIFI-BT” (because that is what happens to be on my desk and press Next.

When the Project Creator starts it will go to the Cypress GitHub site and load in a file that will tell it all of the example projects and BSPs available.  This means that Cypress can release new BSPs and Example templates without you having to update your version of Modus Toolbox.

I give my example a name of “ExampleMTB21” then pick out the “Empty PSoC6 App” and press Create.


To create a project we basically “git clone” a bunch of different repositories.  So, in the output window, you will mostly see the output of Git doing its thing.

When it is all done you can press “Close”.  Now you can look in the file browser and you will see a complete project.

Updated Directory Structure

When you look at the files inside of a file browser you should notice two things.   First there is a directory called “deps” which has two files called “dot Libs”.  These two files contain URLs to the GitHub repositories for two of the BSPs.  In Modus Toolbox 2.0 we stored the dotLib files in the same directory with the actual libraries.  When you look in the Libs directory you will see the actual libraries which are required to build your project.  The Libs directory is now “Generated Source” which means you can blow it away and it can be re-generated with “make getlibs” (which will read the dotLibs from the Deps directory”

Command Line Interface

If you don’t like IDE’s you can now build and program this with our command line interface by running “make build”

And then you can program your board with “make program”

Visual Studio Code Integration

The command line stuff is cool, but I know that there are tons of people out there who like Visual Studio Code which we we now officially support.  To use it you can run “make vscode” and it will create the files required to use your project in Visual Studio Code.

When I look at the project I see that Modus Toolbox create a directory called “.vscode” which has all of the secret sauce to make Visual Studio Code work.

Now I can run “code .” (on my Mac) to start Visual Studio Code.  Or on a PC you can run Visual Studio Code from the Start and just open the directory.  Notice that when you run the “make vscode” we give you the instructions.  Inside of Visual Studio Code you will see the project.

You can press cmd-shift-b to build the project

Which will send all of the output to the Visual Studio Code console.

And you can then press F5 to start the programmer/debugger

Eclipse Integration

But if you are an Eclipse user where does this leave you?  Notice that you don’t have the Eclipse project files in your project anymore… AHHHHH???!?!?!   Relax.  We still love you.  To get your project back into Eclipse you just need to run “make eclipse” and we will recreate the Eclipse project for you.

If you want to add this to an existing workspace you can run “Import”

Then pick out your directory.

Press “Generate Launches for …” on the Quick Panel.  Then Press Build.

And now you have a fully functioning Modus Toolbox Project inside of your Eclipse workspace.

HAL Updates

Another one of my favorite things was a massive update to the documentation inside of the Hardware Abstraction Layer.  At this point most of the HAL drivers should have Code snippets to give you an example of what to do.