AnyCloud – Wireless Connection Manager (Part 2)

Summary

Part 2 of the discussion of using the Infineon (Cypress) PSoC 6 with a CYW4343W and the AnyCloud Connection Manager with Modus Toolbox.  The AnyCloud Connection Manager is an RTOS thread that lets you manage a connection to a WiFi network.  It knows how to scan for networks, attach, detach etc. and was recently released for use with PSoC6 and 43xxx WiFi combos.

The Story

In the last article I walked you through creating a project using the wireless connection manager (WCM) which is one of the libraries that is part of the Infineon/Cypress AnyCloud SDK.  I introduced the wifi-mw-core, wifi-connection-manager and ntshell libraries.  In this article I will update the program to include commands to

  • connect (to an Access Point)
  • disconnect (from an Access Point)
  • print (mac address and ip address)

Add the Connect

I want to add a command that will let me issue a command line like

  • connect SSID (if it doesn’t have a passphrase)
  • connect SSID passphrase

In order to connect to an Access Point you need to call the WCM API, cy_wcm_connect_ap.  Here is the function prototype:

cy_rslt_t cy_wcm_connect_ap(const cy_wcm_connect_params_t *connect_params, cy_wcm_ip_address_t *ip_addr)

This function requires that you give it two arguments

  1. A pointer for a place to store the IP address (that comes from the DHCP server)
  2. A pointer to a structure of connection parameters.  That structure is as follows:
typedef struct
{
    cy_wcm_ap_credentials_t  ap_credentials;       /**< Access point credentials */
    cy_wcm_mac_t             BSSID;                /**< Specifies the MAC address of Access Point (optional) */
    cy_wcm_ip_setting_t      *static_ip_settings;  /**< Specifies the static IP settings of the device (optional) */
    cy_wcm_wifi_band_t       band;                 /**< Specifies the Radio band to be connected (optional) */
} cy_wcm_connect_params_t;

Typically you would setup the “ap_credentials” part of the structure (unless you happen to know the MAC address of the AP you want to connect to).  Those credentials include the SSID and password as well as the security (WPA2 PSK etc…)

typedef struct
{
    cy_wcm_ssid_t        SSID;                /**< SSID of the Wi-Fi network to join, SSID should be a null terminated string. */
    cy_wcm_passphrase_t  password;            /**< Password needed to join the AP, password should be a null terminated string. */
    cy_wcm_security_t    security;            /**< Wi-Fi Security. @see cy_wcm_security_t. */
} cy_wcm_ap_credentials_t;

Security is an enumeration of possible security types.

typedef enum
{
    WHD_SECURITY_OPEN             = 0,                                                                 /**< Open security                                         */
    WHD_SECURITY_WEP_PSK          = WEP_ENABLED,                                                       /**< WEP PSK Security with open authentication             */
    WHD_SECURITY_WEP_SHARED       = (WEP_ENABLED | SHARED_ENABLED),                                    /**< WEP PSK Security with shared authentication           */
    WHD_SECURITY_WPA_TKIP_PSK     = (WPA_SECURITY | TKIP_ENABLED),                                     /**< WPA PSK Security with TKIP                            */
    WHD_SECURITY_WPA_AES_PSK      = (WPA_SECURITY | AES_ENABLED),                                      /**< WPA PSK Security with AES                             */
    WHD_SECURITY_WPA_MIXED_PSK    = (WPA_SECURITY | AES_ENABLED | TKIP_ENABLED),                       /**< WPA PSK Security with AES & TKIP                      */
    WHD_SECURITY_WPA2_AES_PSK     = (WPA2_SECURITY | AES_ENABLED),                                     /**< WPA2 PSK Security with AES                            */
    WHD_SECURITY_WPA2_TKIP_PSK    = (WPA2_SECURITY | TKIP_ENABLED),                                    /**< WPA2 PSK Security with TKIP                           */
    WHD_SECURITY_WPA2_MIXED_PSK   = (WPA2_SECURITY | AES_ENABLED | TKIP_ENABLED),                      /**< WPA2 PSK Security with AES & TKIP                     */
    WHD_SECURITY_WPA2_FBT_PSK     = (WPA2_SECURITY | AES_ENABLED | FBT_ENABLED),                       /**< WPA2 FBT PSK Security with AES & TKIP */
    WHD_SECURITY_WPA3_SAE         = (WPA3_SECURITY | AES_ENABLED),                                     /**< WPA3 Security with AES */
    WHD_SECURITY_WPA3_WPA2_PSK    = (WPA3_SECURITY | WPA2_SECURITY | AES_ENABLED),                     /**< WPA3 WPA2 PSK Security with AES */

    WHD_SECURITY_WPA_TKIP_ENT     = (ENTERPRISE_ENABLED | WPA_SECURITY | TKIP_ENABLED),                /**< WPA Enterprise Security with TKIP                     */
    WHD_SECURITY_WPA_AES_ENT      = (ENTERPRISE_ENABLED | WPA_SECURITY | AES_ENABLED),                 /**< WPA Enterprise Security with AES                      */
    WHD_SECURITY_WPA_MIXED_ENT    = (ENTERPRISE_ENABLED | WPA_SECURITY | AES_ENABLED | TKIP_ENABLED),  /**< WPA Enterprise Security with AES & TKIP               */
    WHD_SECURITY_WPA2_TKIP_ENT    = (ENTERPRISE_ENABLED | WPA2_SECURITY | TKIP_ENABLED),               /**< WPA2 Enterprise Security with TKIP                    */
    WHD_SECURITY_WPA2_AES_ENT     = (ENTERPRISE_ENABLED | WPA2_SECURITY | AES_ENABLED),                /**< WPA2 Enterprise Security with AES                     */
    WHD_SECURITY_WPA2_MIXED_ENT   = (ENTERPRISE_ENABLED | WPA2_SECURITY | AES_ENABLED | TKIP_ENABLED), /**< WPA2 Enterprise Security with AES & TKIP              */
    WHD_SECURITY_WPA2_FBT_ENT     = (ENTERPRISE_ENABLED | WPA2_SECURITY | AES_ENABLED | FBT_ENABLED),  /**< WPA2 Enterprise Security with AES & FBT               */

    WHD_SECURITY_IBSS_OPEN        = (IBSS_ENABLED),                                                    /**< Open security on IBSS ad-hoc network                  */
    WHD_SECURITY_WPS_SECURE       = AES_ENABLED,                                                       /**< WPS with AES security                                 */

    WHD_SECURITY_UNKNOWN          = -1,                                                                /**< May be returned by scan function if security is unknown. Do not pass this to the join function! */

    WHD_SECURITY_FORCE_32_BIT     = 0x7fffffff                                                         /**< Exists only to force whd_security_t type to 32 bits */
} whd_security_t;

Having to know the security of the AP is a total pain in the neck.  Where do you find the security from?  It turns out that when an AP beacons, the security of that SSID is one of the things that is broadcast.  What this means is that my program will need to

  1. When the connect command is called it should scan for the SSID that is part of the connect command & wait
  2. When the scan finds that SSID it will put the security type into the correct datastructure
  3. Then call the connect.

The way that I will do this is to

  1. Build a filter (that looks only for the user specified SSID)
  2. Provides a pointer for a place to store the security type.

I use the cy_wcm_scan function to do this.  Here is the function prototype:

cy_rslt_t cy_wcm_start_scan(cy_wcm_scan_result_callback_t callback, void *user_data, cy_wcm_scan_filter_t *scan_filter)

The scan filter is just a structure that specified

  1. A mode (which type of filter you want)
  2. The specific thing that you are looking for.
typedef struct
{
    cy_wcm_scan_filter_type_t      mode;        /**< Scan filter mode */
    union
    {
        cy_wcm_ssid_t              SSID;        /**< Service Set Identification */
        cy_wcm_mac_t               BSSID;       /**< MAC address of Access Point */
        cy_wcm_wifi_band_t         band;        /**< Radio band */
        cy_wcm_scan_rssi_range_t   rssi_range;  /**< RSSI range */
    } param;                                    /**< Scan filter mode specific paramter */
} cy_wcm_scan_filter_t;

The mode is simply an enumeration of the types of filters:

typedef enum
{
   CY_WCM_SCAN_FILTER_TYPE_SSID = 0,   /**< Denotes SSID based scan filtering */
   CY_WCM_SCAN_FILTER_TYPE_MAC,        /**< Denotes MAC  based scan filtering */
   CY_WCM_SCAN_FILTER_TYPE_BAND,       /**< Denotes BAND based scan filtering */
   CY_WCM_SCAN_FILTER_TYPE_RSSI,       /**< Denotes RSSI based scan filtering */
}cy_wcm_scan_filter_type_t;

What I want to do is start the scan and then wait for a semaphore.  To do this I will create a semaphore variable at the top of the netTask.c

SemaphoreHandle_t scanApSempahore = NULL;

Inside of the switch I will

  1. Create a connection parameters structure (line 226-227)
  2. setup the scan filter (line 231-232)
  3. create the semaphore (line 235)
  4. run the scan (line 236)
  5. wait for the semaphore to be set or timeout (line 239) notice that I hardcoded it to 10 seconds
				cy_wcm_connect_params_t connect_params;
				memset(&connect_params, 0, sizeof(cy_wcm_connect_params_t));

				// setup scan filter - In order to connect to an SSID you need to know the security type
				// To find the security I scan for JUST that SSID which will tell me the security type
				scanFilter.mode = CY_WCM_SCAN_FILTER_TYPE_SSID;
				strcpy((char *)scanFilter.param.SSID,(char *)msg.val0);

				// The scan callback will either 1) unlock the semaphore or 2) timeout (meaning it didnt find it)
				scanApSempahore = xSemaphoreCreateBinary();
				cy_wcm_start_scan(findApCallback,&connect_params.ap_credentials.security,&scanFilter);
				
				// The semaphore will return pdFALSE if it TIMES out or pdTrue IF it got unlocked by the scan
				if(xSemaphoreTake( scanApSempahore, pdMS_TO_TICKS(10000)) == pdTRUE)

In the scan callback I will check to see if I have real data (in other words the scan is not complete).  In the setup above I made the user data be a pointer to the place to store the security.  On line 54 I will store the security type that came back from the scan in the place pointed to by the user data pointer.  Then I will stop the scan and give the semaphore.

// This callback is used to find a specific SSID and then store the security type into the user data
// When I want to connect it will scan with a "filter" and the user data will be a pointer to the
// place to store the security
void findApCallback( cy_wcm_scan_result_t *result_ptr, void *user_data, cy_wcm_scan_status_t status )
{
	if(status == CY_WCM_SCAN_INCOMPLETE)
	{
		whd_security_t *mySecurity = (whd_security_t *)user_data;
	 	*mySecurity = result_ptr->security;
		cy_wcm_stop_scan();
		xSemaphoreGive(scanApSempahore);
	}
}

Now back in the switch statement you can actually connect because you know the security type (line 244)  The else clause on line 253 handles the case where the timeout of the semaphore occurred, meaning that the scan didn’t find the AP.

				// The semaphore will return pdFALSE if it TIMES out or pdTrue IF it got unlocked by the scan
				if(xSemaphoreTake( scanApSempahore, pdMS_TO_TICKS(10000)) == pdTRUE)
				{
					strcpy((char *)connect_params.ap_credentials.SSID,(char *)msg.val0);
					strcpy((char *)connect_params.ap_credentials.password,(char *)msg.val1);

					result = cy_wcm_connect_ap(&connect_params,&ip_addr);
					if(result == CY_RSLT_SUCCESS)
						printf("Connect Succeeded SSID=%s\n",(char *)msg.val0);
					else
					{
						printf("Connect to %s failed\n",(char *)msg.val0);
					}	
				}
				else
				{
					printf("Scan semaphore failed - couldnt find AP\n");
				}
				free((void *)msg.val0); // Free the SSID and PW that was passed by the caller
				free((void *)msg.val1);

			}
			break;

With all of the connection work done, you can add the scan command to “usrcmd.c”.  It just looks at the number of arguments (either 2 or 3), then sets up the message to send to the network task, the queues the message.

static int usrcmd_connect(int argc, char **argv)
{
    networkQueueMsg_t msg;

    if(argc == 2)
    {
        msg.val0 = (uint32_t)malloc(strlen(argv[1])+1);
        msg.val1 = (uint32_t)malloc(sizeof(""));
        strcpy((char *)msg.val0,argv[1]);
        strcpy((char *)msg.val1,"");
        msg.cmd = net_connect;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }
    
    if(argc == 3)
    {
        msg.val0 = (uint32_t)malloc(strlen(argv[1])+1);
        msg.val1 = (uint32_t)malloc(strlen(argv[2])+1);
        strcpy((char *)msg.val0,argv[1]);
        strcpy((char *)msg.val1,argv[2]);
        msg.cmd = net_connect;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }

    
    return 0;

}

Add the Disconnect Command

The disconnect command is trivial.  Just call the disconnect api.

			case net_disconnect:
				cy_wcm_disconnect_ap();
			break;

Which you also need to add to the usercmd.c

static int usrcmd_disconnect(int argc, char **argv)
{
    networkQueueMsg_t msg;
    msg.cmd = net_disconnect;
    xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    
    return 0;
}

Add the Print Command

The print command will have two optional parameters, IP (to print the current IP address) and MAC (to print our MAC address).  The first command is print ip.

			case net_printip:
			result = cy_wcm_get_ip_addr(CY_WCM_INTERFACE_TYPE_STA,&ip_addr,1);
			if(result == CY_RSLT_SUCCESS)
			{
				printf("IP Address=");
				printIp(&ip_addr);
				printf("\n");
			}
			else if(result == CY_RSLT_WCM_NETWORK_DOWN)
				printf("Network disconnected\n");
			else 
				printf("IP Address call return unknown %d\n",(int)result);
			break;

The MAC address command is also simple:

			case net_printmac:
				result = cy_wcm_get_mac_addr(CY_WCM_INTERFACE_TYPE_STA,&mac_addr,1);
				if(result == CY_RSLT_SUCCESS)
				{
					printf("MAC Address =");
					printMac(mac_addr);
					printf("\n");
				}
				else
					printf("MAC Address = Unknown\n");
			break;
		}

And you need to add the print command to usrcmd.c

static int usrcmd_print(int argc, char **argv)
{
    networkQueueMsg_t msg;
    if(argc == 2 && strcmp(argv[1],"ip")==0)
    {
        msg.cmd = net_printip;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }
    if(argc == 2 && strcmp(argv[1],"mac")==0)
    {
        msg.cmd = net_printmac;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }
    return 0;

}

All of this code is available on github at

  • git@github.com:iotexpert/wcm_example
  • https://github.com/iotexpert/wcm_example

AnyCloud – Wireless Connection Manager (Part 1)

Summary

A discussion of using the Infineon (Cypress) PSoC 6 with a CYW4343W and the AnyCloud Connection Manager with Modus Toolbox.  The AnyCloud Connection Manager is an RTOS thread that lets you manage a connection to a WiFi network.  It knows how to scan for networks, attach, detach etc. and was recently released for use with PSoC6 and 43xxx WiFi combos.

The Story

In the WICED WiFI SDK there is an example program called “test.console” which allows you to use a UART command line to do networking “stuff”.  With the release of the new AnyCloud SDK I decided that I should rebuild some of that program using the PSoC and WiFi.  Basically a set of commands like “scan” to interact with the Radio World!  In the picture you below you can see that I use the command “scan” to list out the networks at my house.

You can also use the command “help” to list out the commands.

Architecture

My implementation will have three tasks

  1. A Blinking LED Task (which doesn’t interact with any other tasks)
  2. The NT Shell – which will send commands to the network queue.
  3. The Network Task which will receive commands from the NT Shell task and trigger the Wireless Connection Manager to do something
  4. The Wireless Connection Manager which will interact with the WiFI Radio via the Wireless Host driver.

Make the Basic Project

To build this project start by creating a new project.  The development board that I had plugged into my computer when I began this project is a CY8CPROTO-062-4343W so this is the one I will select.

In the new project creator pick the “CY8CPROTO-062-4343W”

I created a FreeRTOS template project that does all of the right stuff.  I wrote an article about how to use the IoT Expert Manifestt here, and until you add the IoT Manifest to your setup you will not get the FreeRTOS Template project.

After the new project work is done you should see

Add the nt-shell, which comes from my IoT Expert library (read about how to add the IoT Expert library here).

I have been on a kick of using Visual Studio code.  So, run “make vscode” to setup the project for Visual Studio Code.

In the finder, copy the template files from nt-shell into your project.

You can also do it with the command line.

Edit main.c to include the configuration.

#include "ntshell.h"
#include "psoc6_ntshell_port.h"

Update the main.c to have the ntShellThread

// Global variable with a handle to the shell
ntshell_t ntshell;

void ntShellTask()
{

  printf("Started ntshell\n");
  setvbuf(stdin, NULL, _IONBF, 0);
  ntshell_init(
               &ntshell,
               ntshell_read,
               ntshell_write,
               ntshell_callback,
               (void *)&ntshell);
  ntshell_set_prompt(&ntshell, "AnyCloud> ");
  vtsend_erase_display(&ntshell.vtsend);
  ntshell_execute(&ntshell);
}

And start the ntshell task.

    xTaskCreate(ntShellTask, "nt shell task", configMINIMAL_STACK_SIZE*2,0 /* args */ ,4 /* priority */, 0);

When you want to add a command to the shell you need to do three things

  1. Add a function prototype for the command
  2. Add the command to the command list
  3. Write the function
static int usrcmd_help(int argc, char **argv);
static int usrcmd_info(int argc, char **argv);
static int usrcmd_clear(int argc, char **argv);
static int usrcmd_printargs(int argc, char **argv);
static const cmd_table_t cmdlist[] = {
    { "help", "This is a description text string for help command.", usrcmd_help },
    { "info", "This is a description text string for info command.", usrcmd_info },
    { "clear", "Clear the screen", usrcmd_clear },
    { "printargs","print the list of arguments", usrcmd_printargs},
};

Here is an example of of the printargs command.  The shell will call your function with a ARGC=number of arguments and ARGV[] an array of the pointers to the actual arguments.

static int usrcmd_printargs(int argc, char **argv)
{
    printf("ARGC = %d\n",argc);

    for(int i =0;i<argc;i++)
    {
        printf("argv[%d] = %s\n",i,argv[i]);
    }
    return 0;

}

Build and test your program.

Turn on the Connection Manager and Core Middleware Library

Now, add the wifi-mw-core library & Connection Manager using the library manager.  You can start it with “make modlibs”.  These two libraries are in the “WiFi Middleware” category.

Copy the FreeRTOSConfig.h from the libs/wifi-mw-core/configs directory into your project (so you can modify it)

Update the Makefile to include the WiFi components (line 71) and the MBED TLS configuration (line 86)

COMPONENTS=FREERTOS PSOC6HAL LWIP MBEDTLS 4343W
# Like COMPONENTS, but disable optional code that was enabled by default.
DISABLE_COMPONENTS=

# By default the build system automatically looks in the Makefile's directory
# tree for source code and builds it. The SOURCES variable can be used to
# manually add source code to the build process from a location not searched
# by default, or otherwise not found by the build system.
SOURCES=

# Like SOURCES, but for include directories. Value should be paths to
# directories (without a leading -I).
INCLUDES=

# Add additional defines to the build process (without a leading -D).
DEFINES=MBEDTLS_USER_CONFIG_FILE='"configs/mbedtls_user_config.h"' CYBSP_WIFI_CAPABLE

Create networkTask.h/.c & Start the Task in main.c

Now let’s create the networkTask.  This task will control all of the networking functions in the system.  The first file, networkTask.h, is the public interface.  It declares a Queue where you can push messages (line 5) an enumeration of the messages (lines 7-14), a definition of the message data (lines 17-22) and finally the network task declaration (line 24).

#pragma once
#include "FreeRTOS.h"
#include "queue.h"

extern QueueHandle_t networkQueue;

typedef enum {
    net_scan,
    net_connect,
    net_disconnect,
    net_printip,
    net_printmac,

} networkCmd_t;


typedef struct {
    networkCmd_t cmd;
    uint32_t val0;
    uint32_t val1;

} networkQueueMsg_t;

void networkTask(void *arg);

Ill go ahead and modify main.c to start the yet to be written network task.  You need to include the header file for the task and define a handle for the task.

#include "networkTask.h"

TaskHandle_t networkTaskHandle;

Finally in main function, start the task.

    xTaskCreate(networkTask, "networkTask", configMINIMAL_STACK_SIZE*8,0 /* args */ ,4/* priority */, &networkTaskHandle);

Create the file networkTask.c.  Make a bunch of includes.

#include <stdio.h>
#include <stdlib.h>
#include "FreeRTOS.h"
#include "task.h"
#include "cy_wcm.h"
#include "cy_wcm_error.h"
#include "whd_types.h"
#include "queue.h"
#include "semphr.h"
#include "networkTask.h"

Now let’s define the actual task function.  This function will

  1. Call the wcm_init function to make a WiFi Station (lines 201-203)
  2. Tell the task that you would like to be called back when things happen (line 205)
  3. Initialize the Queue to receive command messages from other tasks.
  4. Make an infinite loop to receive the commands and process the messages.  Notice that I dont do anything with the message for now.  Ill add the code later.
// The networkTask will:
// - startup the wireless connection manager
// - sit waiting on the rtos queue... getting messages from other tasks
// - and do ( net_scan, net_connect, net_disconnect, net_printip, net_printmac,)

void networkTask(void *arg)
{
	cy_rslt_t result;
	cy_wcm_config_t config;
	cy_wcm_scan_filter_t scanFilter;

	memset(&config, 0, sizeof(cy_wcm_config_t));
    config.interface = CY_WCM_INTERFACE_TYPE_STA;
	cy_wcm_init	(&config);

	cy_wcm_register_event_callback(	wcmCallback	);

	networkQueue = xQueueCreate( 5, sizeof(networkQueueMsg_t));

	while(1)
	{
		networkQueueMsg_t msg;

		xQueueReceive(networkQueue,(void *)&msg,portMAX_DELAY);  // Wait for commands from other tasks

		switch(msg.cmd)
		{
			case net_scan: // 0=stop scan !0=start scan
			break;

			case net_connect:
			break;

			case net_disconnect:
			break;

			case net_printip:
			break;

			case net_printmac:
			break;
		}
	}

}

Create Utilities for Printing out the IP and MAC Address

It the network task I want to be able to print out IP address (both IPV4 and IPV6).  I also want to be able to print out 6-byte MAC address.  In the Cypress drivers, an IP address is a structure that holds either a IPV4 or and IPV6 address.  It then contains the 4 or 6 bytes of the address.  Here is the type definition.

/**
 * IP Address Structure
 */
typedef struct
{
    cy_wcm_ip_version_t version;  /**< IP version */
    union
    {
        uint32_t v4;     /**< IPv4 address bytes */
        uint32_t v6[4];  /**< IPv6 address bytes */
    } ip;                /**< IP address bytes */
} cy_wcm_ip_address_t;

The IP v ersion is just an enumeration.

/**
 * IP Version
 */
typedef enum
{
    CY_WCM_IP_VER_V4 = 4,      /**< Denotes IPv4 version */
    CY_WCM_IP_VER_V6 = 6       /**< Denotes IPv6 version */
} cy_wcm_ip_version_t;

To print an address, figure out which version you are working on.  Then dump the raw bytes.  Notice in the IPV4 case it is encoded into a uint32_t as 4 continuous bytes.

void printIp(cy_wcm_ip_address_t *ipad)
{
	if(ip_addr.version == CY_WCM_IP_VER_V4)
	{
		printf("%d.%d.%d.%d",(int)ipad->ip.v4>>0&0xFF,(int)ipad->ip.v4>>8&0xFF,(int)ipad->ip.v4>>16&0xFF,(int)ipad->ip.v4>>24&0xFF);
	}
	else if (ip_addr.version == CY_WCM_IP_VER_V6)
	{
		for(int i=0;i<4;i++)
		{
			printf("%0X:",(unsigned int)ip_addr.ip.v6[i]);
		}
	}
	else
	{
		printf("IP ERROR %d",ipad->version);
	}			
}

A MAC address is just an array of uint8_t of length CY_WCM_MAC_ADDR_LEN  (which we pound defined to 6)

typedef uint8_t cy_wcm_mac_t[CY_WCM_MAC_ADDR_LEN];                   /**< Unique 6-byte MAC address */

So, the print is just a loop. (probably should have done something slightly better so I dont end up with the trailing : – oh well)

void printMac(cy_wcm_mac_t mac)
{
	for(int i=0;i<CY_WCM_MAC_ADDR_LEN;i++)
	{
		uint8_t val = mac[i];
		printf("%02X:",val);
	}
}

Add the Scan Command

For the scan I want to print out the:

  1. SSID
  2. RSSI (in dBM)
  3. Channel
  4. Band
  5. Speed
  6. Type of Ap
  7. Country Code
  8. MAC address of the Access Point (AKA BSSID)
  9. Type of Security

In order to have the WCM run a scan you need to call the function cy_wcm_start_scan.  What this will do is tell the 4343W to scan all the channels on the 2.4GHZ band and listen for access point beacons.  This function has three arguments

  1. A function pointer to call back when you find an AP
  2. A user settable data pointer
  3. A filter (which can limit to an SSID, BSSID or RSSI)
cy_rslt_t cy_wcm_start_scan(cy_wcm_scan_result_callback_t callback, void *user_data, cy_wcm_scan_filter_t *scan_filter)

Here is my version of the scanCallback which I put in the networkTask.c file.

// This function is called back when the user asks for an overall scan.
// It just prints out the information about the networks that it hears about
void scanCallback( cy_wcm_scan_result_t *result_ptr, void *user_data, cy_wcm_scan_status_t status )

The result_ptr is a pointer to a structure that contains the data for the found access point.

/**
 * Structure for storing scan results
 */
typedef struct
{
    cy_wcm_ssid_t                SSID;             /**< Service Set Identification (i.e. Name of Access Point)                    */
    cy_wcm_mac_t                 BSSID;            /**< Basic Service Set Identification (i.e. MAC address of Access Point)       */
    int16_t                      signal_strength;  /**< Receive Signal Strength Indication in dBm. <-90=Very poor, >-30=Excellent */
    uint32_t                     max_data_rate;    /**< Maximum data rate in kilobits/s                                           */
    cy_wcm_bss_type_t            bss_type;         /**< Network type                                                              */
    cy_wcm_security_t            security;         /**< Security type                                                             */
    uint8_t                      channel;          /**< Radio channel that the AP beacon was received on                          */
    cy_wcm_wifi_band_t           band;             /**< Radio band                                                                */
    uint8_t                      ccode[2];         /**< Two letter ISO country code from AP                                       */
    uint8_t                      flags;            /**< flags                                                                     */
    uint8_t                      *ie_ptr;          /**< Pointer to received Beacon/Probe Response IE(Information Element)         */
    uint32_t                     ie_len;           /**< Length of IE(Information Element)                                         */
} cy_wcm_scan_result_t;

The other parameter to the callback is the status.  The status will either be CY_WCM_SCAN_COMPLETE or CY_WCM_SCAN_INCOMPLETE.  The first thing that to do is see if this callback is the one that tells me that the scan is complete, if so I just return (and don’t print anything)

	if(status == CY_WCM_SCAN_COMPLETE)
		return;

Then I print out the raw SSID, Signal Strength and Channel.

	printf("%32s\t%d\t%d\t",result_ptr->SSID,result_ptr->signal_strength,result_ptr->channel);

Next I figure out what channel we are talking about.  The 4343W is single band 2.4GHZ, however, other Cypress chips have dual band.

	switch(result_ptr->band)
	{
		case CY_WCM_WIFI_BAND_ANY:
			printf("ANY");
		break;
		case CY_WCM_WIFI_BAND_5GHZ:
			printf("5.0 GHZ");
		break;
		case CY_WCM_WIFI_BAND_2_4GHZ:
			printf("2.4 GHZ");
		break;
	}

Then I printout the maximum data rate, which is 0 for all of my test cases.  I have no idea why.

	printf("%d",(int)result_ptr->max_data_rate);

Then I printout what type of AP we are talking about.

	switch(result_ptr->bss_type)
	{
		case CY_WCM_BSS_TYPE_INFRASTRUCTURE:
			printf("INFR");
		break; 	
		case CY_WCM_BSS_TYPE_ADHOC: 
			printf("ADHOC");
		break;	
		case CY_WCM__BSS_TYPE_ANY: 	
			printf("ANY");
		break;
		case CY_WCM_BSS_TYPE_MESH:
			printf("MESG");
		break;
		case CY_WCM_BSS_TYPE_UNKNOWN: 
			printf("UNKWN");
		break;
	}

Then the country code.

	printf("%c%c",result_ptr->ccode[0],result_ptr->ccode[1]);

Then the Basic Service Set ID of the AP, which is also known as the MAC address of the AP.

	printMac(result_ptr->BSSID);

Then the security type.

	switch(result_ptr->security)
	{
		case CY_WCM_SECURITY_OPEN:
			printf("OPEN");
		break;
    	case CY_WCM_SECURITY_WEP_PSK:
			printf("WEP_PSK");
		break;
		case CY_WCM_SECURITY_WEP_SHARED:
			printf("WEP_SHARED");
		break;
    	case CY_WCM_SECURITY_WPA_TKIP_PSK:
			printf("WPA_TKIP_PSK");
		break;
		case CY_WCM_SECURITY_WPA_AES_PSK:
			printf("WPA_AES_PSK");
		break;
		case CY_WCM_SECURITY_WPA_MIXED_PSK:
			printf("WPA_MIXED_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_AES_PSK:
			printf("WPA2_AES_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_TKIP_PSK:
			printf("WPA2_TKIP_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_MIXED_PSK:
			printf("WPA2_MIXED_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_FBT_PSK:
			printf("WPA2_FBT_PSK");
		break;
		case CY_WCM_SECURITY_WPA3_SAE:
			printf("WPA3_SAE");
		break;
		case CY_WCM_SECURITY_WPA3_WPA2_PSK:
			printf("WPA3_WPA2_PSK");
		break;
		case CY_WCM_SECURITY_IBSS_OPEN:
			printf("IBSS_OPEN");
		break;
		case CY_WCM_SECURITY_WPS_SECURE:
			printf("WPS_SECURE");
		break;
		case CY_WCM_SECURITY_UNKNOWN:
			printf("UNKNOWN");
		break;
		case CY_WCM_SECURITY_FORCE_32_BIT:
			printf("FORCE_32_BIT");
		break;
	}

With a complete scanCallback function I can now update the networkTask to deal with the queued commands of net_scan type.  This simply either stops or starts the scan based on the message parameter.

			case net_scan: // 0=stop scan !0=start scan
				if(msg.val0 == 0)
					cy_wcm_stop_scan();
				else
				{
					printf("\n");
					result = cy_wcm_start_scan(scanCallback,0,0);
					if(result != CY_RSLT_SUCCESS)
						printf("Scan error\n");
				}
			break;

The last things to do is to add the scan command to the usrcmd.c

static int usrcmd_scan(int argc, char **argv)
{

    static int state=0;

    if(argc==1)
    {
        state = (state ==0)?1:0;
    }
    else if(argc == 2)
    {
        if(strcmp(argv[1],"on") == 0)
        {
            state=1;
        } 
        else if(strcmp(argv[1],"off") == 0)
        {
            state = 0;
        }
        else
        {
            printf("usage: scan [on|off]\n");
            return 0;
        }
        
    }
    else
    {
        printf("usage scan: [on|off]\n");
        return 0;
    }
    
    networkQueueMsg_t msg;
    msg.cmd = net_scan;
    msg.val0 = state;
    xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    return 0;
}

OK.  Let it rip.  You should be able to run network scans.  In the next Article I will add a connect and disconnect commands.

You can find all of this example code at https://github.com/iotexpert/wcm_example

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.

ModusToolbox 2.0 – Libraries an Example for emWin & Super Manifests

Summary

In this article I will walk you through creating a library for ModusToolbox 2.0 that will glue the Cypress RTOS Abstraction layer, emWin, the SSD1306 and the PSoC 6 together.  In the end, the library will become available in the Modus Toolbox Library Manager by creating a Super Manifest file.

NOTE:  This blog was originally written about the Modus Toolbox 2.0 library scheme.  With the release of Modus Toolbox 2.2 this scheme was changed and this blog is now obsolete! Instead, you can use the Infineon display-oled-ssd1306 library.

The Story

I like using the Segger emWin graphics library.  And, I have always hated getting the “glue” into my project that makes the library work.  The glue includes the configuration files for emWin plus the hardware driver functions to talk to the SSD1306 screen.  I have always thought that it would be really nice to have a simple library scheme – yes I know that there are lots of schemes out there but I wanted one that was neatly integrated into our tools.  Well, with ModusToolbox 2.0 my wish was granted.

In ModusToolbox 2.0 if you create a file called “someLibraryName.lib” that contains a URL to a GitHub (or Git) repository, the make system will know how to bring that library into your project.  The make target “getlibs” does exactly that.  And, once it is on your computer in your project the Modus Toolbox make system will know how to include it as part of your project.

For this article I will create a library called “p6sdk-ssd1306-emWin-freerots-config” which will contain:

Files Purpose
GUIConf.h Configures emWins abilities, fonts etc.
GUIConf.c Defines & Assigns RAM for the GUI,initializes the font
GUI_X_CYRTOS.c Makes a connection between emWin and Cypress RTOS abstraction for things like timing, semaphore etc.
LCDConf.h A blank file
LCDConf.c Functions to configure screen,connect the emWin APIs to the I2C write functions, configures the display driver
SSD1306Driver.h Functions prototypes to initialize the I2C and read/write the I2C
SSD1306Driver.c Initialize the SSD1305 driver, write commands, write data, write datastream functions which are called by the LCDConf.c functions
template A directory (which is not compiled) that contains template c files for use as an example

SSD1306 Driver

In order to glue the hardware to the emWin library you need to provide functions that

  1. Initialize the driver – (Tell it what I2C hardware and I2C address the display is connected to)
  2. Write data/command bytes and streams
  3. Read data streams (which it actually never does)

The SSD1306Driver.h provides a public interface for these functions which are used in the LCDConf.c file.

#ifndef SSD1306_DRIVER_H
#define SSD1306_DRIVER_H
#include "cyhal.h"


void SSD1306DriverInit(cyhal_i2c_t *obj,uint8_t oledAddress);


void          SSD1306_WriteCommandByte(unsigned char c);
void          SSD1306_WriteDataByte(unsigned char c);
void          SSD1306_WriteDataStream(unsigned char * pData, int NumBytes);
void          SSD1306_ReadDataStream(unsigned char * pData, int NumBytes);

#endif

The first part of SSD1306Driver.c makes a pointer to and Cypress HAL I2C object.  In the initialization code, it connects the provided I2C object and the static pointer.  The purpose of this is to allow the application developer to use the I2C for other devices on the bus, in other words the I2C bus is shared.  On most of my screens the I2C address is 0x3C,  so I let the user not provide an I2C address.  Probably in hindsight I should have just made them always provide an I2C address.

#include "SSD1306Driver.h"
#include "GUI.h"
#include "cyhal.h"
#include "cybsp.h"
#include <stdlib.h>

/*********************************************************************
*
*       Defines: Configuration
*
**********************************************************************
  Needs to be adapted to custom hardware.
*/
/* I2C port to communicate with the OLED display controller */
static cyhal_i2c_t *I2C=0;

/* I2C slave address, Command and Data byte prefixes for the display controller */
#define OLED_CONTROL_BYTE_CMD       (0x00)
#define OLED_CONTROL_BYTE_DATA      (0x40)
static uint8_t OLED_I2C_ADDRESS     =    (0x3C);

void SSD1306DriverInit(cyhal_i2c_t *obj,uint8_t oledAddress)

{
	CY_ASSERT(obj);
	I2C=obj;
	if(oledAddress)
		OLED_I2C_ADDRESS = oledAddress;
}

In order for emWin to update a display it needs to be able to write data to the display via “commands” and “data” writes.  However, it doesn’t know anything about the fact that these displays are typically attached via I2C.  The function SSD1306_WriteCommandByte uses the Cypress HAL master write API to send a command byte to the display.  This function is called by emWin via the configuration in LCDConf.c

void SSD1306_WriteCommandByte(unsigned char c)
{
    uint8_t buff[2];

    /* The first byte of the buffer tells the display that the following byte
        is a command */
    buff[0] = OLED_CONTROL_BYTE_CMD;
    buff[1] = (char)c;

    /* Write the buffer to display controller */
    cyhal_i2c_master_write(I2C, OLED_I2C_ADDRESS, buff, 2, 0, true);
}

And the write data byte function works exactly the same way as the write command byte, except it send a different first byte.

void SSD1306_WriteDataByte(unsigned char c)
{
	uint8_t buff[2];

    /* First byte of the buffer tells the display controller that the following byte
        is a data byte */
    buff[0] = OLED_CONTROL_BYTE_DATA;
    buff[1] = c;

    /* Write the buffer to display controller */
    cyhal_i2c_master_write(I2C, OLED_I2C_ADDRESS, buff, 2, 0, true);
}

emWin Configuration Files

I have written extensively about how to configure emWin and specifically how to setup the files for the SSD1306.  You can see the articles here.  So, I am not going to describe those files in detail except to say that the ones that you need are GUIConf.h/.c, LCDConf.h/.c and GUI_X_CYRTOS.c.  The only changes from the previous configurations of LCDConf.c is to hookup the APIs we defined in the previous section.

    PortAPI.pfWrite8_A0  = SSD1306_WriteCommandByte;
    PortAPI.pfWrite8_A1  = SSD1306_WriteDataByte;
    PortAPI.pfWriteM8_A1 = SSD1306_WriteDataStream;

GitHub

I checked in all of these files into GitHub at git@github.com:iotexpert/p6sdk-ssd1306-emwin-cyrtos-config.git  Now they can be used as a Modus Toolbox library.

Test Library

Lets build a test project.  Start by creating  new project.  Notice that I am not using the built in project creator, but a standalone project creator which does not require Eclipse.  This is automatically installed for you as part of the Modus Toolbox installation (so, if you are one of the legions of people who don’t like Eclipse you don’t have to use it).  Run it from the start menu.

For this demo I will use the CY8CKIT-062-WiFi-Bt kit… but this works on all of of our PSoC 6 kits with Arduino headers.

I start using the IoT Expert FreeRTOS template.   And I store the project in the directory “~/iotexpert-projects/xxx”.  A long time ago I started using directories named “xxx” to mean that I can “rm -rf xxx” and I never store anything that matters in those files/directories.  Give the project a name then press next.

Now you have everything setup.  So click “Create”

And our software will do its thing.

Now if I look in the directory, I will have a complete project.

Now lets manually add the .lib for the p6sdk-ssd1306-emWin-cyrtos-config.  You can use whatever editor you want, but it’s emacs for me.

Enter the URL for the library.  Then put a “/”.  Then a branch.

Now that the file is updated you can run “make getlibs” which will search for all of the “.lib” files.  Then make sure those libraries are part of your project.

After the make getlibs is run you can see that the p6sdk-ssd1306-emWin-cyrtos-config directory is in your libs directory, with all of the stuff you need.

The next step is to edit your main.  I like to use vscode.  If you run  “make vscode” our build system will create a vscode project for you.  You can start vscode by running “code .”

Before the emWin library work you will need to add the EMWIN_OSNTS and FREERTOS components to your Makefile.

COMPONENTS=EMWIN_OSNTS FREERTOS

Here is what it looks like in vscode

Now, write the your main.c code to display a little message.

#include "cybsp.h"
#include "GUI.h"
#include "SSD1306Driver.h"

int main(void)
{
        /* Set up the device based on configurator selections */
        cybsp_init();

        cyhal_i2c_t I2C;

        /* Configuration to initialize the I2C block */
        static cyhal_i2c_cfg_t i2c_config = {
                .is_slave = false,
                .frequencyhal_hz = 400000
        };
        cyhal_i2c_init(&I2C, CYBSP_I2C_SDA, CYBSP_I2C_SCL, NULL);
        cyhal_i2c_configure(&I2C, &i2c_config);

        SSD1306DriverInit(&I2C,0x3C);

        GUI_Init();
        GUI_SetColor(GUI_WHITE);
        GUI_SetBkColor(GUI_BLACK);
        GUI_SetFont(GUI_FONT_8_ASCII);
        GUI_SetTextAlign(GUI_TA_CENTER);
        GUI_DispStringAt("Hello World", GUI_GetScreenSizeX()/2,GUI_GetScreenSizeY()/2 - GUI_GetFontSizeY()/2);
}

You can build your project by running “Build” from vscode or your can built it on the command line with “make build”

After building you should have output like this:

You can program by Pressing selecting or by pressing F5 or selecting “Run->Start Debugging”

Now you can press the play button and you are off to the races

Or on the command line you can program with “make program”

OK.  Looks like the library works!

Updating the IoT Expert MTB2 Manifests

I showed you how to manually add a library to your project.  But what if you want to have the library you built show up in the Library manager?  In the picture below you can see that is exactly what I did by adding a new category called “iotexpert”

To make this work you need a “Super Manifest” which is just a XML file that contains URLs references to your custom Application Templates manifest file(s), Middleware manifests file(s), and BSP manifest file(s). A “manifest” file is just an XML file with a list of URLs to Git Repos and some meta data.

In words you need:

  1. A file in your home directory called ~/.modustoolbox/manifest.loc which contains a URL for your cusom super manifest file
  2. A super manifest file that optionally contains a references to your application templates manifest file(s)
  3. And/Or contains a reference to your middleware manifest file(s)
  4. And/Or contains a reference to your board support manifest(s)

I start by creating the manifest.loc file to refer to a specific file on GitHub. “https://github.com/iotexpert/mtb2-iotexpert-manifests/raw/master/iotexpert-super-manifest.xml”.  Notice that it is stored in “.modustoolbo” which is a directory that starts with a “.” which is a PITA on Windows.  The only way I know how to edit/create this is by using the “modus shell” (which we provided as part of the Modus Toolbox installation.  Here is a screenshot from my Mac.  Notice that I use the GitHub URL mechanism to get to the “raw” file.

In my SuperManifest file I create references to the

  • board-manifest-list (of which I have none)
  • app-manifest-list which links to my application template manifest
  • middleware-manifest which links to my middleware manifest file

Notice that both of these files are on the same GitHub repository.

<super-manifest>
  <board-manifest-list>
  </board-manifest-list>
  
  <app-manifest-list>
    <app-manifest>
      <uri>https://github.com/iotexpert/mtb2-iotexpert-manifests/raw/master/iotexpert-app-manifest.xml</uri>
      </app-manifest>
   </app-manifest-list>
  <board-manifest-list>
  </board-manifest-list>
  <middleware-manifest-list>
    <middleware-manifest>
      <uri>https://github.com/iotexpert/mtb2-iotexpert-manifests/raw/master/iotexpert-mw-manifest.xml</uri>
    </middleware-manifest>
  </middleware-manifest-list>
</super-manifest>

Then if you look at the actual middleware manifest you will see that I have two (currently) middleware repositories

  • (The NTSHell) https://github.com/iotexpert/middleware-ntshell
  • (The SSD1306 Middleware in this article) https://github.com/iotexpert/p6sdk-ssd1306-emWin-cyrtos-config

The rest of the XML is meta data which specifies how the middleware is presented by the library manager.

<middleware>
  <middleware>
    <name>ntshell</name>
    <id>ntshell</id>
    <uri>https://github.com/iotexpert/middleware-ntshell</uri>
    <desc>NT Shell</desc>
    <category>iotexpert</category>
    <req_capabilities>psoc6</req_capabilities>
    <versions>
      <version>
        <num>Latest 1.X release</num>
        <commit>master</commit>
        <desc>Latest 1.X release</desc>
      </version>
    </versions>
  </middleware>
  <middleware>
    <name>SSD1306-emwin-cyrtos-config</name>
    <id>SSD1306-emwin-cyrtos-config</id>
    <uri>https://github.com/iotexpert/p6sdk-ssd1306-emwin-cyrtos-config</uri>
    <desc>SSD1306-emwin-cyrtos-config</desc>
    <category>iotexpert</category>
    <req_capabilities>psoc6</req_capabilities>
    <versions>
      <version>
        <num>Latest 1.X release</num>
        <commit>master</commit>
        <desc>Latest 1.X release</desc>
      </version>
    </versions>
  </middleware>
</middleware>

You can see that my GitHub repository contains

  1. iotexpert-super-manifest.xml  – amazingly enough the super manifest
  2. iotexpert-mw-manifest.xml – my middleware manifest file
  3. iotexpert-app-manifest.xml – my application template manifest file

Now if you look at the bottom of the library manager you will see that when it starts up it read the Cypress Super Manifest file, as well as the IoT Expert Super Manifest.

To be clear.  If you add my “manifest.loc” file to your ~/.modustoolbox directory you will access to all of my libraries.

Infineon SupIRbuck – Low Voltage/High Current Measurement

Summary

This article will show you the steps to produce 12A from and IRDC3894.  It also discusses low voltage high current measurement and burden voltage.

The Story

In the last few weeks I have been working on a TypeC to Infineon SupIRbuck power supply that will turn 20V@3A into 5V@12A.

In that process I have been working with a Keithley 2380-500-15 which can act as a load simulator for my design.  The 15 in name means that it can pull 15A which should be more an adequate to test my design.  Unfortunately when I was looking at the specs, I read the part about “15A” but not the part about the “Min Operating Voltage in the 15A range = 4.5V”

And what I really should have bought (and now have) is a 2380-120-60

Big Resistors

But I really wanted to be able to measure the current coming out of the IR3894.  So, I decided to buy some “Big” resistors.  Well they are not actually high resistance, but they are HIGH power. 35W and 50W.  Unfortunately they are also $4 each.  Here is a 0.33Ω 50W resistor.  (I will totally understand the value of that giant heat sink later on in this article)

My measurement setup looks like this:

And actually looks like this terrible mess:

Start the measurements

I bought 7 different resistors ranging from 0.1Ω to 1Ω.  This means that I should be able to get something like this table.  Note that the 0.1Ω is marked in red because it exceeds the current limit of my multimeter. (i.e. don’t do that)

V R A W Rating
1.2 0.10 12.0 14.4 35
1.2 0.15 8.0 9.6 50
1.2 0.20 6.0 7.2 35
1.2 0.33 3.6 4.4 50
1.2 0.50 2.4 2.9 35
1.2 0.75 1.6 1.9 50
1.2 1.00 1.2 1.4 50

I wasn’t exactly sure what was going to happen so I decided to start with the 0.33Ω resistor

I was hoping to get 1.2V/0.33Ω = 3.6A yet I end up with 1.99A where is my other 1.6A?

I put in a 0.1Ω, 0.15Ω and 0.75Ω and measured the current.  Then I calculate the effective resistance of the system and it turns out that there is essentially another 0.27Ω in series with my R1

V=I(R1+Rb)

V/I-R1 = Rb

V A R1Ω RbΩ R1+RbΩ
1.2 2.0 0.33 0.27 0.60
1.2 3.16 0.10 0.28 0.38
1.2 2.85 0.15 0.27 0.42
1.2 1.175 0.75 0.27 1.02

Where in the world is the other 0.27Ω?

Melt the Probe

This leads me to the great idea that is just the lead wires, and that I ought to just pull out the resistor and see what happens.  Well, what happens is that I melt the probe, and I still don’t get numbers that make sense.

Burden Voltage

The real story is, of course, that digital multimeters are hardly “ideal” and that when you are measuring current what you are really doing is measuring the voltage across a “shunt resistor”.  A shunt resistor is just a small valued highly precise resistor.  The voltage across this resistor is also known as the burden voltage (which is why I called it Rb above).  Here is a picture out of the Keysight documentation.

But how big is that resistor?  Well, unfortunately it is not specified directly in the documentation.  But, when you look at the data sheet you find that the maximum burden voltage is 0.5V at 10A.

This means that shunt resistor is no more than 0.5v/10A=0.05Ω.  I just ordered a new Keithley DAQ6510 Digital Acquisition System Multimeter (which Im very excited about) that has the following table in documentation where it says the shunt resistor is 100mΩ for the 3A range.

Measure the Lead Resistance

So now we know that the shunt resistor is something like 0.05Ω.  That means the rest of the resistance has to be in the test setup.  So

V=(R1 + Rl + Rb)*I … the lead resistance + the shunt resistor + the actual resistor.  I go ahead and calculate what the lead resistance with several different resistors.

This means the resistance of my lead wires must be something like 0.23Ω

V A R1 Rl Rb R1+Rb+Rl VR1 VRl VRb
1.2 2.00 0.33 0.23 0.04 0.37 0.66 0.46 0.08
1.2 3.16 0.10 0.23 0.04 0.15 0.32 0.73 0.16
1.2 2.85 0.15 0.23 0.04 0.19 0.43 0.66 0.12
1.2 1.18 0.75 0.23 0.04 0.79 0.88 0.27 0.05

Again I start to look for the lead wire resistance … 0.6Ω for the melted lead.  Curious.

It is pretty easy to get some crazy measurements.  2.7Ω just pushing two banana plugs together (though you can see it blinking)

If I measure the red banana plug wire looped back the measurement is 0.016Ω – OK that is pretty small

And the black one is almost the same.

After some experimenting mixing and matching cables together the lowest I manage to get in a configuration that I can actually plug together is 0.15Ω which gave me 7.8A.

So where does this leave me?  To tell you the truth it leaves me a bit frustrated.  I really wanted to get 12A out of my setup (which is what it should be able to do).  But the most I can safely measure is 10A.  And the lowest combination of resistors I seem to be able to get is 0.15Ω.  So I decide to solder the 0.1Ω resistor straight into the board and see what happens. (yes that is some ugly soldering)

And now Im really frustrated because when I turn on the bench power supply I get 9.9V and 0.6A … even though it is set for 12V

And when I look at the output voltage I get 0.4V.  Which means it isn’t working.  Why?  I don’t know…. which is beyond annoying.  Walk away now I say to myself (actually that was my wife yelling at me 🙂 )

24 Hours Later

After sleeping on it, I remember that the Enable pin is used as an “Under Voltage Lock Out”.  The purpose of the UVLO is to not turn on until the input voltage has enough power to supply the system.  Given that I am am asking for 12A I realize… maybe I need to “enable” later in the power supply voltage rise.

So what I do I solder in a jumper to “Enable” then I press fit it into the ground.  Then turn on the power supply.  I measure the output as 0V.  That is good.  Then pull the enable jumper wire and let the IR3894 turn on.

Sure enough.  When I look at the input it gets to 12V@1.4A and the output is still steady at 1.2v.  And my 0.1Ω is very hot.  I suppose that heatsink tab is there for a  reason.

I know from Ohms law that I am getting 12A@1.2V.  So the IR3894 seems to do the trick!

Cypress Type-C Barrel Connector Replacement + Infineon Buck DC/DC (Part 3)

Summary

This article walks you through the steps to test the CY4533 Type-C BCR & IR3894 under the load conditions from 1A to 12A.  In the previous article, I supplied power to the IR3894 using a bench top power supply.  For this set of experiments I will use a Type-C wall wart connected to the Cypress 4533 BCR development kit to supply power.

Test the BCR

The first thing that I do is connect the whole mess together like this:

Here is how it looks on my desk.  Note that the Keithey can measure current and voltage… but that I don’t have a way in this setup to measure either the voltage/current from the power supply or the current out of the CY4533

I step the output load from 1A to 12A in 1A increments.  I am super happy to see that the output voltage of the IR3894 is perfectly regulated to 1.198V.  It is also interesting to see that the Type-C power supply is able to keep the voltage within 3.25% of nominal even when I am using 12A on the IRDC3894 output (probably around 1.5A from the Type-C)

Measure the Input Current

In the previous article I used the current measurement from the Keithley bench top power supply.  In the setup above I don’t have a way to measure the actual input current.  To fix this put my new Keithley DAQ6510 in series with the IRDC3894 board.  Like this:

Then I step through the 1A-12A load conditions.  Once again the IR3894 provide a very well regulated voltage and current (exactly the same as before so I didn’t write them down)

Here is a table with the data from the previous post (without the Type-C power supply) versus the Type-C power supply.

2230-30-1 Power Supply With 6510 current meter in input path
Vin Iin Win Vout Iout Wout Eff Vin Iin Win Eff-C Loss
12 0.27 3.24 1.198 0 0 0%
12 0.129 1.548 1.198 0.998 1.195604 77% 11.91 0.129 1.53639 77.8% -0.6%
12 0.239 2.868 1.198 1.998 2.393604 83% 11.8 0.242 2.8556 83.8% -0.4%
12 0.345 4.14 1.198 2.998 3.591604 87% 11.7 0.352 4.1184 87.2% -0.5%
12 0.454 5.448 1.198 3.998 4.789604 88% 11.59 0.467 5.41253 88.5% -0.6%
12 0.564 6.768 1.198 4.999 5.988802 88% 11.47 0.586 6.72142 89.1% -0.6%
12 0.677 8.124 1.198 5.998 7.185604 88% 11.36 0.709 8.05424 89.2% -0.8%
12 0.792 9.504 1.198 6.998 8.383604 88% 11.25 0.837 9.41625 89.0% -0.8%
12 0.909 10.908 1.198 7.998 9.581604 88% 11.13 0.97 10.7961 88.8% -0.9%
12 1.029 12.348 1.198 8.998 10.779604 87% 10.95 1.115 12.20925 88.3% -1.0%
12 1.152 13.824 1.198 9.999 11.978802 87% 10.85 1.258 13.6493 87.8% -1.1%
12 1.277 15.324 1.198 10.998 13.175604 86% 10.8 1.401 15.1308 87.1% -1.1%
12 1.406 16.872 1.198 11.997 14.372406 85% 10.68 1.558 16.63944 86.4% -1.2%

These measurements use 1A/3A range on the Keithley DAQ6510 DMM, which means that they have a 100mΩ shunt resistor in series which drops the voltage by V=IR or about 0.1-ish volts.  This explains most of the difference from the Power Supply to the Type-C setup.

It is actually very interesting to look at the data to see the impact of lowering the input voltage on the efficiency of the IR3894.  It appears that at the highest load and lowest input voltage the efficiency is down by 1.2%

Watch the Sunrise

While I was sitting there at my desk thinking about what to do next, I decided that the best thing to do was go sit in my hottub and watch the sunrise on God’s country.

USB C Power Meter Tester

I was hoping to be able to measure the input current and voltage from the Type-C power supply so that I could calculate the efficiency of the CY4533 EZ BCR.  And as a result the efficiency of the whole system.  There wasn’t a place on the Type-C development kit to make these measurements, but the Cypress Apps manager for Type-C – Palani – said I should buy something like this from Amazon.

 

So I did.  You can plug it into Type-A or Type-C and it will tell you how much V/I are coming out.  In the picture below you can see 20.4v@0.11A

Even better it has a handy-dandy mode where it can display Chinese?

Here is a picture in my actual setup:

And a picture of the whole crazy setup.

Now I step through my 12 load conditions from 1A to 12A and record the V/I from the Fluke and the USB Power Tester.

Here is the data in table form with power and efficiency added.

Type C Power Tester
Vin Iin Win Eff-No Meter
11.99 0.15 1.7985 66.5%
11.95 0.26 3.107 77.0%
11.92 0.36 4.2912 83.7%
11.88 0.48 5.7024 84.0%
11.85 0.59 6.9915 85.7%
11.82 0.7 8.274 86.8%
11.79 0.82 9.6678 86.7%
11.75 0.94 11.045 86.8%
11.71 1.07 12.5297 86.0%
11.68 1.2 14.016 85.5%
11.64 1.33 15.4812 85.1%
11.6 1.46 16.936 84.9%

Next, I plot the new data with the previous two plots.  Obviously, it is screwed up.  I would bet money that the data points at 2A, 4A and 12A are wrong.  But, I don’t think that it is worthwhile to take steps to figure out the real current.  So, I suppose that is what you get from a $19 power meter.

Efficiency of CY4533 EZ-PD BCR

I had really wanted to measure the efficiency of the BCR setup.  To do that I needed to be able to measure the output power (V/I) and the input power (V/I).  Unfortunately the power meter doesn’t seem to be very good… so I suppose that I will have to wait to build my real board where I can install some power jumpers the real numbers.

Cypress Type-C Barrel Connector Replacement + Infineon Buck DC/DC (Part 2)

Summary

In this article I will show you how to use a Keithley 2380 (actually two different ones) to test the output of the IRDC3894 12V->1.2V 12A buck development kit.

The Story

To this point I have written several articles about my process of designing a power supply for my new IoT device.  It needs to provide for quite a bit of power, actually 60W is what I am planning on.  I really wanted to make sure that the IR3894 chip would do what it says it would, specifically supply 12A.  The development kit is pretty simple.  There are two banana plug to  provide power to Vin and two banana plus for the load.

For this round of tests I will Keithley 2230-30-1 to provide power and I will use my Keithley 2380-120-60 to serve as the load.

The two mini grabbers are attached to to remote sensing terminals on the Keithley 2380.

After I had it all hooked up I went in 1A increments from 0 to 12A, then I went in 0.1A increments until I ran out of input power.

Here is the actual data table.  Note that I added columns to show the calculated input power.  And I calculated the efficiency of the system Wout/Win

Vin Iin Win Vout Iout W Eff
12 0.27 3.24 1.198 0 0 0%
12 0.129 1.548 1.198 0.998 1.195604 77%
12 0.239 2.868 1.198 1.998 2.393604 83%
12 0.345 4.14 1.198 2.998 3.591604 87%
12 0.454 5.448 1.198 3.998 4.789604 88%
12 0.564 6.768 1.198 4.999 5.988802 88%
12 0.677 8.124 1.198 5.998 7.185604 88%
12 0.792 9.504 1.198 6.998 8.383604 88%
12 0.909 10.908 1.198 7.998 9.581604 88%
12 1.029 12.348 1.198 8.998 10.779604 87%
12 1.152 13.824 1.198 9.999 11.978802 87%
12 1.277 15.324 1.198 10.998 13.175604 86%
12 1.406 16.872 1.198 11.997 14.372406 85%
12 1.42 17.04 1.198 12.098 14.493404 85%
12 1.434 17.208 1.198 12.198 14.613204 85%
12 1.448 17.376 1.198 12.297 14.731806 85%
12 1.462 17.544 1.198 12.398 14.852804 85%
12 1.477 17.724 1.198 12.498 14.972604 84%
12 1.49 17.88 1.198 12.59 15.08282 84%

When I plot the data there is something sticking out like a sore thumb.  WTF?  At first I assume that I typed in the wrong number when I transposed the hand written data to the spreadsheet.  So I went and looked at the data table where it appears that I typed it in correctly.  Does the efficiency really have a peak like that?

I decided to go remeasure the 5A datapoint.

Then I looked at my handwritten data sheet where I find that I transposed the last two digits of the input current. (I definitely should automate this measurement)

OK… now the plot looks way better

When I compare the plot from the data sheet versus my data on the same scale (about) they look very similar.  All seems good.

 

Stupid Python Tricks: Install MBED-CLI on macOS

Summary

This article describes a good set of steps to install the ARM MBED-CLI onto your Mac into a Python Virtual Environment.  You COULD make MBED-CLI work on your Mac a bunch of other/different ways, but, what I describe in this article is the best way.  You could follow the specific ARM instructions here.

The big steps are:

  1. Install Homebrew
  2. Install Python3
  3. Create a Virtual Python Environment
  4. Use PIP to Install MBED-CLI
  5. Download the GCC Compiler
  6. Configure the Compiler
  7. Test

macOS Homebrew & Python3

You need to be able to run Python, and in my opinion, specifically Python 3.  There are problems with doing this, first most of the macOS versions do not have Python 3 built in (and the Python 2 is old).  But, on macOS Catalina, you could use the macOS built-in Python3 and install MBED-CLI into the system libraries.  But, I believe that you should never make changes to the macOS system which could collide with its normal functioning.  I think that the easiest way to get a standalone Python onto your computer is to use “Homebrew” package manager.   Homebrew will enable you to easily install Python3 (and a bunch of other stuff) into /usr/local/bin.

To install Homebrew run:

  • /usr/bin/ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”

And after a bunch of streaming console messages, it finishes:

Now you can use Homebrew to install python3 into /usr/local/bin by running:

  • brew install python3

And it puts out a bunch of stuff on the console… finally ending like this:

Now you should be able to “which python3” and see that python 3.7.6 is installed.

Create a Virtual Environment and Install mbed-cli

I always use a virtual environment when I run python to insure that all of the packages I install are together.  Run:

Command Comment
mkdir mbed-cli-example Create a directory to hold the virtual environments
cd mbed-cli-example
python3 -m venv venv Create the virtual environment in the directory called "venv"
source venv/bin/activate Activate the virtual environment
pip install mbed-cli Download the Mbed-cli package and install it into the virtual environment

Here is what is looks like:

Once that is done you can run “which mbed” where you will find that the MBED-CLI is in your virtual environment path.  And when you run “mbed” you should see all of the options.

Download the GCC Compiler

In order to actually do something with the MBED-CLI you also need to have an ARM compiler toolchain installed e.g. GCC.  The easiest (best?) way to get this done is download it from this the ARM website here.

After you download the Mac OS X 64-bit Tarball file you will end up with a “…tar.bz2” file in your Downloads directory.  When you double click the file, the finder will unzip and untar the file into a folder with all of the stuff you need for the Mac.

I have a directory on my Mac called ~/proj where I hold all of my “project” stuff.  So after downloading and untaring the file, I move it to my project directory with “mv ~/download/gcc-arm-none-eabi-9-2019-q4-major ~/proj”

Configure the Compiler

The final step is to tell the MBED-CLI where your toolchain is located.  There are a bunch of slightly different variants for how to do this.  BUT I think that the best is to change the mbed global configuration by running:

  • mbed config –global GCC_ARM_PATH ~/proj/gcc-arm-none-eabi-9-2019-q4-major/bin

The “–global” flag will store that configuration variable in the file ~/.mbed/.mbed (notice a directory called .mbed and a file called .mbed)

It took me a while to figure out which directory you should put in the path because there are multiple bin directories in the “gcc-arm…” directory.

If you want to use one of the other toolchains mbed has configuration variables for those as well:

You can also configure these paths with environment variables, but I dont like that.

macOS Gatekeeper

On macOS there is a security infrastructure called Gatekeeper that will prevent you from running applications that Gatekeeper doesn’t know about.  This includes the GCC toolchain (which you just downloaded).  To make it so that you can run the toolchain there are a couple of things you COULD do, but I think that the best thing to do is tell Gatekeeper that the Terminal program is exempt from the Gatekeeper rules.

To do this type the following command into a terminal: “spctl developer-mode enable-terminal”

Then go to the System Preferences –> Security & Privacy –> Developer Tools and check that the “Terminal” is exempt from the Gatekeeper Rules.

You can read more here and here.

Test

To test everything you have done, you should:

Command Comment
mbed import mbed-os-example-blinky Load the example project.  Notice that mbed also will install the Python requirements into your virtual environment.  It does this by running "pip install -r mbed-os/requirements.txt"
cd mbed-os-example-blinky
mbed config target CY8CKIT_062S2_43012 Setup the target (in my case the really cool P6 43012 devout)
mbed config toolchain GCC_ARM Set the toolchain to the one we setup in the previous step
mbed detect See if mbed can detect the development kit… it can

Here is what it looks like:

You can now run the compiler (with the -f option to program).  It should start up and barf out the compile messages.

Eventually it ends… then programs the development kit

The last thing to do is turn off the virtual environment

Which can later be turned back on with “source venv/bin/activate”

PyVISA First Use

Summary

In this article I will show you how to install PyVISA, and use it to control to a Keithley 2230-30-1 Triple Channel DC Power Supply.

The Story

I have been working on a power supply for a new IoT device that I am building you can read about it here.  This power supply uses an Infineon IR3894 DC buck converter to turn 20V from a Type-C wall wart into a usable 5V for my system.  In the IRDC3894 data sheet there is a graph of efficiency versus load current that I would like to duplicate with real measurements.

In order to make this graph you need to:

  1. Turn on the Keithley 2280-30-1 power supply (that is simulating the Type-C power supply)
  2. Set the Keithley 2380-500-15 load to +1.2A (starting at 0)
  3. Read the input voltage and current
  4. Read the output voltage and current
  5. Loop back to (2) until you get to 12A
  6. Calculate the input and output power at each of the point
  7. Plot the data

Obviously this could all be done by hand… but I know that my Power supply and DC load are both programmable over “GPIB”.  I also know that theoretically I could get National Instruments Labview and it would probably know how to do something like that.  BUT… what I really wanted was to write code in Python.

So I posted on forum.tek.com and their excellent Apps engineer “Andrea C.” replied:

I have another (future) article where I will write extensively about the bewildering jungle of Test Automation.  But for today Ill say that VISA stands for Virtual Instrument Software Architecture which is basically a standard way to talk to Lab Instruments.  And, NI-VISA is the National Instrument implementation of VISA.  But wait, a quick google search lead me to the Open Source project PyVISA.  So, I’ll start there.

There will also be a future in-depth discussion of PyVisa, but this is the first step.

PyVISA

For this example, I’ll show you the simple steps of turning on the power supply and printing out the current, voltage and calculated power as retrieved from the power supply.

I always like to use a virtual environment for working with Python on my MacBook (or any computer for that matter).  So, I start by creating the virtual environment.  Then I run the PIP installer to install PyVisa (and all of its requirements).

The specific commands are:

Command Comment
python3 -m venv venv Create a Python3 virtual environment
source venv/bin/activate Turn on the virtual environment
pip install pyusb Install the Python usb interface (so that PyVISA can search for devices on the USB bus)
pip install pyvisa Install the PyVisa environment
pip install pyvisa-py Install the low level python drivers for PyVisa to use to talk to the bus

Then I tested the installation by running

Command Comment
python Startup an interactive Python environment
import visa Load the PyVISA module
rm = visa.ResourceManager() Attach to the interface
rm.list_resources() Get a list of all VISA devices attached to the interface

Here is what the screen looks like:

SCPI & Keithley 2230-30-1

OK, we have a working Python environment that can talk to the Power Supply.  Now what? In the unbelievable chaos of talking to Test Instruments, the vendors got together and agreed to a “Standard Commands for Programmable Instruments” also called SCPI.  In order to learn about SCPI (in the context of this power supply) you can read Chapter 4 of the Keithley Power Supply user manual.  This chapter describes how commands work etc.

After the general section there is an entire chapter (5) that discusses the specific commands for this instrument.

And if you look down into the document you will find a description of each of the commands.  For instance if you issue the command “SYS:REM” it will let you take control of the instrument.

Run the Test

Now that we know everything is working, I write a Python program to do the needful.

When you start the program the power supply is off and the load is set to 2A (but has no power being applied)

When you run the program it turns on the power supply and grabs control of device.  The little symbol next to the 12 says that the device is being control via USB.

Finally here is the source code:

import visa
import time
rm = visa.ResourceManager()
vm = rm.open_resource('USB0::1510::8752::9202095::0::INSTR')

# Turn on remote mode (so that SCPI commands work)
vm.write('SYST:REM')
vm.write('APPL CH1, 12V, 1A')
vm.write('OUTP ON')
print('Power on... waiting for settling')
# need a little delay right here
time.sleep(3)

print('Taking measurements')
voltCH1 = float(vm.query('MEAS:VOLT? CH1'))
currentCH1 = float(vm.query('MEAS:CURR? CH1'))
powerCH1 = voltCH1 * currentCH1

print(f'V={voltCH1:.1f}V I={currentCH1:.2f}A P={powerCH1:.2f}W')

vm.write('OUTP OFF')
print('Turned off power')

 

Keithley 2380-500-15 Programmable Load Remote Sensing

Summary

In this article I will show you how to install and use the remote sensing inputs for the Keithley 238-500-15 Programmable Load.

The Problem

In a previous article I talked about a power supply that I was working on.  In that design, I will be taking 20V@3A from a USB Type-C power supply and turning it into 5V@12A for use to drive a PSoC and a bunch of LEDs.  For testing the setup, I use a Keithley 2380-500-15 Programmable Load that I got from Mouser.  The device is really cool as it can be programmed to provide constant current, constant resistance, constant power or constant voltage.  For this test, I am using the 2380-500-15 as constant current load to pull current out of the Infineon IRDC3894 development board which produces 1.2v@12A.

In my first setup I got this result:  The board is generating 1.2V (which you can see on the top DMM).  And the 2380-500-15 is showing 1.204V.  The same, thats good.

But, when I turn on the current to 1A you can see that the 2380-500-15 is showing 1.15V.  What happened?  Is the power supply drooping?

And it gets worse at 4A

So, I posted on the Tek Forum, and the excellent Tek engineer Andrea C. gave me an answer and posted a nice picture.  The problem is that my test wire to the 2380-500-15 has resistance and the voltage is dropping through the test wire.  You mean test-wires have resistance 🙂  Here is the picture that she posted.

The Solution

So, what is the answer to this problem?  Remote sensing which will display the voltage at the source instead of at the end of the wire.  In the world this method is more commonly called “4 terminal sensing” or “Kelvin Sensing”

The 2380 is capable of using this method.  And it is described (poorly) on page 52 of the user guide.  Here is the picture:

To make this work you need to do two things

  1. Attach two wires on the back panel sensing connection
  2. Switch the load into remote sensing mode.

Back Panel

When you look at the back panel you see this crazy terminal block.  The first thing you notice is that some of the screw terminals are blocked by the BNC connector.  And the angle for putting a screw driver isn’t great.

But, if you loosen the two flat head screws on the far left and right of the green connector, you will be able to pull it out.  It has a little bit of “snap” so you need to pull on it a bit (just with your fingers so you don’t break it).  You will then be able to see into the connector.

Here is what the green connector looks like once it is removed.

Notice that each of the terminals have a screw to tighten the corresponding wire.

In order to hook to the screw terminals you need a cable.  I bought this one from Mouser

Then I clipped off the Banana plug, and tinned the ends of the wire with solder (you can see the black wire is a bit flattened because I took this picture after I unhooked the wire)

Then screwed the wires into the connector

Finally screwed the connector back into the meter.

Enable Remote Sensing

To enable remote sensing, the documentation tells you what to do:

Here are pictures of the screens.  When you press Shift-9 you get this screen.  You need to press the “right arrow” to go to the next screen.

Select the “Remote-Sense” and press enter.

Use the left/right arrow to select “On” and press the “Enter”

Results

Now it displays “1.204V” which matches the DMM (well pretty close)