Calibration in a Storm

Summary

A description of an analytic model to adjust pressure sensor depth data to reflect measured data.

Story

About a year ago I repaired my Creek Water Level sensing system.  At that time I installed a new pressure sensor into the system (which had been blown up).  When I did the surgery I did not have data to recalibrate the system.  All along I knew that the system was reading low by about 1 foot or so.  I am  in America, I do Imperial measurement 🙂  But I didn’t really know exactly how much.  I also knew that my original calculate used 0.53ft/psi as the conversion to depth.  This is only true with pure water at about 80 degrees F which meant that it was something else for muddy creek water.

Well on Wednesday last week I had a flood.  So I got the opportunity to collect some real data on the conversion

Old School Measurement

A couple of years ago a friend an I went out with a site level and measure a bunch of marker points, including the base of this treehouse which I know is 12.6 feet over the normal creek level.  When I woke up and saw that the flood was going strong I went out with a long ruler and screwed it into the post holding up the birdhouse.

Here is how it looks close up.

Collect the Data

Unfortunately as the water went higher and higher the only way to collect the data was with a pair of (bad-ass) binoculars.

Over the course of the flood, my children and I would occasionally go out and collect the data.

The next morning I did two things.

  1. Entered the data into a table.
  2. Used mysql to look up the sensor readings at the same time we took the measurement.

Here is the data:

Time Ruler Ruler + BH Sensor Measure
8:15 5 23 13.0 14.6
8:52 7 25 13.4 14.8
9:34 10 28 13.3 15.0
10:17 12 30 13.5 15.2
10:46 13 31 13.6 15.3
12:54 18 36 14.0 15.7
13:43 21 39 14.2 16.0
15:00 26 44 14.7 16.4
16:18 30 48 15.0 16.7
17:50 33 51 15.2 17.0
7:55 15 33 13.8 15.5

Analyze the Data

The next step was to analyze the data.  So, I created an x-y plot.  Notice the red datapoint almost certainly was read in error.  The dotted line is an excel created least squares fit of the data.

When I remove the red dot I get a correlation coefficient of 0.9951 … that is money in my business.

Now when I create column the new model you can see that all of the datapoints are within 1%.

Time Ruler Ruler + BH Sensor Measure Model Error RMS
8:15 5 23 13.0 14.6 14.7 0.8%             0.0
8:52 7 25 13.4 15.1
9:34 10 28 13.3 15.0 15.0 -0.3%             0.0
10:17 12 30 13.5 15.2 15.2 -0.2%             0.0
10:46 13 31 13.6 15.3 15.3 -0.1%             0.0
12:54 18 36 14.0 15.7 15.7 -0.2%             0.0
13:43 21 39 14.2 16.0 15.9 -0.5%             0.0
15:00 26 44 14.7 16.4 16.4 0.36%             0.0
16:18 30 48 15.0 16.7 16.7 0.10%             0.0
17:50 33 51 15.2 17.0 17.0 0.02%             0.0
7:55 15 33 13.8 15.5 15.5 0.1%             0.0
0.011

Here is a plot of the error:

Fix the Firmware

The next step is to update the firmware on sensor system.  The comment on line 56 of the code “USC correction model” means that I talked with the guy in charge of transistor device modeling at Infineon/Cypress.  He suggested some improvements from my original analysis.

dp.pressureCounts = adc_GetResult16(PRESSURE_CHAN);
            // 408 is the baseline 0 with no pressure = 51.1ohm * 4ma * 2mv/count
            // 3.906311 is the conversion to ft
            // Whole range in counts = (20mA - 4mA)* 51.1 ohm * 2 mv/count = 1635.2 counts
            // Range in Feet = 15PSI / 0.42PSI/Ft = 34.88 Ft
            // Count/Ft = 1635.2 / 34.88 = 46.8807 Counts/Ft 
            
            float depth =( ((float)dp.pressureCounts)-408)/46.8807;
            
            // Apply the USC correction model
            depth = 1.0106 * depth + 1.5589;
            
            dp.depth = ( depth + 7*previousDepth ) / 8.0;  // IIR Filter

The last thing to do is fix all of the old data in my database.  So I use mysql to update all of the datapoint with the adjusted values since I installed the new sensor.

update creekdata set depth=depth*1.016 + 1.5589 where id>=1749037 AND id<= 1981538;

This morning while I was doing the updates the creek started flooding again.  Here is the plot where you can see the offset being applied.

And with the offset applied, things are “more better” as my mom would say.

 

PSoC 6 TCPWM Pulse Width Measurement

Summary

This article walks you through a PSoC 6 SDK example of measuring a pulse width using the TCPWM block’s counter function.

The Story

Recently I have been working with a Cypress guy and frequent collaborator Hassane.  He is working on a project that includes some kind of sonic distance sensor (I am actually not sure which one).  In order to “read” the sensor you need to be able to measure the width of a pulse which is easy enough to do using the TCPWM.  I originally wanted to use the Cypress HAL to setup the PSoC 6 to do this function, but it is not yet enabled.  So, I need to use PDL and the PSoC 6 Configurator.

Configure The TCPWM

Start with a new PSoC 6 project.  In my case I am using the CY8CKIT-062-WiFi-BT development kit because I had one where I had already soldered a wire onto the user switch.  My plan is to create a “pulse” with the user switch, starting from the press, ending with the user letting go.  This will be an active low pulse.  I will configure the TCPWM to have start, reload setup as Falling edges.  In other words the counter will reset and start counting when the button is pressed.  Then I will setup Stop and Reload on Rising edges, in other words the timer will STOP and give an interrupt on the rising edge.  All of these pins are connected to P9[0] on the PSoC.

You might ask yourself, why P9[0]?  That development kit has a switch connected to P0[4] seems like you should have connected the TCPWM to that pin.  That was done because the TCPWM is only attached to a limited set of signals.  Here is a screenshot from the configurator.

I want this setup to be interrupt based, specifically I want an interrupt when the Capture signal is active.  It is also interesting when the counter rolls over (overflow).  So I setup the interrupt source to be “Overflow & Underflow or Compare & Capture”.

The last thing you need is a clock.   you now need a clock.  To do this clock on the Clock Signal.  For this example I decided to use a 16-bit clock.

But what frequency is the input clock?  To set this go to the “Peripheral-Clocks”  The click on the divider you choose from above.  Then pick a divide value.  I choose 10000 which will give me 100MHz/10K=10KHz.

But where did the 100MHz come from?  Click on the “System” and look at the clock diagram.  All of the “Peripheral Clocks” use the signal “CLK_PERI” as their source.  This is attached to CLK_HF0 (which is 100Mhz) in this case.

The Program

Once is everything is configured you need to write a bit of code.

9-14: First I create an ISR which will set a flag and clear the interrupt source in the TCPWM

Then I write a main function

20: Turn on the printf functionality using the cy_retarget_io library

25: Initialize the counter using the structure created by the configurator.

26: Then I enable the counter to run.  Be careful with this function because counter numbers are NOT the same thing as counter masks.  Counter 3 is counter mask 1<<3=8

28-30: Turn on the interrupts and register my ISR called counterDone

34: In the loop I wait around until the flag gets set by the ISR

36: Read the value from the counter and convert it to seconds

37: Print the value

38: Reset the flag

And do it all over again

#include "cy_pdl.h"
#include "cyhal.h"
#include "cybsp.h"
#include "cy_retarget_io.h"
#include <stdio.h>

volatile int flag=0;

void counterDone()
{
    uint32 intCause = Cy_TCPWM_GetInterruptStatusMasked(MYC_HW,MYC_NUM);
    flag = 1;
    Cy_TCPWM_ClearInterrupt(MYC_HW,MYC_NUM,intCause);
}

int main(void)
{
    cybsp_init() ;
    
    cy_retarget_io_init(CYBSP_DEBUG_UART_TX, CYBSP_DEBUG_UART_RX, CY_RETARGET_IO_BAUDRATE);
    __enable_irq();

    printf("Started\n");

    Cy_TCPWM_Counter_Init(MYC_HW,MYC_NUM,&MYC_config);
    Cy_TCPWM_Enable_Multiple(MYC_HW,MYC_MASK);

    cy_stc_sysint_t mycounter= {.intrSrc = MYC_IRQ, .intrPriority=7};
    Cy_SysInt_Init(&mycounter,counterDone);
    NVIC_EnableIRQ(MYC_IRQ);

    for(;;)
    {
        if(flag == 1)
        {
            float val = (float)Cy_TCPWM_Counter_GetCounter(MYC_HW,MYC_NUM) / 10000.0;
            printf("%.2fs\n",val);
            flag = 0;
        }
    }
}

Test

For an earlier article I had to solder a wire onto the switch.  This let me attach it to an oscilliscope.  It turns out that was an easy way to attach the switch to P9[0]

When I run the project I get a bunch of different pulse.  Looks good.

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