IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

Summary

In this lesson we will add an output capability to our project.  But what output device?  I am going old school with a serial terminal using VT100 graphics commands.

Here is the update to the architecture.  Notice the new green boxes.

  1. A new thread (called Display Thread)
  2. The Serial terminal
  3. An RTC (the one built into the PSoC 6)
  4. An RTOS Queue called “Display Command Queue”
  5. A function called displaySendUpdateTime (to tell the display to show the time)

The way this will work is a new thread called “displayThread” will be created.  I will have an RTOS queue associated with it.  Tasks that want to display something will push a message into the queue.  The display thread will wait around until some other task pushes a message into the queue.  When another task pushes a message, the displayThread will process the message and “do the needful”.

To implement this I will:

  1. Import from GitHub lesson02
  2. Discuss VT100 Commands
  3. Create & code displayThread.h
  4. Create & code displayThread.cpp
  5. Create & code mbed_app.json
  6. Update blinkThread.cpp
  7. Update main.cpp
  8. Build, Program and Test

Import from GitHub Lesson 02

Mbed Studio can start a new project by “importing” an old project and giving it a new name.  This is a super convenient way for me to build each project for this class on the base of the previous one.  To make this easier for you I save each of these lesson projects on GitHub so you can start from there.

In Mbed Studio use “File->Import Program…”

Give it the path to my github site for the project you want to start from:

https://github.com/iotexpert/mouser-mbed-02.git

And then give it the new name you want for the new project – in this case mouser-mbed-03

After clicking “Add program” you should have a new program called “mouser-mbed-03” and it should be set as the active program.

At this point you could press the play button and get exactly the same behavior as the previous lesson 02.

VT100 Commands

Back in the dark ages when I started programming, most work was done on a “main frame” of some kind or the other.  The two that I used were the PDP-11 and the Prime.  To access these systems you typically used a “Dumb Terminal” attached to the mainframe via a long serial cable.  One of the most used terminals was the VT100 made by Digital Equipment Corporation (DEC).

These terminals could output a fixed width font in about 80 columns and 24 rows.  Basically, the mainframe sent ASCII characters and the terminal would display them.

An interesting feature that was put into these terminals was the ability to take commands and move the cursor around to screen, or clear it, or draw a line or ….

What does that have to do with today?  Simple, the VT100 command set is still used by almost all serial programs on your PC (Putty, etc).  And I am going to use these commands to create a graphical user interface for the thermostat that looks like this:

  • Line 1 will be the current temperature
  • Line 2 will be the setPoint of the thermostat
  • Line 3 will be the time
  • Line 4 will be the status of the system (Heat, Cool or Off)

To use the VT100 commands you just use printf to send them to the serial terminal.  The commands I will use are:

Command ESC Sequence printf
Home ESC [ H printf("\033[H");
Clear Screen ESC [ J printf("\033[2J");
Turn Cursor Off ESC [ ? 25l printf("\033[?25l");
Goto column & row ESC [ column ; row H printf("\033[%d;%dH%s",y,x,buffer);

Add displayThread.h

As in lesson 02 I will create a new thread.  This time the header file will be called “displayThread.h”  Right click and select “New File”

Give it the name “displayThread.h”

This file will:

  1. Guard against multiple include
  2. Provide the function prototype of the thread
  3. Provide four functions to tell the displayThread to write onto the four different lines of the GUI
#ifndef DISPLAY_THREAD_H
#define DISPLAY_THREAD_H

void displayThread();


void displaySendUpdateTemp(float temperature);
void displaySendUpdateTime();
void displaySendUpdateSetPoint(float setPoint);
void displaySendUpdateMode(float mode);

#endif

Add displayThread.cpp

From now on I will just show the file creation.  In this case “displayThread.cpp”

The first part of the file is to specify the queue.  To do this I create a new type called msg_t which will be inserted into the queue (actually a pointer to the msg_t).  The message structure will have two members the “command_t” and a generic floating point variable called “value”.

The Queue and Memory pool will only be accessible inside of this file.  The queue will hold up to 16 “msg_t”.  The MemoryPool is just an easy  way to do memory allocation.

#include "mbed.h"
#include "displayThread.h"

typedef enum {
    CMD_temperature,
    CMD_setPoint,
    CMD_time,
    CMD_mode,
} command_t;


typedef struct {
    command_t cmd;
    float    value;
} msg_t;


static Queue<msg_t, 16> queue;
static MemoryPool<msg_t, 16> mpool;

In order to insert msg_t into the queue I create one function per msg_t that:

  • allocates a new message
  • sets the command
  • sets the value
  • puts the message in the queue

Notice if something goes wrong, I just ignore the problem.  In a real system you would probably want to do something else.

These four functions will actually run inside of the thread that calls them, but they will insert messages into the queue that the displayThread is processing.  This queue mechanism is a safe way for processes to talk to each other.

// Function called by other threads to queue a temperature change onto the display
void displaySendUpdateTemp(float temperature)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_temperature;
        message->value = temperature;
        queue.put(message);
    }
}

// Function called by other threads to queue display to update time
void displaySendUpdateTime()
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_time;
        message->value = 0;
        queue.put(message);
    }
}

// Function called by other threads to queue a setPoint change onto the display
void displaySendUpdateSetPoint(float setPoint)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_setPoint;
        message->value = setPoint;
        queue.put(message);
    }
}

// Function called by other threads to queue a setPoint change onto the display
void displaySendUpdateMode(float mode)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_mode;
        message->value = mode;
        queue.put(message);
    }
}

The function displayAtXY creates a string with the VT100 Goto command plus the string that was send.

static void displayAtXY(int x, int y,char *buffer)
{
    // row column
    printf("3[%d;%dH%s",y,x,buffer);
    fflush(stdout);
}

The actual display thread:

  • Clears the screen and turns the cursor off
  • Goes into an infinite loop waiting for messages to be put into the queue
  • When a new message comes in, look at the command
  • sprintf to create the display message
  • print it using the displayAtXY function
void displayThread()
{
    char buffer[128];

    printf("3[2J3[H"); // Clear Screen and go Home
    printf("3[?25l"); // Turn the cursor off
    fflush(stdout);

    while(1)
    {
        osEvent evt = queue.get();
        if (evt.status == osEventMessage) {
            msg_t *message = (msg_t*)evt.value.p;
            switch(message->cmd)
            {
                case CMD_temperature:
                    sprintf(buffer,"Temperature = %2.1fF",message->value);
                    displayAtXY(1, 1, buffer);
                break;
                case CMD_setPoint:
                    sprintf(buffer,"Set Point = %2.1fF",message->value);
                    displayAtXY(1, 2, buffer);
                break;
                case CMD_time:
                    time_t rawtime;
                    struct tm * timeinfo;
                    time (&rawtime);
                    rawtime = rawtime - (5*60*60); // UTC - 4hours ... serious hack which only works in winter
                    timeinfo = localtime (&rawtime);
                    strftime (buffer,sizeof(buffer),"%r",timeinfo);
                    displayAtXY(1,3, buffer);
                break;
                case CMD_mode:
                    if(message->value == 0.0)
                        sprintf(buffer,"Mode = Off ");
                    else if (message->value < 0.0)
                        sprintf(buffer,"Mode = Heat");
                    else
                        sprintf(buffer,"Mode = Cool");
                    displayAtXY(1, 4, buffer);
                break;

            }
            mpool.free(message);

        }
    }
}

Here is the whole file displayThread.cpp (to make a copy/paste easier):

#include "mbed.h"
#include "displayThread.h"
typedef enum {
CMD_temperature,
CMD_setPoint,
CMD_time,
CMD_mode,
} command_t;
typedef struct {
command_t cmd;
float    value;
} msg_t;
static Queue<msg_t, 32> queue;
static MemoryPool<msg_t, 16> mpool;
// Function called by other threads to queue a temperature change onto the display
void displaySendUpdateTemp(float temperature)
{
msg_t *message = mpool.alloc();
if(message)
{
message->cmd = CMD_temperature;
message->value = temperature;
queue.put(message);
}
}
// Function called by other threads to queue display to update time
void displaySendUpdateTime()
{
msg_t *message = mpool.alloc();
if(message)
{
message->cmd = CMD_time;
message->value = 0;
queue.put(message);
}
}
// Function called by other threads to queue a setPoint change onto the display
void displaySendUpdateSetPoint(float setPoint)
{
msg_t *message = mpool.alloc();
if(message)
{
message->cmd = CMD_setPoint;
message->value = setPoint;
queue.put(message);
}
}
// Function called by other threads to queue a setPoint change onto the display
void displaySendUpdateMode(float mode)
{
msg_t *message = mpool.alloc();
if(message)
{
message->cmd = CMD_mode;
message->value = mode;
queue.put(message);
}
}
static void displayAtXY(int x, int y,char *buffer)
{
// row column
printf("\033[%d;%dH%s",y,x,buffer);
fflush(stdout);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void displayThread()
{
char buffer[128];
printf("\033[2J\033[H"); // Clear Screen and go Home
printf("\033[?25l"); // Turn the cursor off
fflush(stdout);
while(1)
{
osEvent evt = queue.get();
if (evt.status == osEventMessage) {
msg_t *message = (msg_t*)evt.value.p;
switch(message->cmd)
{
case CMD_temperature:
sprintf(buffer,"Temperature = %2.1fF",message->value);
displayAtXY(1, 1, buffer);
break;
case CMD_setPoint:
sprintf(buffer,"Set Point = %2.1fF",message->value);
displayAtXY(1, 2, buffer);
break;
case CMD_time:
time_t rawtime;
struct tm * timeinfo;
time (&rawtime);
rawtime = rawtime - (5*60*60); // UTC - 4hours ... serious hack which only works in winter
timeinfo = localtime (&rawtime);
strftime (buffer,sizeof(buffer),"%r",timeinfo);
displayAtXY(1,3, buffer);
break;
case CMD_mode:
if(message->value == 0.0)
sprintf(buffer,"Mode = Off ");
else if (message->value < 0.0)
sprintf(buffer,"Mode = Heat");
else
sprintf(buffer,"Mode = Cool");
displayAtXY(1, 4, buffer);
break;
}
mpool.free(message);
}
}
}

Create mbed_app.json

By default the serial terminal on Mbed OS is 9600 baud or approximately the same speed as back in VT100 days.  Fortunately you can change that by setting up a global configuration.  To do this you need to create a file called “mbed_app.json”.

In that file, I tell the system that I want 115200 baud for all targets.

{
"target_overrides": {
"*": {
"platform.stdio-baud-rate": 115200
}
}
}

Update blinkThread.cpp

In order to test the new displayThread, I will fix up the blinkThread to temporarily send the four messages.

#include "mbed.h"
#include "blinkThread.h"
#include "displayThread.h"
static DigitalOut led1(LED1);
void blinkThread()
{
while (true) 
{
led1 = !led1;
displaySendUpdateTime();
displaySendUpdateTemp(69.2);
displaySendUpdateSetPoint(23.5);
displaySendUpdateMode(-1.0);
ThisThread::sleep_for(500);
}
}

Update main.cpp

The last step is to update main to start the new displayThread.

#include "mbed.h"
#include "blinkThread.h"
#include "displayThread.h"
Thread blinkThreadHandle;
Thread displayThreadHandle;
int main()
{
printf("Started System\n");
blinkThreadHandle.start(blinkThread);
displayThreadHandle.start(displayThread);
}

Build, Program and Test

Now press the play button.

One nice thing in Mbed Studio is a built-in serial port viewer.  You can use it by clicking “View -> Serial Monitor”

Change the baud rate to 115200 and you should see something like this:

Recommended Posts

8 Comments

    • Almost 100% sure that this is a baud rate problem.
      Alan

  1. It seems like it was a Windows problem – because after a few manipulations, there was nothing at all in the terminal window.
    The next day, I tried it one more time and it worked.
    Thanks.

    • Switch to Mac 🙂
      Im glad you sorted it out.
      Alan

  2. Hi Alan,
    my company is designing a new IoT product, and our development is based on CY8CPROTO-06204343W Wi-Fi BT Prototyping Kit. We are employing the ARM mbed Studio for firmware development.

    My question is, if there is available -form CYPRESS or from a partner- a module compatible with specific proto kit, or we should have to design from scratch-pad the hardware? You know it would be great it should available a module containing at least the PSoC6 and the 4343 radio chip in a package.

    Best Regards
    Bill Kalompatsos

    • Ill respond via a Cypress email (actually a Cypress person)

  3. Hi Alan,
    I subscribe to the same question. I wish there is a way to avoid having to reinvent the wheel and be able to buy the module in CY8CPROTO-062-4343W already assembled and tested, My guess is that Cypress builds them by the thousands at a time. When you have very small volume the time and cost to build it from scratch skyrockets !


Add a Comment

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