Lesson 4 – WICED Bluetooth: Using Snips

WICED Bluetooth Using the CYW20719

# Title Comment
0 A Two Hour WICED Bluetooth Class WICED Bluetooth Using the CYW20719 in all its glory
1 Resources Links to all of the Cypress WICED information including videos, application notes etc.
2 Your First Project Making Hello World & the Blinky LED
3 The Super Mux Tool Learning about platforms and the Super Mux Tool
4 Snips Using the example projects to learn Bluetooth
5 Bluetooth Designer Using the tool to customize a project and get going fast
6 The CCCD & Notification Writing back to the Central
7 Advertising  Beacon Building a beacon project to advertise your custom information 
8 Scanner Viewing the world around you
9 Bluetooth Classic SPP Using the Serial Port Profile to Transmit Lots of Data

Source code: 

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

 

Summary

In this lesson I am going to show you how to NOT write all of your own code and still get the job done.  In this lesson we are going to do three things.

  1. Examine & Run the hal_gpio snip
  2. Examine & Run the hal_i2c_master snip
  3. Copy the hal_i2c_master snip and make it “more better”

To modify the hal_i2c_master snip I will

  1. Make a new folder called L4_Accelerometer
  2. Copy the makefile.mk and hal_i2c_master.c into the L4_Accelerometer folder
  3. Create a new make target and make sure that things still work
  4. Look at the LSM9DS1 datasheet
  5. Update the function initialize_app to startup the Accelerometer and speed up the polling
  6. Update the function comboread_cb to read the Acceleration registers and print out the values

Run hal_gpio

If you dont already have a make target for snip.hal.hal_gpio create one and then program the board.

Notice that the light blinking will change speeds if you press the button.  Let’s look at the code that does this:

At the beginning it sets up a timer

        // Initialize timer to control the pin toggle frequency
        if (wiced_init_timer(&hal_gpio_app_timer, &hal_gpio_app_timer_cb, 0, WICED_SECONDS_PERIODIC_TIMER) == WICED_SUCCESS)
        {
            if (wiced_start_timer(&hal_gpio_app_timer, LED_BLINK_FREQ_A_IN_SECONDS) != WICED_SUCCESS)
            {
                WICED_BT_TRACE("Seconds Timer Error\n");
            }
        }

The timer calls this function each time the timer expires.

/*
 * The function invoked on timeout of app. seconds timer.
 */
void hal_gpio_app_timer_cb(uint32_t arg)
{
    static uint32_t wiced_seconds = 0; /* number of seconds elapsed */
    uint8_t index = 0;

    wiced_seconds++;

    if (wiced_seconds & 1)
    {
        for (index = 0; index < sizeof(output_pin_list); index++)
        {
            wiced_hal_gpio_set_pin_output(output_pin_list[index], GPIO_PIN_OUTPUT_LOW);
        }
    }
    else
    {
        for (index = 0; index < sizeof(output_pin_list); index++)
        {
            wiced_hal_gpio_set_pin_output(output_pin_list[index], GPIO_PIN_OUTPUT_HIGH);
        }
    }
}

And when the button is pressed all it does is switch back and forth between two different intervals for the timer.  And after the switch it restarts the timer.

/*
 * Handle interrupt generated due to change in the GPIO state
 */
void hal_gpio_app_interrrupt_handler(void *data, uint8_t pin)
{
    static uint32_t blink_freq = LED_BLINK_FREQ_A_IN_SECONDS;

    // toggle LED blink rate upon each button press
    if (blink_freq == LED_BLINK_FREQ_A_IN_SECONDS)
    {
        blink_freq = LED_BLINK_FREQ_B_IN_SECONDS;
    }
    else
    {
        blink_freq = LED_BLINK_FREQ_A_IN_SECONDS;
    }

    if (wiced_stop_timer(&hal_gpio_app_timer) == WICED_SUCCESS)
    {
        wiced_start_timer(&hal_gpio_app_timer, blink_freq);
    }

    // clear the interrupt status
    wiced_hal_gpio_clear_pin_interrupt_status(pin);
}

Run hal_i2c_master

This CYW920719Q40EVB_01 development kit has an I2C LSM9DS1 accelerometer on it.  And I noticed that when looking around in the snips that the Snip called “hal_i2c_master.c” appears to talk to the chip.  Here is a little section of the comments from the top of the snip

 *
 * WICED sample application for I2C Master usage
 *
 * This application demonstrates how to use I2C driver interface
 * to send and receive bytes or a stream of bytes over the I2C hardware as a master.
 * The on-board LSM9DS1 motion sensor acts as the I2C slave

So, lets run the snip and see what happens.  If you don’t have a make target… well then make one.

Then make the make target.

It turns out that “0” is a bug in the example project.  And printing out the WHO_AM_I register isnt really very interesting.

Modify the hal_i2c_master.c Create a Better Project

I don’t like making changes inside of the WICED SDK files.  But, I want to fix the bug and printout something more interesting.  So start by creating a new folder in the wiced_bt_class folder

Type in the directory name L4_Accelerometer (notice in the screenshot below I mistyped it)

Select the makefile.mk and the hal_i2c_master.c then right click copy the files.

Then select the L4_Accelerometer folder and pick paste.

Create a make target for the L4_Accelerometer

Build it to make sure it still works.

Now that we have a base to stand-on.  Let’s have a look at the data sheet.  I have used these before and I know that you need to turn on the Accelerometer to give you anything interesting.  Turns out CTRL_REG_6_XL is the control register we need.

The other interesting registers are the actual output of the accelerometer.  That is 0x28 –> 0x2D

Start by modifying the function initialize_app to turn on the accelerometer by writing 0x40 to register 0x20

uint8_t status;
    // Turn on Accelerometer - Register 0x20... 2g accelerometer on @ 50hz
    uint8_t data[] = {0x20, 0x40};
    status = wiced_hal_i2c_write(data,sizeof(data),LSM9DS1_ACC_GYRO_I2C_ADDRESS);

I dont really like printing the values every two seconds so I will modify the timer:

  • Make it a milisecond timer
  • Set it to print every 500ms
if ( WICED_SUCCESS == wiced_init_timer( &seconds_timer, &comboread_cb, 0, WICED_MILLI_SECONDS_PERIODIC_TIMER )) {
        if ( WICED_SUCCESS != wiced_start_timer( &seconds_timer, 500 )) {
            WICED_BT_TRACE( "Seconds Timer Error\n\r" );
        }
    }

Here is the whole function initialize_app together

void initialize_app( void )
{
    wiced_hal_i2c_init();
    uint8_t status;

    // Turn on Accelerometer - Register 0x20... 2g accelerometer on @ 50hz
    uint8_t data[] = {0x20, 0x40};
    status = wiced_hal_i2c_write(data,sizeof(data),LSM9DS1_ACC_GYRO_I2C_ADDRESS);


    /* register callback for button available on the platform */
    wiced_platform_register_button_callback( WICED_PLATFORM_BUTTON_1, button_cb, NULL, WICED_PLATFORM_BUTTON_RISING_EDGE);

    current_speed = wiced_hal_i2c_get_speed();

    WICED_BT_TRACE("Default I2C speed: %d KHz\n", (CLK_FREQ/current_speed));

    /*Start a timer for POLL_TIMER seconds*/

    if ( WICED_SUCCESS == wiced_init_timer( &seconds_timer, &comboread_cb, 0, WICED_MILLI_SECONDS_PERIODIC_TIMER )) {
        if ( WICED_SUCCESS != wiced_start_timer( &seconds_timer, 500 )) {
            WICED_BT_TRACE( "Seconds Timer Error\n\r" );
        }
    }

}

Next I need to modify the comboread_cb callback.  It will

  • Setup a structure to hold the three acceleration values (Line 145)
  • Then it will read from the LSM9DS1 (Line 152)
  • Then print them (Line 156)
/******************************************************************************
 * This function reads the value from I2C slave and prints it
 *****************************************************************************/

void comboread_cb (uint32_t arg)
{
    UINT8  status = 0xFF;
    UINT8 reg_add = 0x28; // Acceleromter register

    typedef struct {
        int16_t ax;
        int16_t ay;
        int16_t az;
    } __attribute__((packed)) accel_val_t;

    accel_val_t data;
    status = wiced_hal_i2c_combined_read((UINT8 *)&reg_add, sizeof(UINT8), (uint8_t *)&data, sizeof(data), LSM9DS1_ACC_GYRO_I2C_ADDRESS);

    if(I2CM_SUCCESS == status) {

        WICED_BT_TRACE("Ax=%d Ay=%d Az=%d\n",data.ax,data.ay,data.az);
    }else if(I2CM_OP_FAILED == status) {
        WICED_BT_TRACE("I2C comboread operation failed\r\n");
    }else if(I2CM_BUSY == status) {
        WICED_BT_TRACE("I2C busy\r\n");
    }else{
        WICED_BT_TRACE("Unknown status from I2C\r\n");
    }

}

Now double click the make target and make sure that everything is working.

Lesson 3 – WICED Bluetooth: The Super Mux Tool

WICED Bluetooth Using the CYW20719

# Title Comment
0 A Two Hour WICED Bluetooth Class WICED Bluetooth Using the CYW20719 in all its glory
1 Resources Links to all of the Cypress WICED information including videos, application notes etc.
2 Your First Project Making Hello World & the Blinky LED
3 The Super Mux Tool Learning about platforms and the Super Mux Tool
4 Snips Using the example projects to learn Bluetooth
5 Bluetooth Designer Using the tool to customize a project and get going fast
6 The CCCD & Notification Writing back to the Central
7 Advertising  Beacon Building a beacon project to advertise your custom information 
8 Scanner Viewing the world around you
9 Bluetooth Classic SPP Using the Serial Port Profile to Transmit Lots of Data

Source code: 

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

 

Summary

You probably noticed and wondered “Why did he use WICED_LED_2 instead of WICED_LED_1”?  The answer to that question is that by default the CYW920719Q40EVB_01 is setup with WICED_LED_2 enabled as a GPIO and WICED_LED_1 used for another purpose.  But to what purpose?  In this lesson we will answer the questions:

  1. What are the default pins?
  2. How do you use the SuperMux tool?
  3. How do you use a PWM?

To do this we are going to copy the L2_HelloWorld project and add a PWM to drive the Green LED also known as WICED_LED_1.

The steps we are going to follow are

  1. Copy the L2_HelloWorld to start a new project called L3_SuperMux
  2. Rename L2_HelloWorld.c
  3. Fix the makefile.mk for the updated source file
  4. Create a new make target
  5. Program to make sure everything is still working
  6. Look at the platform files for CYW920719Q40EVB_01
  7. Run  the SuperMux Tool
  8. Delete the SPI Slave_1 From the SuperMux
  9. Add an LED to the SuperMux
  10. Configure the LED to P28
  11. Apply the SuperMux configuration
  12. Look at the new files added to the project
  13. Look a the makefile.mk
  14. Look L3_SuperMux_pin_config.c
  15. Update L3_SuperMux.c to have correct includes
  16. Update L3_SuperMux.c to start the clock, pin and PWM
  17. Program the project
  18. Look at the Hardware Abstraction Layer Documentation

Copy L2_HelloWorld –> L3_SuperMux

Instead of starting from a blank project.  Lets make a copy of the L2_HelloWorld project.  If you right click on the L2_HelloWorld folder and select copy

Then click on the “wiced_bt_class” folder and select paste.

WICED Studio will then complain that you already have a directory called “L2_HelloWord” and give you the opportunity to rename it.  Call the new project “L3_SuperMux”

Now you need to rename the L2_HelloWorld.c to be L3_SuperMux.c.  Right click on the L2_HelloWorld.c and select rename

Then give it a new file name… like L3_SuperMux.c

Double click makefile.mk and edit it.  You need to change the comment, and the name of the APP_SRC source file.

#
# Lesson 3 - SuperMux
#
APP_SRC +=  L3_SuperMux.c

C_FLAGS += -DWICED_BT_TRACE_ENABLE

Create a make target for this project by right clicking the L2_HelloWorld Make Target, then selecting “New”

That will make a new target… and it will bring up this dialog box.  Notice that it named the target “Copy of …”

Fix it to be “L3_SuperMux” like this:

You should now have an exact copy of L2_HelloWorld, in the project L3_SuperMux.  Double click the make target and make sure that things are still working.  When you build you should get this.  Don’t forget to “Start the Bootloader” if the programming doesn’t work.

Platform Files

If you look on the back of your CYW920719Q40EVB-01 development kit you will find the exact pin map of this board.  On this picture you can see that LED1 is connected to P28

In WICED Studio, the world “Platform” is just another word for Board Support Package.  Basically all of the configuration required to build the firmware for a specific board.  If you click on platforms you will find a directory for the CYW920719Q40EVB.  All of the default configuration for the pins are located in the file “wiced_platform_pin_config.c”

If you look at this file closely, you will see on line 47 that pin P28 is setup as the MOSI of WICED_SPI_1.  That isnt a GPIO!!!.  And you will see a whole block of code on line 74 that is commented out that COULD   configure P28 as a GPIO.  But that would require modifying our default platform files, which I dont want to do.  Now what?  Simple use the SuperMux tool.

/* all the pins available on this platform and their chosen functionality */
const wiced_platform_gpio_t platform_gpio_pins[] =
    {
        [PLATFORM_GPIO_0 ] = {WICED_P00, WICED_GPIO              },      //Button
        [PLATFORM_GPIO_1 ] = {WICED_P01, WICED_SPI_1_MISO        },
        [PLATFORM_GPIO_2 ] = {WICED_P02, WICED_PCM_OUT_I2S_DO    },
        [PLATFORM_GPIO_3 ] = {WICED_P04, WICED_PCM_IN_I2S_DI     },
        [PLATFORM_GPIO_4 ] = {WICED_P06, WICED_GCI_SECI_IN       },
        [PLATFORM_GPIO_5 ] = {WICED_P07, WICED_SPI_1_CS          },
        [PLATFORM_GPIO_6 ] = {WICED_P10, WICED_GCI_SECI_OUT      },
        [PLATFORM_GPIO_7 ] = {WICED_P16, WICED_PCM_CLK_I2S_CLK   },
        [PLATFORM_GPIO_8 ] = {WICED_P17, WICED_PCM_SYNC_I2S_WS   },
        [PLATFORM_GPIO_9 ] = {WICED_P26, WICED_GPIO              },      //Default LED 2
        [PLATFORM_GPIO_10] = {WICED_P25, WICED_I2C_1_SCL         },
        [PLATFORM_GPIO_11] = {WICED_P28, WICED_SPI_1_MOSI        },      //Optional LED 1
        [PLATFORM_GPIO_12] = {WICED_P29, WICED_I2C_1_SDA         },
        [PLATFORM_GPIO_13] = {WICED_P33, WICED_UART_2_TXD        },
        [PLATFORM_GPIO_14] = {WICED_P34, WICED_UART_2_RXD        },
        [PLATFORM_GPIO_15] = {WICED_P38, WICED_SPI_1_CLK         },
    };

/* LED configuration */
const wiced_platform_led_config_t platform_led[] =
    {
        [WICED_PLATFORM_LED_2] =
            {
                .gpio          = (wiced_bt_gpio_numbers_t*)&platform_gpio_pins[PLATFORM_GPIO_9].gpio_pin,
                .config        = ( GPIO_OUTPUT_ENABLE | GPIO_PULL_UP ),
                .default_state = GPIO_PIN_OUTPUT_HIGH,
            },

// We can use either LED1 or SPI1 MOSI, by default we are using WICED_P28 for SPI1 MOSI,
// uncomment the following initialization if WICED_P28 is to be used as an LED and set PIN
// functionality in platform_gpio_pins as WICED_GPIO

//        [WICED_PLATFORM_LED_1] =
//            {
//                .gpio          = (wiced_bt_gpio_numbers_t*)&platform_gpio_pins[PLATFORM_GPIO_11].gpio_pin,
//                .config        = ( GPIO_OUTPUT_ENABLE | GPIO_PULL_UP ),
//                .default_state = GPIO_PIN_OUTPUT_HIGH,
//            }
    };

SuperMux Tool

The SuperMux tool is a GUI for setting the default configurations of the Pins on the chip.  Like all capable MCUs, this chip has PWMs, SPIs, UARTs, GPIOs, I2C, ADCs etc.  Each pin on the chip can do a bunch of different functions, but only one at a time.  Each pin has a multiplexor in front of it that selects the function of that pin.  The SuperMux tool helps you setup the multiplexors for each pin on the chip.

To run the SuperMux tool, first click on your project directory (remember L3_SuperMux).  The select File–>New–>WICED SuperMux GPIO Pin Configuration

It will ask you which “App Name” you want it to work on.  Since we clicked on the L3_SuperMux app, it uses that name by default.  Press Next

The SuperMux Wizard will give you the opportunity to select which pins you want to configure.  It also shows you the default configuration of each of the pins.  In this case just press “Next” because we want to configure them all.

Now you will see the functions of the chip and which pins they are assigned to.  Notice that WICED_P28 is assigned as the MOSI of SPI(Slave)_1.  We don’t want that.

Remove the SPI(Slave)_1 by selecting it and then pressing the “Remove” button

Now your screen will look like this.  In order to add a new pin configuration you can press the little “+” at the bottom of the function column.

Next press the little “+” button and select LED.

The select which Pin you want assigned to the LED.  In this case we want WICED_P28

After you press finish you will notice that it adds a several files to your project.  And you notice that it creates a file called “makefile.mk.bak” (which is the backup of the original makefile)

First look at the makefile and notice that it added the “L3_SuperMux_pin_config.c” to the sources and added a CFLAG

#
# Lesson 3 - SuperMux
#
APP_SRC +=  L3_SuperMux.c

C_FLAGS += -DWICED_BT_TRACE_ENABLE
C_FLAGS += -DSMUX_CHIP=$(CHIP)
APP_SRC += L3_SuperMux_pin_config.c

So, what is up with the  L3_SuperMux_pin_config.c.  OH!!! I See, this is just a replacement for the default platform configuration.  Notice that P28 is now a WICED_GPIO and that it is now defined in the LED list.

wiced_platform_gpio_t platform_gpio_pins[]=
	{
		[PLATFORM_GPIO_0]	= {WICED_P00, WICED_GPIO},
		[PLATFORM_GPIO_1]	= {WICED_P02, WICED_PCM_OUT_I2S_DO},
		[PLATFORM_GPIO_2]	= {WICED_P04, WICED_PCM_IN_I2S_DI},
		[PLATFORM_GPIO_3]	= {WICED_P06, WICED_GCI_SECI_IN},
		[PLATFORM_GPIO_4]	= {WICED_P10, WICED_GCI_SECI_OUT},
		[PLATFORM_GPIO_5]	= {WICED_P16, WICED_PCM_CLK_I2S_CLK},
		[PLATFORM_GPIO_6]	= {WICED_P17, WICED_PCM_SYNC_I2S_WS},
		[PLATFORM_GPIO_7]	= {WICED_P25, WICED_I2C_1_SCL},
		[PLATFORM_GPIO_8]	= {WICED_P26, WICED_GPIO},
		[PLATFORM_GPIO_9]	= {WICED_P28, WICED_GPIO},
		[PLATFORM_GPIO_10]	= {WICED_P29, WICED_I2C_1_SDA},
		[PLATFORM_GPIO_11]	= {WICED_P33, WICED_UART_2_TXD},
		[PLATFORM_GPIO_12]	= {WICED_P34, WICED_UART_2_RXD},
	};

const wiced_platform_button_config_t platform_button[WICED_PLATFORM_BUTTON_MAX]=
	{
		[WICED_PLATFORM_BUTTON_1] =
			{
				.gpio			= &platform_gpio_pins[PLATFORM_GPIO_0].gpio_pin,
				.config			= (GPIO_INPUT_ENABLE | GPIO_PULL_UP),
				.default_state	= GPIO_PIN_OUTPUT_LOW,
				.button_pressed_value	= GPIO_PIN_OUTPUT_LOW,
			},
	};

const size_t button_count =  (sizeof(platform_button) / sizeof(wiced_platform_button_config_t));


const wiced_platform_led_config_t platform_led[WICED_PLATFORM_LED_MAX]=
	{
		[WICED_PLATFORM_LED_1] =
			{
				.gpio			= &platform_gpio_pins[PLATFORM_GPIO_9].gpio_pin,
				.config			= (GPIO_OUTPUT_ENABLE | GPIO_PULL_UP),
				.default_state	= GPIO_PIN_OUTPUT_HIGH,
			},
		[WICED_PLATFORM_LED_2] =
			{
				.gpio			= &platform_gpio_pins[PLATFORM_GPIO_8].gpio_pin,
				.config			= (GPIO_OUTPUT_ENABLE | GPIO_PULL_UP),
				.default_state	= GPIO_PIN_OUTPUT_HIGH,
			},
	};

Now that the pins are configured.  We need to setup the PWM.

Configure the Clock and the PWM

Now I will add a little bit of code to the top of  our L3_SuperMux.c to configure the PWM, Clock and Pin.

First add includes for the ACLK and PWM driver.

#include "wiced_hal_aclk.h"
#include "wiced_hal_pwm.h"

Then startup the Clock, Pin and PWM.

    wiced_hal_aclk_enable(2000, ACLK1, ACLK_FREQ_1_MHZ );
    wiced_hal_pwm_configure_pin (WICED_GPIO_PIN_LED_1, PWM1 );
    wiced_hal_pwm_start(PWM1, PMU_CLK, 0xFFFF-500, 0xFFFF-999,0);

If you want to turn on the PWM you need to do three things

  1. Turn on a clock to drive it (line 17) sets the clock frequency to 2000hz
  2. Attach the PWM to a Pin (line 18) attaches PWM 1 to the pin
  3. Turn on the PWM which is a 16-bit up-counting PWM.  When the PWM is reset it will go to 0xFFFF-999 (the period)… then it will switch at 0xFFFF-500 (the compare value)

When you program this your Green LED aka WICED_LED_1 is being driven by the PWM.  And your RED LED is being driven by your firmware.

Documentation

All of the hardware blocks on the chip have a set of API functions to help you interface with them.  You can find all of that in the Documentation

Lesson 2 – WICED Bluetooth: Your First Project(s)

WICED Bluetooth Using the CYW20719

# Title Comment
0 A Two Hour WICED Bluetooth Class WICED Bluetooth Using the CYW20719 in all its glory
1 Resources Links to all of the Cypress WICED information including videos, application notes etc.
2 Your First Project Making Hello World & the Blinky LED
3 The Super Mux Tool Learning about platforms and the Super Mux Tool
4 Snips Using the example projects to learn Bluetooth
5 Bluetooth Designer Using the tool to customize a project and get going fast
6 The CCCD & Notification Writing back to the Central
7 Advertising  Beacon Building a beacon project to advertise your custom information 
8 Scanner Viewing the world around you
9 Bluetooth Classic SPP Using the Serial Port Profile to Transmit Lots of Data

Source code: 

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

 

Summary

For our first project, I am going to stand on the shoulder of giants.  In 1978, Brian Kernighan and Dennis Ritchie published “The C Programming Language”.  Here are pictures of my copy.

Kernighan & Ritchie

The reason you do “Hello, World” is that you want to make sure that your compiler chain, programmer etc are all working correctly with something that is super simple.  The only change that I will make to their classic program is to add the “Blinking LED” which is the embedded developers version of “Hello, World”.

The concepts that I want to show in this lesson are.

  1. How to make a new project – makefile.mk, <appname>.c
  2. How NOT to make a new project
  3. How to create a “Make Target”
  4. CYW920719Q40EVB01 Development Kit
  5. WICED PUART and WICED HCI UART
  6. How to start the bootloader
  7. Where the documentation resides for the WICED 20719 hardware abstraction layer
  8. WICED uses ThreadX RTOS

To make this first project the steps are:

  1. Make a new folder called wiced_bt_class  in the Apps Folder
  2. Make a new folder called L2_HelloWorld in the wiced_bt_intro Folder
  3. Create a new file called L2_HelloWorld.c
  4. Create a new file called makefile.mk
  5. Add the code to print HelloWorld & blink the LED to L2_HelloWorld.c
  6. Add the secret incantation to makefile.mk to build the project
  7. Create a “Make Target”
  8. Connect the development kit to your computer
  9. Attach a serial terminal to the PUART
  10. Run the Make Target to Build and Program

Lets do this!

DO NOT DO File->New Project

I always hate to start with a negative statement… but DO NOT make a file project by doing File->New Project.  This is used for creating a new Eclipse project, not a new WICED Studio project.  In WICED Studio we use the make external build system.  If you do File->New Project all hell is going to break loose.  So don’t do any of the things on this menu:

Hello World & Blinking LED

Now lets get on with making a WICED Studio Project.  First create a new folder to hold the projects for the Class in the “Apps” folder by right-clicking and selecting New->Folder

Give it the name “wiced_bt_class”

Create a folder to hold the first project called L2_HelloWorld

Call the folder L2_HelloWorld

Make a new file called L2_HelloWorld.c by right clicking on the L2_HelloWorld folder and selecting New–>File

Give it the name L2_HelloWorld.c

Make a new file called makefile.mk by right clicking on the L2_HelloWorld directory and selecting New->File

and giving it the name makefile.mk

Add some code to the L2_HelloWorld.c

#include "wiced.h"
#include "sparcommon.h"
#include "wiced_platform.h"
#include "wiced_rtos.h"
#include "wiced_hal_gpio.h"
#include "wiced_bt_trace.h"

APPLICATION_START()
{
    wiced_set_debug_uart(WICED_ROUTE_DEBUG_TO_PUART);

    WICED_BT_TRACE("Hello, World\n");
    while(1)
    {

        WICED_BT_TRACE("Setting 0\n");
        wiced_hal_gpio_set_pin_output(WICED_GPIO_PIN_LED_2,0);
        wiced_rtos_delay_milliseconds(500,KEEP_THREAD_ACTIVE );
        WICED_BT_TRACE("Setting 1\n");
        wiced_hal_gpio_set_pin_output(WICED_GPIO_PIN_LED_2,1);
        wiced_rtos_delay_milliseconds(500,KEEP_THREAD_ACTIVE );
    }
}

Add the secret incantation to the makefile.mk

#
# Lesson 2 - Hello, World
#
APP_SRC +=  L2_HelloWorld.c

C_FLAGS += -DWICED_BT_TRACE_ENABLE

Create a make target

The make target has a VERY specific format.  It is:

directory.directory.appname-platform download

In our case we have all of our projects in a directory called “wiced_bt_class”.  Then we have a directory called “L2_HelloWorld” which holds the exact project.  And our platform name is “CYW920719Q40EVB_01”

Connect the Development Kit To Your Computer

When you plug in your development kit, it will USB enumerate a TWO serial ports.  One of the serial ports (the first one) is called the “WICED HCI UART”.  The second serial port is called the “WICED Peripheral UART” (this is often abbreviated “PUART”)

One of the key things that the WICED HCI UART is used as is a UART to download new code to the bootloader.

The PUART is used as a general purpose serial port.  When we call this function it causes all of our “WICED_BT_TRACE” outputs to go to the the PUART.

    wiced_set_debug_uart(WICED_ROUTE_DEBUG_TO_PUART);

You can see these two UARTs on a PC by running the device manager.

You can see COM17 is the “WICED HCI UART” and COM18 is the “WICED Peripheral UART”

On my Mac I use the program “Serial” which I downloaded from the App Store.

When I run Serial and then to open a Port

You can see the two UARTs.

In order to see the output I will connect to the port with the settings

  • 115200 Baud
  • 8-n-1 (Data bits, Parity, Stop Bits)

With my PC I typically use Putty (remember it was COM18 from the screen above)

On the Mac program serial you can configure it with Terminal->Settings

Program your Development Kit

In the Make Target window you should see a bunch of “targets”.  You probably have a bunch more targets, which came in your installation of WICED Studio by default, but I deleted a bunch of them so I could just see the ones that I created.

To build and program your project, double click the make target we made before.

When you look in the console you should see something like this:

And when you look at your serial terminal you will see this:

And you should also see the blinking LED!!!

Start the Bootloader

If you get this message there are three posibilites

  1. The kit isn’t plugged in
  2. The driver didn’t install properly
  3. The bootloader wont start

Check the first two… and if that doesnt work then what this means is that the bootloader is not listening on the WICED HCI UART.  In order to fix this you need to press reset and hold down the button called “Recover”.  Then release the reset, then release the recover button.  What does this do?  Simple, when the chip comes out of reset, if the recover button is pressed, the chip starts the bootloader instead of the main application.

Here is a picture of the bottom corner of the board.  The button circled in Green is the “Recover”.  The button in Red is “Reset” and the Blue surrounds the LED circuit.

The two LEDs are labeled LED1 and LED2.  LED2 is the Red one, LED1 is the Green one.  The dip switches circled in Blue connect or disconnect the LEDs from the CYW20719.  In my case you can see (barely) that the switch is set to On.  Both of these LEDs are active LOW (0 turns them on)

Lesson 1 – WICED Bluetooth: A Tour of the Resources

WICED Bluetooth Using the CYW20719

# Title Comment
0 A Two Hour WICED Bluetooth Class WICED Bluetooth Using the CYW20719 in all its glory
1 Resources Links to all of the Cypress WICED information including videos, application notes etc.
2 Your First Project Making Hello World & the Blinky LED
3 The Super Mux Tool Learning about platforms and the Super Mux Tool
4 Snips Using the example projects to learn Bluetooth
5 Bluetooth Designer Using the tool to customize a project and get going fast
6 The CCCD & Notification Writing back to the Central
7 Advertising  Beacon Building a beacon project to advertise your custom information 
8 Scanner Viewing the world around you
9 Bluetooth Classic SPP Using the Serial Port Profile to Transmit Lots of Data

Source code: 

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

 

A Tour of the Resources

Cypress is committed to the “Whole Product”.  What that means is that we believe that you should have great software, hardware, dev kits, community etc. experience while using our chip.  So, before we get started Id like to show you all of the learning and development resources available to you.

  1. BLE & Bluetooth Connectivity Solutions
  2. WICED CYW20719 Product Page
  3. CYW20719 Product Guide
  4. CYW20719 Datasheet
  5. CYW20719 Software Features
  6. WICED Module Selection Guide
  7. CYW920719Q40EVB-01 Development Kit
  8. CYW920719Q40EVB-01 Product Page
  9. CYW920719 Quick Start
  10. CYW920719Q40EVB-01 Evaluation Board User Guide
  11. Cypress Community
  12. WICED Studio Bluetooth Community
  13. WICED Studio Bluetooth Forums
  14. WICED Studio
  15. WICED Studio Bluetooth Example Projects
  16. WICED Studio Documentation
  17. WICED Bluetooth API Guide
  18. WICED README.txt
  19. WICED Studio Release Notes
  20. WICED Studio Technical Brief
  21. WICED Bluetooth 101

Bluetooth BR+EDR Connectivity Solutions Page

This pages gets to you all of the Cypress WICED BR+EDR+Bluetooth products

WICED CYW20719 Product Page

When you get the the BLE+Bluetooth products page, then click “BLE+BT” to see just the chips Im talking about here (CYW20719)

CYW20719 Product Guide

The Product Guide is a website that has all (most?) of the links you might need to learn about the CYW20719

CYW20719 Datasheet

The Datasheet always anchors you to the reality of what the chip can and cannot do

CYW20719 Software Features

This webpage has a list of all of the stuff that you have access to inside of the WICED Bluetooth SDK.

And it goes on and on and on from here.

WICED Module Selection Guide

If you feel like building a Bluetooth Product, you are almost certainly going to want to use a FCC certified module.  This guide is a discussion of all of the module vendors.

CYW920719Q40EVB-01 Development Kit

Here is the development kit.  You can see in the picture that this is an Arduino form factor board.  It has a button and and LED plus programmer and UART bridge.  Most importantly it has a daughter card with the 20719 and and antenna.

CYW920719Q40EVB-01 Product Page

The product landing pages for the development kit has lots of resources specific to this kit including the manual and quick start guide.

CYW920719 Quick Start

The Quickstart guide is included in the kit.  Just a single sheet of paper that points out all of the features of the development kit.

CYW920719Q40EVB-01 Evaluation Board User Guide

The Users Guide is the manual for the development kit.  It shows you how to use all of the resouces on the board and how to get going with WICED Studio.

Cypress Community

The community is your anchor for support.  It has all of the documentation etc… and most importantly a vibrant user forum.

WICED Studio Bluetooth Community

The Bluetooth Community website brings together all of the people and product collateral for WICED Bluetooth.

WICED Studio Bluetooth Forums

The actual forum is accessible to everyone to ask questions about the Cypress products.  It is staffed by our technical support team and you will get good answers.

WICED Studio

WICED Studio is the development tool which you can use to build projects.  This will be the central tool used for the rest of this class.

WICED Studio Bluetooth Example Projects

Cypress delivers a bunch of “apps” which range from small examples we call SNIPs to more fully featured projects (in the Demo) folder.  Ill be showing you how to use the in the next set of tutorials.

WICED Studio Documentation

In the “doc” folder resides all of the documentation for WICED bluetooth.

WICED Bluetooth API Guide

The API guide is doxygen generated API documentation for the WICED Bluetooth SDK.

WICED Studio README.txt

WICED Studio Release Notes

 

WICED Studio Technical Brief

WICED Bluetooth 101

I have been working with  some amazing people to build a class for learning WICED Bluetooth.  You can find all of the material at https://github.com/cypresssemiconductorco/CypressAcademy_WBT101_Files

Lesson 0 – A Two Hour WICED Bluetooth Class

Summary

This is the top level web page for a two hour class about getting you started building products with WICED Bluetooth using the CYW20719.  My friend Victor told me that I am totally insane and that I have enough material for a semester long class, but I have faith in you.  The whole point of WICED Bluetooth is to make it possible for you to build your own Bluetooth application using the best Bluetooth radios in the world.  Life is too short for flaky Bluetooth!

When I started working on this class the marketing guys asked if they could show a “few” powerpoint slides at the begining.  But I knew that is just a euphemism for power point carpet bombing you to sleep.  That sucks, so we aren’t doing that.

AFH, TDD, ∏/4 DQPSK, ISM, 8DPSK, Symbol Rate, binary FM modulation, dBi, LMP, AMP, Gaussian Frequency Shift Keying,  Modulation Index, ppm, eye diagram, FCC, Frequency Offset, Slot Length, Frequency Drift, Differential Phase Encoding, Pulse Shaping, Modulation Accuracy, Differential Error Vector Magnitude, BER, Sensitivity, Co-Chanel interference, Intermodulation Characteristics, Symbol rate, Timeslot, piconet clock, piconet channel timing,  blah blah blah blah….

Whew… now that is out of the way.  Forget that.  Rather than start at the bottom with the radio and Maxwells equations I going to start at the top.  Cypress has a huge team of radio designers to deal with all of that so you don’t have to.  To be clear, this stuff matter A LOT to how well your product works but it is only the second best reason to use Cypress WICED Bluetooth.  The best reason to use Cypress is that our software team lets you have access to the most robust Bluetooth stack and radio infrastructure without having to figure all that crap out.  You may, in time, dig into all of that.  But none of it matters for building your applications.

This workshop is hands on, as that is the only real way to learn.  This series of web pages have the exact steps that I am going to use, so you can follow along with me.

You will need a few things for the class:

  • WICED Studio 6.2.1 which you can download from the Cypress Community
  • Copies of the example projects which you can get from GitHub.
  • A CYW920719Q40EVB-01 which you can get from Mouser
  • A Terminal Program like Putty
  • CySmart, a Bluetooth GATT DB Browser for Android (Google Play Store) or iPhone (Apple App Store)
  • The courage to be WICED!

Todays virtual workshop is going to go like this:

WICED Bluetooth Using the CYW20719

# Title Comment
0 A Two Hour WICED Bluetooth Class WICED Bluetooth Using the CYW20719 in all its glory
1 Resources Links to all of the Cypress WICED information including videos, application notes etc.
2 Your First Project Making Hello World & the Blinky LED
3 The Super Mux Tool Learning about platforms and the Super Mux Tool
4 Snips Using the example projects to learn Bluetooth
5 Bluetooth Designer Using the tool to customize a project and get going fast
6 The CCCD & Notification Writing back to the Central
7 Advertising  Beacon Building a beacon project to advertise your custom information 
8 Scanner Viewing the world around you
9 Bluetooth Classic SPP Using the Serial Port Profile to Transmit Lots of Data

Source code: 

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

 

WICED Studio 6.2.1

This class is build around WICED Studio 6.2, the Cypress IDE built on top of Eclipse.  WICED Studio has all of the tools, examples and SDKs to build projects for the Cypress WICED Bluetooth and WiFi products.  We support Windows, Mac and Linux and you can download it from our community website: https://community.cypress.com/community/wireless (which I hope you have done by now)

CYW920719Q40EVB-01

I am going to build and program all of the projects in this class into our development kit, the CY920719Q40EVB-01.  This development kit (which you should buy from Mouser) uses the Cypress CYW20719 Bluetooth chip.  This is the worlds best Dual-mode Bluetooth 5.0 chip.  Dual mode means that it does Bluetooth Classic BR/EDR as well as Bluetooth Low Energy.  Even better it can do both standards at the same time.

Matrix Orbital GTT43A: Driver Library – Part 2

Summary

In the previous article I showed you how to integrate the Matrix Orbital Driver into a PSoC4200M project.  I am planning on using this device on a bus with multiple displays, and using an RTOS.  The byte based driver in the previous example isn’t that great for this situation.  In one of the earlier articles I showed you how to build a packet based interface instead of a byte-based interface.  Lets integrate that into the PSoC4200M project, and add some more commands.

In this article I will

  1. Integrate the packet driver
  2. Add event handlers
  3. Add some new commands
  4. Fix a nasty little bug that is lurking in the driver

Integrate the Packet Driver

In the file gtt_parser.c there is a big long function which reads a byte at a type every time that it is called.  It then assembles the packet into a buffer of bytes that the rest of the system can consume.  After the packet is completely read, it sets up pointers to the start and end of the packet and finally calls the function “gtt_process_packet”.  For me what I will do is read in a packet, then call this function to setup things and call the gtt_process_packet.

// This function process a whole packet at a time    
uint8_t gtt_parser_process(gtt_device *device)
{
    gtt_packet_error_t rval;
    rval = device->ReadPacket(device);
    if(rval == GTT_PACKET_NODATA)
        return 0;
    
    if(rval != GTT_PACKET_OK)
    {
#if DEBUG_PSOC        
        sprintf(buff,"GTT_PACKET_ERROR %d\r\n",rval);
        UART_UartPutString(buff);
#endif
        return 0; // No data
    }

    device->Parser.PacketStart = device->Parser.Index;
    device->Parser.Index += device->Parser.Length;
    
	uint8_t Result = gtt_process_packet(device, device->Parser.PacketStart);
	if (Result)
	    return 0;
	else
	    return 1;

}

I use almost the same packet driver as I built in the earlier example.  Except that I need to modify it to read into the gtt buffers that the gtt driver library expects.  The biggest benefit of this whole thing is that it makes complete I2C transactions, rather than issuing a bunch of start/address/reads which makes it significantly more efficient.

gtt_packet_error_t readPacketI2C(gtt_device *device)
{
    
    uint8_t data;
    uint32_t i2cerror;
    
    i2cerror = I2C_I2CMasterSendStart( ((i2cContext_t *)device->Context)->slaveAddress,I2C_I2C_READ_XFER_MODE , ((i2cContext_t *)device->Context)->timeout);
    i2cerror |= I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA,&data,((i2cContext_t *)device->Context)->timeout);
    i2cerror |= I2C_I2CMasterSendStop(((i2cContext_t *)device->Context)->timeout);
    
    
    // Something bad happened on the I2C Bus ....
    if(i2cerror)
    {
        sprintf(buff,"I2C Return Code %X\r\n",(unsigned int)i2cerror);
        UART_UartPutString(buff);
        return GTT_PACKET_I2CERROR;
    }
    
     // The screen returns a 0 when there is nothing in the buffer.
    if(data == 0)
    {
        return GTT_PACKET_NODATA;
    }

    // This is bad because there was something other than a packet start byte
    if(data != 252)
    {
        sprintf(buff,"bad data = %d\r\n",data);
        UART_UartPutString(buff);
        return GTT_PACKET_DATABAD;
    }
    
    // We know that we have a command
    i2cerror = I2C_I2CMasterSendStart( ((i2cContext_t *)device->Context)->slaveAddress,I2C_I2C_READ_XFER_MODE , ((i2cContext_t *)device->Context)->timeout);
    i2cerror |= I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA,&data,((i2cContext_t *)device->Context)->timeout); // command
    device->Parser.Command = data;

    // Read the Length
    i2cerror |= I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA,&data,((i2cContext_t *)device->Context)->timeout); // length
    device->Parser.Length = data<<8;
    i2cerror |= I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA,&data,((i2cContext_t *)device->Context)->timeout); // length
    device->Parser.Length += data;
    i2cerror |= I2C_I2CMasterSendStop(((i2cContext_t *)device->Context)->timeout);
    
    if(i2cerror)
        return GTT_PACKET_I2CERROR;
    
    if(device->Parser.Length > device->rx_buffer_size)
    {
        return GTT_PACKET_SIZE;
    }
    
    // If the packet has any data... then read it.
    if(device->Parser.Length != 0)
    {
        i2cerror |= I2C_I2CMasterSendStart( ((i2cContext_t *)device->Context)->slaveAddress,I2C_I2C_READ_XFER_MODE , ((i2cContext_t *)device->Context)->timeout);
    
        for(uint32_t i=0;i < device->Parser.Length-1; i++)
        {
            i2cerror |= I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA,&data,((i2cContext_t *)device->Context)->timeout); // length
            device->rx_buffer[device->Parser.Index+i] = data;
        }

        // Read the last byte
        i2cerror |= I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA,&data,((i2cContext_t *)device->Context)->timeout); // length
        device->rx_buffer[device->Parser.Index +device->Parser.Length - 1 ] = data;
        i2cerror |= I2C_I2CMasterSendStop(((i2cContext_t *)device->Context)->timeout);
        
        if(i2cerror)
            return GTT_PACKET_I2CERROR;
    }
      
    sprintf(buff,"command = %d length = %d bytes= ",device->Parser.Command,device->Parser.Length);
    UART_UartPutString(buff);
    for(uint32_t i=0;i<device->Parser.Length;i++)
    {
        //sprintf(buff,"%d ",inbuff[i]);
        sprintf(buff,"%d ",device->rx_buffer[device->Parser.Index+i]);
        UART_UartPutString(buff);
    }
    UART_UartPutString("\r\n");
    return GTT_PACKET_OK;
}

Add Event Handlers

I noticed when I looked at the gtt_device.h that the structure for the gtt_device has an member called “gtt_events”, but what is that?

typedef struct gtt_device
{
	void* Context;            /* device depended storage */
	gtt_write Write;          /* Function for writing data */
	gtt_read Read;            /* Function for reading data */
    gtt_packet_error_t (*ReadPacket)(gtt_device *);

	uint8_t secured_packets;  /* 0 = regular protocol, 1 = wrap all outgoing packets with crc protection*/
	
	/* The fields below are internal and shall NOT be used by the read/write functions */
	
	gtt_parser Parser;        /* Protocol parser data */
	uint8_t *rx_buffer;       /* Buffer for incoming data */
	size_t rx_buffer_size;    /* size of the rx buffer in elements */
	uint8_t *tx_buffer;       /* Buffer for outgoing data */
	size_t tx_buffer_size;    /* size of the tx buffer in elements */
	size_t tx_index;          /* current index for the packet writer */
	gtt_events events;        /* Event Callbacks */
	size_t wait_idx;          /* Current Packet Index for the waitlist */
	gtt_waitlist_item waitlist[8]; /* Packet recieve waitlists */
} gtt_device;

Well. the gtt_events structure is defined in gtt_events.h.  Basically it is a bunch of function pointers, which if you provide functions, it will call those functions when things happen on the screen.  For instance the function that gtt_event_slider_change is pointing to will be called when a slider changes.

typedef void(*gtt_event_key)(gtt_device* device, uint8_t key, eKeypadRepeatMode type);
typedef void(*gtt_event_sliderchange)(gtt_device* device, eTouchReportingType type, uint8_t slider, int16_t value);
typedef void(*gtt_event_touch)(gtt_device* device, eTouchReportingType type, uint16_t x , uint16_t y);
typedef void(*gtt_event_regiontouch)(gtt_device* device, eTouchReportingType type, uint8_t region);
typedef void(*gtt_event_baseobject_on_property_change)(gtt_device* device, uint16_t ObjectID, uint16_t PropertyID);
typedef void(*gtt_event_visualobject_on_key)(gtt_device* device, uint16_t ObjectID, uint8_t Row, uint8_t Col, uint8_t ScanCode, uint8_t Down);
typedef void(*gtt_event_button_click)(gtt_device* device, uint16_t ObjectID, uint8_t State);

typedef struct gtt_events {
	gtt_event_key key;
	gtt_event_sliderchange sliderchange;
	gtt_event_touch touch;
	gtt_event_regiontouch regiontouch;
	gtt_event_baseobject_on_property_change baseobject_on_property_change;
	gtt_event_visualobject_on_key visualobject_on_key;
	gtt_event_button_click button_click;
} gtt_events;

To start with I just created stub functions that would just print out the information.  Here is an example of a function for the “gtt_event_button_click”

void my_gtt_event_button_click(gtt_device* device, uint16_t ObjectID, uint8_t State)
{
    (void)device;
    (void)ObjectID;
    (void)State;
    UART_UartPutString("event button click\r\n");
}

Once you have those functions you need to add them to the gtt_device structure like this:

gtt_events myEvents = {
    .sliderchange = my_gtt_event_sliderchange,
    .touch = my_gtt_event_touch,
    .regiontouch = my_gtt_event_regiontouch,
    .baseobject_on_property_change = my_gtt_event_baseobject_on_property_change,
    .visualobject_on_key = my_gtt_event_visualobject_on_key,
    .button_click = my_gtt_event_button_click
};

Add New Commands

Now we are ready to update the test project to add some more commands.  Here are a few examples which call the “gtt25” functions.

            case 'q':
                UART_UartPutString("Set Text\r\n");
                gtt25_set_label_text(gtt,2,t);
            break;
                       
            case '2':
                gtt25_set_slider_value(gtt,3,2);
            break;
               
            case '9':
                gtt25_set_slider_value(gtt,3,9);
            break;
                    
            case 'I':
                gtt_set_default_channel(gtt, eChannel_I2C);
            break;
                    
            case '+':
                count += 1;
                if(count>100)
                    count = 100;
                gtt25_set_gauge_value(gtt,9,count);
            break;    
                
            case '-':
                if(count > 0)
                    count -= 1;
                gtt25_set_gauge_value(gtt,9,count);
            break;

Fix a Nasty Little Bug

While I was debugging the library I found myself where the program was hung.  When I ran the debugger I found myself here.  This means that there was an ARM exception.  But why?

Then when you look at the call stack you find out that the exception is in the function “gtt_parser_getS16”

OK… but what in the world?  All this function is doing is taking the bytes and casting them into a uint16_t

Well it turns out that if the address that is being read is ODD meaning not even aligned, you will endup with an ARM exception for an unaligned access of the memory.  This is why you need to be super careful with a pointer cast.  In this case you are casting a uint8_t pointer which can be byte aligned.

Here is a proper fix to this problem, assemble the composite type byte-by-byte.

int16_t gtt_parser_getS16(gtt_device* device, size_t index, size_t *outIndex)
{
    
    int16_t data = (device->rx_buffer[index]<<8 | device->rx_buffer[index+1]);	    
	*outIndex = index + 2;
    return data;
  
}

In the next article I will port all of this stuff to PSoC 6.

You can "git" these projects from

https://github.com/iotexpert/GTT43A

And the driver library from 

https://github.com/iotexpert/GTT-Client-Library

Title
Matrix Orbital GTT43: A Cool Display
Matrix Orbital GTT43A: Serial Interface
Matrix Orbital GTT43A: GTT Scripts
Matrix Orbital GTT43A: A PSoC 4 Interface
Matrix Orbital GTT43A: Debugging the I2C
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: PSoC 6 using RTOS and the GTT Driver Library

Matrix Orbital GTT43A: Driver Library – Part 1

Summary

In this article Im going to show you how to build a driver for the Matrix Orbital GTT43A that I have been talking about in the last several articles.  As you can see from the protocol manual, the Matrix Orbital Display has a bunch of different commands.  And I know that I need a driver.  But there isn’t (yet) one on the Matrix Orbital Website.  However, when I look at the pre-release of the GTT25 protocol guide, it seems clear that they are planning on one.  But what to do in the interim?  As usual Google is your friend and after looking around a little bit I found this YouTube video of a Matrix Orbital Demo.  One more Google search lead me to this Matrix Orbital GitHub repo which appears to hold the driver that they wrote for this demo.

Clone it! Clone it! Good.

Now what?  In this article Ill show you:

  1. How to port the library to PSoC
  2. How to implement the HAL as conceived by Matrix Orbital
  3. How to make a test jig

And in the next Article I will

  1. Replace the HAL and Parser with a Packet based HAL, much better
  2. Show you how to fix some bugs in the library (nastiness)
  3. Add more test code

And in the one after that Ill port the whole thing to PSoC6 & RTOS

Port the GTT Client Library

After running “git@github.com:MatrixOrbital/GTT-Arduino-Thermometer-Demo.git” I look around a little bit… and immediately find “GttClient”.  And when you look there, perfect a bunch of C and Header files.

The first step is to make a new PSoC Creator project.  As I have done in the past, Ill drive the display with I2C, and Ill build a command line parser to talk to the system.  Here is the schematic:

Assign the Pins (I am using a PSoC 4200M, my favorite PSoC, development kit, specifically the CY8CKIT-044)

After running “Generate Application”, the next thing I do is pull in the library into PSoC Creator.  To do this

  1. Right click on the Source Files and make a new Folder, rename it “GTT”
  2. Right click on the GTT Folder and do “Add Existing Item…”
  3. Navigate to the GttClient directory, select all of the .h and .c files

Your WorkSpace Explorer should look like this now:

Because those files are not in the normal build path, you next need to add the directory to the include path so that PSoC Creator can find the header files.  Right click on the project and pick “Build Settings”.

Then add a path to the library in the “Additional Include Directories”

Before we fix up the library to work, I always like to hit build to make sure everything is working.

Implementing the Hardware Abstraction Layer

In order to use the driver you need a Hardware Abstraction Layer.  After looking around a little bit I find the “.ino” file which is the Arduino main project file.  In that file, the first thing that they do is declare a structure of type “struct gtt_device”.  All of the function calls to the library take a pointer to this structure.  OK.  Lets have a look at the structure

  1. First it appears that they let you store some generic data via a “Context”
  2. Then there are two functions to read and write data
  3. Then some private stuff (which is used by the packet parser)
typedef struct gtt_device
{
	void* Context;            /* device depended storage */
	gtt_write Write;          /* Function for writing data */
	gtt_read Read;            /* Function for reading data */
  

	uint8_t secured_packets;  /* 0 = regular protocol, 1 = wrap all outgoing packets with crc protection*/
	
	/* The fields below are internal and shall NOT be used by the read/write functions */
	
	gtt_parser Parser;        /* Protocol parser data */
	uint8_t *rx_buffer;       /* Buffer for incoming data */
	size_t rx_buffer_size;    /* size of the rx buffer in elements */
	uint8_t *tx_buffer;       /* Buffer for outgoing data */
	size_t tx_buffer_size;    /* size of the tx buffer in elements */
	size_t tx_index;          /* current index for the packet writer */
	gtt_events events;        /* Event Callbacks */
	size_t wait_idx;          /* Current Packet Index for the waitlist */
	gtt_waitlist_item waitlist[8]; /* Packet recieve waitlists */
} gtt_device;

That means you need to provide a function called “Write” and one called “Read” which reads bytes from the serial interface.  Here is how they setup the structure for the Arduino Demo.  Apparently they are going to make two functions called i2cWrite and i2cRead.

  gtt.Write = i2cWrite; //Set the write function
  gtt.Read = i2cRead; //Set the read function
  gtt.rx_buffer = rx_buffer; //Declare a buffer for input data
  gtt.rx_buffer_size = sizeof(rx_buffer); //Declare the size of the input buffer
  gtt.tx_buffer = tx_buffer; //Declare a buffer for output data
  gtt.tx_buffer_size = sizeof(tx_buffer); //Declare the size of the output buffer

So, what do those functions look like?

The I2C Write function just uses the Arduino Wire library to send bytes out the I2C.  And the I2C read function just reads one byte from the I2C and returns it.  OK I know how to do that on the PSoC

}     

int i2cWrite(gtt_device* gtt_device, char* data, byte data_length) {//Write an array of bytes over i2c
  Wire.beginTransmission(I2C_Address);  
  for (int i = 0; i < data_length; i++) {
    Wire.write(data[i]);        
  }
  Wire.endTransmission();  
  return 0;
}

byte i2cRead(gtt_device* gtt_device) { //Wait for one byte to be read over i2c  
  byte data;
  Wire.beginTransmission(I2C_Address);  
  Wire.requestFrom(I2C_Address, 1);     
  if(Wire.available()<1) 
  {
    return -1;
  }
  else{
    data = Wire.read();  
    Serial.println(data);
    return data;
  } 
}

To do this exact same thing on the PSoC do this.  Notice that I put in a little bit of error checking.  I also make complete legal I2C transactions, Start, Address, R/W, bytes, Stop

int generic_write(gtt_device *device, uint8_t *data, size_t length)
{
    (void)device;
    uint32 returncode;
    
   
    sprintf(buff,"length = %d ",length);
    UART_UartPutString(buff);

            
    returncode = I2C_I2CMasterSendStart( ((i2cContext_t *)device->Context)->slaveAddress,I2C_I2C_WRITE_XFER_MODE , ((i2cContext_t *)device->Context)->timeout);
    if(returncode != I2C_I2C_MSTR_NO_ERROR)
    {
        sprintf(buff,"error = %X\r\n",(unsigned int)returncode);
        UART_UartPutString(buff);
    }
    
    for(size_t i=0;i<length;i++)
    {
        I2C_I2CMasterWriteByte(data[i],((i2cContext_t *)device->Context)->timeout);
        sprintf(buff,"%d ",data[i]);
        UART_UartPutString(buff);
    }
    
    I2C_I2CMasterSendStop(((i2cContext_t *)device->Context)->timeout);
    UART_UartPutString("\r\n");
    return length;
        
}

And to make the PSoC read one byte at a time from the I2C do this … notice for some reason I didnt put in error checking.

int generic_read(gtt_device *device)
{
    (void)device;
     uint8 data;
     
    //uint32 returncode;
    I2C_I2CMasterSendStart( ((i2cContext_t *)device->Context)->slaveAddress,I2C_I2C_READ_XFER_MODE,((i2cContext_t *)device->Context)->timeout);
    I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA,&data,((i2cContext_t *)device->Context)->timeout);
    I2C_I2CMasterSendStop(((i2cContext_t *)device->Context)->timeout);
    return data;
}

Matrix Orbital Parser

The Matrix Orbital Parser is build around a function which you are supposed to call every time through your main loop.

uint8_t gtt_parser_process(gtt_device *device)

If you look in this function you will find a state machine that calls the Read function pointer, then based on the value read and the state of the state machine read in the packet.  Remember that there are two somewhat different packet formats, and this thing handles it.

uint8_t gtt_parser_process(gtt_device *device)
{
	int Res = device->Read(device);
	if (Res != -1)
	{
		switch (device->Parser.state)
		{
		case GTT_PARSER_IDLE:
			if (Res == 252)
				device->Parser.state = GTT_PARSER_COMMAND;
			else if (Res == 0) // Ignore 0's 
            {
                return 0;
            }

I notice in gtt_parser.h that they decided to use #defines for the states, which works, but would have been better done with an enumerated datatype.

#define GTT_PARSER_IDLE       0
#define GTT_PARSER_COMMAND    1
#define GTT_PARSER_LENGTH_1   2
#define GTT_PARSER_LENGTH_2   3
#define GTT_PARSER_DATA       4

One thing that took a little but of looking at is how they handle the packets that come in.  Remember from the previous articles that there are three possible sources of packets that you might read from the buffer.

  1. Packets that were generated by the configuration scripts residing in the display
  2. Packets that are generated from the user of the display doing something e.g. pressing a button
  3. Packets that are responses to the application sending it packets (e.g. get slider value)

They handle this by keeping a list of packets that are going to elicit a response.

The bottom line is that all of their code appears to do the right thing, and all you need to do is call the parser.

Add Test Project Code to main.c

I turns out that I am writing this Article after I already did all of the work for the next one where I replace the byte-by-byte parser with my own packet processor.  In order to make that work, I #ifdef to select which packet processor to use.

#ifdef GTT_ORIG_PARSER
// The original Matrix Orbital Byte Based Parser
    
uint8_t gtt_parser_process(gtt_device *device)

PSoC Creator will allow you to add this to your project on the build settings dialog.  If you click on the compiler option, you can then add defines to the command line on the “Preprocessor Definitions” box.  Notice that I added “GTT_ORIG_PARSER”

OK now the punchline.  In the main loop you need to startup the I2C, UART and setup the GTT interface.  Then you loop infinitely.  Read a key from the keyboard and do something based on what they press.

You can see that I call a bunch of their driver functions which all start with “gtt_”.

int main()
{
    CyGlobalIntEnable; /* Enable global interrupts. */
    I2C_Start();
    UART_Start();
    UART_UartPutString("Started\r\n");
    
    gtt_device *gtt = &gtt_device_instance;
    char c;
    int16_t val;
    while (1)
    {
        c = UART_UartGetChar();
        switch(c)
        {
            case 0:  break;
            case 'l':   gtt_draw_line(gtt, 0, 0, 480, 272);  break; 
            case 'c': gtt_clear_screen(gtt);  break;
            case 'R':  gtt_reset(gtt);      break;                  
            case 'v':
                gtt25_get_gauge_value(gtt,9,&val);
                sprintf(buff,"Gauge Value = %d\r\n",val);
                UART_UartPutString(buff);
                break;
            case 'z':
                UART_UartPutString("System Mode = IDLE\r\n");
                systemMode = MODE_IDLE;
                break;
            case 'Z':
                UART_UartPutString("System Mode = POLLING\r\n");
                systemMode = MODE_POLLING;
            break;
            case '?':
                UART_UartPutString("-------- GTT Display Functions -------\r\n");
                UART_UartPutString("l\tDraw a line\r\n");
                UART_UartPutString("c\tClear Screen\r\n");
                UART_UartPutString("R\tReset\r\n");
                UART_UartPutString("v\tGet and print value of gauge \r\n");
                UART_UartPutString("-------- System Control Functions -------\r\n");
                UART_UartPutString("z\tSystemMode = IDLE\r\n");
                UART_UartPutString("Z\tSystemMode = POLLING\r\n");
            break;    
        }
        if(systemMode == MODE_POLLING)
            gtt_parser_process(gtt);
                
    }
    return 0;
}

In the next article I am going to replace their “gtt_parser_process” with a complete packet reader.

You can "git" these projects from

https://github.com/iotexpert/GTT43A

And the driver library from 

https://github.com/iotexpert/GTT-Client-Library

Title
Matrix Orbital GTT43: A Cool Display
Matrix Orbital GTT43A: Serial Interface
Matrix Orbital GTT43A: GTT Scripts
Matrix Orbital GTT43A: A PSoC 4 Interface
Matrix Orbital GTT43A: Debugging the I2C
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: PSoC 6 using RTOS and the GTT Driver Library

Matrix Orbital GTT43A: PSoC 4 Interface

Summary

In the last several articles I have written about how to use and talk to the Matrix Orbital GTT43A.  Now it is time to write some code.  The PSoC4 program that I am going to show you has evolved over time as I added stuff to it.  For instance, while I was working on this program I ran into a problem where the I2C Bus would hang (the subject of the next article).  As such, some of the code that is in this program was written to help me debug that problem.  With that said, I wanted a program that:

  1. Is command line driven i.e. I can interact with my program via serial commands through the PC COM Port
  2. Can read 1 byte at a time (like I do on the bridge control panel)
  3. Can test the “read whole packet” code
  4. Can selectively send commands via a I2C or the UART
  5. Send test commands to the display e.g. reset, clear
  6. Test the display generated messages (like button presses)

All of this code was built to run on the CY8CKIT-044 which has a PSoC 4200M MCU

Schematic & Pin Assignment

All PSoC 4 projects start with a schematic.  In my schematic I have a UART setup to talk to the KitProg (called UART) and serve as the command processor, an I2C which directly attaches to the I2C bus that drives the display, and a UART that is also attached to the display which I called SCRUART.

The I2CFAIL pin in the schematic I used to help me debug the I2C problem (the subject of the next article)

PSoC 4200m Schematic

The pin assignment has the UART attached to the KitProg UART, the I2C attached to KitProg and the Display.  The SCRUART is attached to UART on the Display.

PSoC 4200M Pin Assignment

 

Main Event Loop

The main event loop has the following parts

  1. Read a character from the keyboard
  2. Process the keyboard command character
  3. Read data from the screen
    1. If you are in packet mode read a whole packet
    2. If you are in streaming mode read one byte

There are three system modes.

  1. IDLE = Dont read from the screen
  2. PACKET = Read whole packets (poll for complete packets)
  3. STREAMING = read bytes (polling)

I also have setup the program to read/write bytes to the UART and I2C.  This is called the “comInterface”.

The enumerated type systemMode is used to setup the polling mode (each time through the main loop, what does it do?).

typedef enum {
    MODE_IDLE,
    MODE_PACKET,
    MODE_STREAMING
} systemMode_t;

systemMode_t systemMode=MODE_IDLE;

typedef enum {
    INTERFACE_I2C,
    INTERFACE_UART
} cominterface_t;

cominterface_t comInterface=INTERFACE_I2C;

The actual main section of the code

  1. Starts by initializing the UART, SCRUART and I2C.
  2. On line 299 it reads a character, then switches on the character to figure out which command the user has type.

You can see that ‘u’ and ‘U’ change the communication interface from UART to I2C and back.

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */
    uint32 returncode;
    
    UART_Start();
    UART_UartPutString("Started\r\n");
    I2C_Start();
    SCRUART_Start();
    
    char c;
     
    for(;;)
    {
        c = UART_UartGetChar();
        switch(c)
        {
            case 0:
            break;
            
            case 'u':
                UART_UartPutString("I2C Mode\r\n");
                comInterface = INTERFACE_I2C;
            break;
            
            case 'U':
                UART_UartPutString("UART Mode\r\n");
                comInterface = INTERFACE_UART;
            break;

The end of the loop handles the “?” case… in other words print out all of the commands.  Then based on the system mode, it either reads a whole message packet from the display or it reads only byte.

           case '?':
                UART_UartPutString("------Communication Mode------\r\n");
                UART_UartPutString("u\tI2C Mode\r\n");
                UART_UartPutString("U\tUART Mode\r\n");
                
                UART_UartPutString("------GTT43A Commands------\r\n");
                UART_UartPutString("N\tDefault Comm None\r\n");
                UART_UartPutString("I\tDefault Comm I2C\r\n");
                UART_UartPutString("S\tDefault Comm Serial\r\n");
                
                UART_UartPutString("e\tEcho abc\r\n");
                UART_UartPutString("R\tReset\r\n");
                UART_UartPutString("c\tSend Clear Screen\r\n");
                
                UART_UartPutString("------Communcation Commands------\r\n");
                UART_UartPutString("r\tRead one byte if IDLE\r\n");
                UART_UartPutString("p\tRead Packet if IDLE\r\n");
                
                UART_UartPutString("------System Mode------\r\n");
                UART_UartPutString("0\tTurn I2C polling off \r\n");
                UART_UartPutString("1\tTurn on I2C packet polling\r\n");
                UART_UartPutString("2\tRead i2c bytes \r\n");
                
                UART_UartPutString("------I2C Debugging------\r\n");
                UART_UartPutString("s\tPrint SCB Status\r\n");
                UART_UartPutString("z\tSend I2C Reset Sequence\r\n");
                UART_UartPutString("x\tPrint I2C SCL and SDA value\r\n");
            break;
        }
        
        switch(systemMode)
        {
            case MODE_IDLE:
            break;
            
            case MODE_PACKET:
                readPacket();
            break;
                
            case MODE_STREAMING:
                readByte();
            break;
        }

I created three keys (0,1,2) to change the system mode, from IDLE, to reading whole packets to reading bytes.

            // System Modes
            case '0':
                    I2CFAIL_Write(0);
                    UART_UartPutString("Packet Poling Off\r\n");
                    systemMode = MODE_IDLE;
                break;
               
            case '1':
                    I2CFAIL_Write(0);
                    UART_UartPutString("Packet Poling On\r\n");
                    systemMode = MODE_PACKET;
            break;
            case '2':
                    I2CFAIL_Write(0);
                    UART_UartPutString("Read continuous\r\n");
                    systemMode = MODE_STREAMING;
                    break;

While I was trying to figure out how things worked I wanted to be able to do one thing at a time. So I create ‘r’ to read one byte (like Bridge Control Panel) and ‘p’ to read a whole packet.  Notice that you really really only want to do this why you are not polling the display.

            // If you are IDLE you can read 1 byte with 'r' or read a whole packet with 'p'
            case 'r':  // Read byte
                if(systemMode != MODE_IDLE)
                    break;
                readByte(&data);
                
            break;
            case 'p': // read packet
                if(systemMode == MODE_IDLE)
                    readPacketI2C();
            break;

The last section of commands send various GTT2.0 commands to the display.  Notice that the writePacket function knows which system interface to use (either I2C or UART).

First, I declare some commands, just an array of bytes.

// These commands come the GTT 2.0 and GTT2.5 Protocol Manuals
uint8 clearCMD[] = { 0x58 };
uint8 resetCMD[] = { 0x01};
uint8 comI2CCMD[] = { 0x05, 0x02};
uint8 comNONECMD[] = { 0x05, 0x00};
uint8 comSERIALCMD[] = { 0x05, 0x01};
uint8 comECHOCMD[] = {0xFF,'a','b','c', 0};

Then I use them:

            case 'e':
                UART_UartPutString("Send Echo Command\r\n");
                writePacket(sizeof(comECHOCMD) , comECHOCMD);
            break;
                
            case 'c':
                UART_UartPutString("Sent Clear String\r\n");
                writePacket(sizeof(clearCMD),clearCMD);
            break;
            case 'R':
                UART_UartPutString("Sent Reset String\r\n");
                writePacket(sizeof(resetCMD),resetCMD);
            break;
            case 'I':
                UART_UartPutString("I2C Communcation Channel\r\n");
                writePacket(sizeof(comI2CCMD),comI2CCMD);
            break;
            case 'N':
                UART_UartPutString("NONE Communcation Channel\r\n");
                writePacket(sizeof(comNONECMD),comNONECMD);
            break;

Read Byte

In order to read one byte from the display I first determine which mode Im in, then call the appropriate sub-function.

uint32_t readByte(uint8_t *data)
{
    uint32_t returncode=0;
    switch(comInterface)
    {
        case INTERFACE_I2C:
            returncode = readByteI2C(data);
        break;
        case INTERFACE_UART:
            returncode = readByteUART(data);
        break;
    }
    sprintf(buff,"Returncode = %X Data=%d\r\n",(unsigned int)returncode,*data);
    UART_UartPutString(buff);

    return returncode;
}

The first sub function to read bytes via I2C.  To read a byte with no error checking you have to

  1. Send a Start
  2. Send the address
  3. Send the Read bit
  4. Clock it 8 times (this is exactly what the I2CMasterReadByte function does)
  5. Send an NAK
  6. Send a Stop

I ran into an I2C issue which I will talk about in the next article, however, if I see an error from any of these commands Ill put the system into MODE_IDLE and throw an error.  In addition I write a 1 to the I2CFAIL pin, which I am using to trigger the Oscilliscope (so I can see what is happening)

uint32_t readByteI2C(uint8_t *data)
{
    uint32 returncode;
    returncode = I2C_I2CMasterSendStart(I2CADDR,I2C_I2C_READ_XFER_MODE , I2CTIMEOUT);
    if(returncode)
    {
        sprintf(buff,"send start error %lX status %lX\r\n",returncode,I2C_I2CMasterStatus());
        UART_UartPutString(buff);
        I2CFAIL_Write(1);
        systemMode = MODE_IDLE;
        goto cnt;
    }
            
    returncode = I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA,data,I2CTIMEOUT);
    if(returncode)
    {
        sprintf(buff,"read byte error %lX status %lX sda=%d scl =%d\r\n",returncode,I2C_I2CMasterStatus(),I2C_sda_Read(),I2C_scl_Read());
        UART_UartPutString(buff);
        I2CFAIL_Write(1);
        systemMode = MODE_IDLE;
        goto cnt;
    }
            
    returncode = I2C_I2CMasterSendStop(I2CTIMEOUT);
    if(returncode)
    {
        sprintf(buff,"send stop error %lX status %lX\r\n",returncode,I2C_I2CMasterStatus());
        UART_UartPutString(buff);
        I2CFAIL_Write(1);
        systemMode = MODE_IDLE;
        goto cnt;
    }
    
    cnt:
    return returncode;
}

Read Packet

For the packet read code I did the same thing as the byte read code.  Specifically I wrote an overall get packet, then called the correct read packet based on the

If you read the source code that Matrix Orbital gives you for drivers, you will find that it reads one byte at a time.  The problem with doing this is that you

  1. Send a start
  2. Send an I2C address
  3. Send a read bit
  4. Read the ACK
  5. Read a byte
  6. Send a NAK
  7. Send a stop

The problem with this approach is that it uses 11 bit-times extra per byte of overhead (steps 1-4) which kinda sucks.  So I wanted to write a complete packet reader.  My packet reader will

  1. Send a start
  2. Send an I2C address
  3. Send a read bit
  4. Read the ACK
  5. Read a byte  [This is the 254 that marks the start of the packet]
  6. ACK
  7. Read a byte [This is the command which identifies the packet]
  8. ACK
  9. Read a byte [The MSB of the Length]
  10. ACK
  11. Read a byte [The LSB of the Length]
  12. NAK
  13. If there is a length then:
  14. Send the start
  15. Send an I2C address
  16. Send a read bit
  17. Read the ACK
  18. read length -1 bytes
  19. ACK
  20. Read the last byte
  21. Send a NAK
  22. Send a stop

By spec you are supposed to NAK your last read byte to indicate that your read transaction is over… that means you have to NAK the last Length byte because there could be 0 bytes to read, in which case you would need to stop.  It would have been nice if the protocol let you send only one start, but Im pretty sure it was designed for UART, which doesn’t suffer from this problem.  Also as a side note, Im pretty sure that the MCU they are using doesn’t really care, but Im not willing to implement it incorrectly.

Here is the code:

void readPacketI2C()
{
    int length;
    int command;
    uint8_t data;
    uint32_t returncode;
    
    returncode = I2C_I2CMasterSendStart(I2CADDR,I2C_I2C_READ_XFER_MODE , I2CTIMEOUT);
    returncode |= I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA,&data,I2CTIMEOUT);
    returncode |= I2C_I2CMasterSendStop(I2CTIMEOUT);
    
    // Something bad happened on the I2C Bus ....
    if(returncode)
    {
        systemMode = MODE_IDLE; 
        sprintf(buff,"I2C Return Code %X\r\n",(unsigned int)returncode);
        UART_UartPutString(buff);
    }
 
    // The screen returns a 0 when there is nothing in the buffer.
    if(data == 0)
    {
        return;
    }

    // This is bad because there was something other than a packet start byte
    if(data != 252)
    {
        sprintf(buff,"bad data = %d\r\n",data);
        UART_UartPutString(buff);
        systemMode = MODE_IDLE; // put it into nothing mode...
        return;
    }
    
    // We know that we have a command
    returncode = I2C_I2CMasterSendStart(I2CADDR,I2C_I2C_READ_XFER_MODE , I2CTIMEOUT);
    returncode |= I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA,&data,I2CTIMEOUT); // command
    command = data;
    
    returncode |= I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA,&data,I2CTIMEOUT); // length
    length = data<<8;
    returncode |= I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA,&data,I2CTIMEOUT); // length
    length = length + data;
    returncode |= I2C_I2CMasterSendStop(I2CTIMEOUT);
    
    // If the packet has any data... then read it.
    if(length != 0)
    {
        returncode |= I2C_I2CMasterSendStart(I2CADDR,I2C_I2C_READ_XFER_MODE , I2CTIMEOUT);
    
        for(int i=0;i<length-1; i++)
        {
            I2C_I2CMasterReadByte(I2C_I2C_ACK_DATA,&data,I2CTIMEOUT); // length
            inbuff[i] = data;
        }

        // Read the last byte
        I2C_I2CMasterReadByte(I2C_I2C_NAK_DATA,&data,I2CTIMEOUT); // length
        inbuff[length-1] = data;
        returncode |= I2C_I2CMasterSendStop(I2CTIMEOUT);
        
        I2C_I2CMasterSendStop(I2CTIMEOUT);
    }
      
    sprintf(buff,"command = %d length = %d bytes= ",command,length);
    UART_UartPutString(buff);
    for(int i=0;i<length;i++)
    {
        sprintf(buff,"%d ",inbuff[i]);
        UART_UartPutString(buff);
    }
    UART_UartPutString("\r\n");
    
}

You can "git" these projects from

https://github.com/iotexpert/GTT43A

And the driver library from 

https://github.com/iotexpert/GTT-Client-Library

Title
Matrix Orbital GTT43: A Cool Display
Matrix Orbital GTT43A: Serial Interface
Matrix Orbital GTT43A: GTT Scripts
Matrix Orbital GTT43A: A PSoC 4 Interface
Matrix Orbital GTT43A: Debugging the I2C
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: PSoC 6 using RTOS and the GTT Driver Library

Matrix Orbital GTT43A: GTT Scripts

Summary

As I have been working my way through understanding the Matrix Orbital displays, I think that the whole scheme for programming them is very clever.  The bottom line of this article is that Matrix Orbital created a command language for making things work in the display, then the built the rest of their toolset around that language.  In this article Ill show you how it is all connected.

A Buffer Full of Mystery

While I was trying to understand how the GTT Support Tool works, I send a “Reset Module” also known as {254, 1}.  When I ran the script, the screen rebooted, and then the serial monitor dumped out a boat load of stuff.  You can see it in the picture below:

This was not very different than when I first attached to the screen using the Bridge Control Panel.  Here are the first I2C reads after the reboot.

So, what is all of this stuff?  The answer to that question resides in how this whole thing is put together.

GTT Scripts

All interactions with the display are done via the command language.  There are multiple paths for sending commands to the screen, the ones we have talked about so far, I2C, USB and UART.  And the one path that I have not specifically talked about but works exactly the same way, the mass storage sd-card.

When you design a screen in GTT Designer, what it does is take that screen, and spit out a text based command language.  Here is a screenshot of that for the test project that I am working on.

Then GTT Designer compiles the text into a binary file with the GTT2.0 and GTT2.5 commands.  You can look at the binary file with a binary file editor.  Here it is for Screen1.bin

And you should recognize the bytes you see.  Look at the top and you will see {FE, 05, 00} which in decimal is {254, 05, 00} and if you look in the GTT20 manual you will find that is “Set Communication Channel to None”.  Here is the screenshot from the manual.

But, what happens when the device boots?  Well, they followed the lead from MS-DOS and created a file called “autoexec”.  And, if you look in that file they so graciously tell you what happens.

How cool is that?  All of these commands are just the things that you did on the project setting and the display settings.  And the last line of the file launches just launches Screen1.bin, which is just the binary file of the commands it takes to load the Screen1.

Now back to the original question.  What is the buffer full of mystery?  Simple, it is the output of all of the commands (if they make output) that are in the autoexec binary and the screen1 binary.  If you had happened to set the default channel to “none” you will find that the buffer doesnt have anything in it… which should make sense.

So, when the manual says to delete the autoexec at the top level directory in order to reset the board.  All that does it remove all of the settings that you created in your project.

The only thing that I wish is that they gave you access to the txt–>bin compiler.  But oh well.

You can "git" these projects from

https://github.com/iotexpert/GTT43A

And the driver library from 

https://github.com/iotexpert/GTT-Client-Library

Title
Matrix Orbital GTT43: A Cool Display
Matrix Orbital GTT43A: Serial Interface
Matrix Orbital GTT43A: GTT Scripts
Matrix Orbital GTT43A: A PSoC 4 Interface
Matrix Orbital GTT43A: Debugging the I2C
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: PSoC 6 using RTOS and the GTT Driver Library

Matrix Orbital GTT43A: Serial Interface

Summary

In this article I will show you how to interact with the Matrix Orbital GTT43A display using the GTT Support Tool via the KitProg UART on a CY8CKIT-044  and the Bridge Control Panel via I2C.  This article will be broken up into four parts

  1. Building a Test Project in GTT Designer
  2. GTT Protocol 2.0
  3. GTT Protocol 2.5
  4. GTT Support Tool
  5. Bridge Control Panel

Build a Test Project

In order to understand the whole serial interface, I will build a very simple project with two buttons.  You can find this project in my GitHub site at git@github.com:iotexpert/GTT43A

Start up a new project called “BasicTest”.  Accept the defaults for Project settings.  On the Display Settings screen change the “Default Channel” to Serial and the Flow Control to Off.  I am using the CY8CKIT-044 to talk to the screen.  On that development kit there is a KitProg which serves as a programmer (for the PSoC 4200M) and as a serial bridge.  That serial bridge does not have the flow control pins turned on.  If you leave the flow control on, the screen will never transmit and you will need to poke your eyeballs out trying to figure out why.

  

Drag a Circle Button (GTT2.5) onto the screen.  Change the Text value (on the right panel) to “GTT25”

 

Now click on the “Legacy” tab and drag a “Circle Button” onto the screen.  Then change the text label to “Legacy”.  In addition change the “ID” from “Auto” to “2”.  I noticed that later in this article that if the button ID is “auto” it will not be identified in the messages.  Im not sure if this is a bug or my lack of understanding.

When I click “Deploy” I end up with this error message.  This appears to be a bug in the GTT Designer.

Click on the “Font” and go to the directory where the Font is supposed to be… and find that it is actually empty.

So.  I navigate up a directory and then down into “LibraSans” instead of “libra-sans-modern”.

Once that is done press “Deploy”, and I get Success!

Now that I have the project built I need to wire the whole mess together.  I am going to use a CY8CKIT-044.  This development kit has a PSoC4200M.  Although the project is using a PSoC6, I didnt have any level translators at my house (bad Alan) and I wanted to get going … so I used the 5V tolerant PSoC 4. [note: it turns out that the MCU used on the display has 5V tolerant serial I/Os but is a 3.3v MCU so I could have used it directly on the PSoC 6 without this work around]  On the far left of the development kit, right next to the USB is the KitProg connector.  This connector has an I2C Master and a UART Bridge.  In addition I can connect the I2C pins to PSoC, the Display and the Bridge.  Here is the schematic for the whole mess.

When you look on the back of the CY8CKIT-044 you can see the same wiring diagram on the silkscreen.

Here is a picture of the development kit.  You can see the top green/yellow wires go to P40/P41 (that is the I2C Bus) and the other green/yellow set goes to P12[6] and P12[7] that is the KitProg UART.  I suppose I should have used different colors for I2C and UART… oh well.

And finally the whole mess.

GTT43A Protocol 2.0

They way that the system works is you can send a command data packet to the display via one of the serial interfaces and then if there is a response required,  it will respond with a response data packet.  In addition when some event occurs on the display (like a touch screen button being pressed) it will send you a response data packet.

There are two GTT Protocols, one called 2.0 and one called 2.5.  First the 2.0 protocol.  You send messages in the GTT2.0 command data packet format.  That format is simple:

  1. 254 – 1 byte
  2. Message ID (look in the manual for all of the legal message codes) – 1 byte
  3. Optional data 0-n bytes

If there is a required response, the screen will respond with a data packet in the following format

  1. 252 – 1 byte
  2. Message ID – 1 byte
  3. Length MSB – 1 byte
  4. Length LSB – 1 byte
  5. Optional Data – 0–>65535 bytes

The total length of the response will be (length msb) << 8 | length lsb – big endian.

For example a command with no optional data …. like reset will look like this { 252, 1 }. [obviously dont send the braces or the comma]

A command with some optional data is “Set Backlight Brightness”. If you want the brightness set to 23% you would send is {252, 153, 23}.

An example of a command that will respond with a response data packet is “Get Module Type” where you would send {254, 55 } and the screen would respond with {252, 55, 00 02 147 01}.  Here is the picture from the manual.

When you look at the picture above there are several things to be VERY careful about.

First, the Matrix Orbital people appear to think in decimal not hex.  So they show you {252,55,00,02,147,01) instead of (0xFE, 0x37, 0x00, 0x02, 0x93, 0x01}.  Notice that my GTT43A value is decimal 37633 which is also known as Hex 0x9301.  I  often found myself typing hex when I mean decimal and vice versa.

The second thing to be careful about is that they send values in big endian… so if you are using an ARM processor be aware as ARM is almost always little endian.

The third thing to be careful about is that the length parameter in the message is always 16-bit Big Endian even though the documentation often shortens this to “Length”

Lastly, if they send you a 16-bit value it will show up as big endian, and they will probably show it to you in decimal (like table 14 above).  Which can look kinda weird in decimal as it is two bytes.

GTT43A Protocol 2.5

As I worked on understanding what the screen is doing, I noticed that I was getting message codes that were not documented in the GTT2.0 Manual.  For example when you press a button you will get {252,235,0,5,21,0,0,1,1}.  When you look at this packet you can recognize the 252… and the message code 235 … and the length seems to make sense 5 … but the rest of the message is less clear.

So I called technical support, which is excellent, and Daniel agreed to send me a prerelease of the new manual.  When you look at the manual you will find:

Which makes good sense as I pressed a button, and the object ID was “1”.  The pre release version of the manual that I got seems to be missing information, but Im hopeful they will get it done and released soon.  But for now, I can work with it.  If you need a copy of the manual you will need to contact them directly.

GTT Support Tool

When you install GTT Designer, it will also install a program called the GTT Support Tool.  This tool will allow you to send and receive GTT messages to/from the screen.   I have the screen attached to the UART on the KitProg Serial bridge (with jumper wires).  This means I will see the screen in the Device Manger under the Ports->KitProg (see the COM5)

You can run the GTT Support Tool from the Start menu or from the Tools menu on the GTT Designer.

When the tool starts you can pick out which ComPort to talk to and setup the Flow Control (meaning use or not CTS/RTS).  The KitProg on the 4200M does not have the CTS/RTS so you need to disable it or things will not work.

When you press the “Test Connection” it will send a message via the UART/ComPort and wait for a response. I am not sure, but I suspect that it is sending a “Get Module” command.  In the picture below you can see that I got both the send and the receive message to work.

I was struggling over the weekend to get the communication to work correctly.  Specifically I was not getting return messages.  It turned out that the project that you build in the GTT Designer must have “Flow Control” turned off and the Default Channel set to Serial or the serial communication responses will not work.  Remember you can set this on the “Display Settings”

You can also send the commands to set the communication channel and flow control, but I found it easier to get the project to the correct settings in the GTT Designer.

One you have a functional connection you can click on the “Commands” tab.  On the left of the screen you will see a list of commands (well… actually on GTT2.0 commands).  On the right side of the screen you will see your “script”, which obviously starts blank.

The first thing that you should do is press the little split screen button just to the right of the red “X”.  When you do this it will bring up a new window that will show what is sent, and what is received.  Obviously it starts blank.

Now you can double click on a command, like “Get Module Version” and it will bring up the command.  When you press “OK” it will add it to the script.  You CAN change the command and it will add it the script (but with a misleading name)

Here is what the window looks like after I add the “Get Module Type” command.

In order to Run the command, you need to click in the script window and then press the green run button.  If you have not clicked in the script window it will not run. Once you have run the command your viewer window will look like this.

In the window above you can see that I wrote to the serial port a {254,55} and the screen responded {252,55,0,2,147,14}.  When you decode this message don’t forget that the data above is in decimal.  In this the example the message the 147,14 is actually 0x930E which is 37646.  Unfortunately 37646 is not documented in the GTT 2.0 protocol manual (but my screen is a GTT43A).

Another cool thing that that happens in the Debug view is that it shows events that are initiated from the screen.  For instance in the picture below I pressed the “GTT25” button from my example project.  It show the button press and the button release.

 

Bridge Control Panel

For my real project I want to interact with the GTT43A via I2C.  For this kind of thing I always like to use the Bridge Control Panel first. I have written a bunch of articles about using BCP to debug.  This allows me to act as an I2C Master and see how the device acts as a slave.  The first thing that I do is press “List Devices” on the BCP.  It shows me that there are a number of things connected to the I2C bus.

You might recall that from the Display Settings that the default I2C address is 80.

That 80 is also known as 0x50 (here we go with the Hex / Decimal thing again).  Moreover that 80 (0x50) is an 8-Bit address, in other words shifted left 1 from the 7-bit address of 0x28.

After I verified that the screen was attached to the bridge control panel.  The next thing to do was to set the default communication via I2C.  Here is the section of the documentation

This means that I need to send {0xFE, ox05, 0x02}.  When I send that command I get ACKs … good.

The next thing that I do is test to make sure that the legacy button is working correctly.  When I press it and then read some bytes I get

Then I press the GTT 25 Button and get {FC,EB,00,05,15,00,00,01,01} and then {FC,EB,00,05,14,00,00,01,00} in other words a press of button ID 1 and then a release of button ID 1.

Now that we understand the command protocol and we know how to talk to the screen, in the next article Ill talk about GTT Scripts.

You can "git" these projects from

https://github.com/iotexpert/GTT43A

And the driver library from 

https://github.com/iotexpert/GTT-Client-Library

Title
Matrix Orbital GTT43: A Cool Display
Matrix Orbital GTT43A: Serial Interface
Matrix Orbital GTT43A: GTT Scripts
Matrix Orbital GTT43A: A PSoC 4 Interface
Matrix Orbital GTT43A: Debugging the I2C
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: PSoC 6 using RTOS and the GTT Driver Library

Matrix Orbital GTT43A: A Cool Display

Summary

I have recently found myself way way down a rabbit hole on a project that uses a Matrix Orbital GTT43A.   This display is a super cool, though expensive, intelligent touch screen display.  Here is a picture:

I call it intelligent because it has a fully featured CPU that runs all of the display and touch functions for you.  Basically you build all of the screens with GTT GUI elements (sliders, buttons, text etc.) using the Matrix Orbital design tool called GTT Designer.  Then you program that configuration into an sd-card that is attached to the display.  When you power up the display, your configuration comes up and you are off to the races.  Then, in your system you can then simply interact with the display via I2C, UART, USB or SPI.

I know that it seems simple, but I will say that this has turned into quite an adventure which has, in turn, been an awesome learning experience.

Matrix Orbital GTT43A

The Matrix Orbital GTT43A is a 4.3″ backlit LCD display with a capacitive touch screen.  Here is a screenshot from the Matrix Orbital website.  Yes, you read the price right, it is $155.84…. well actually $165.84 with capacitive touch.

 

Here is a picture of the back of the screen.

On the far left you can see the connector labeled “Keyboard Power”.  This is a place where you can plug in a matrix keyboard that looks like the next picture.  Though I am not exactly sure why you would make a nice touch screen interface and then use a mechanical button interface?

In the middle of the picture you can see a micro-sd card which holds all of your screen configuration information.  The sd-card is a normal mass storage card and you can drag and drop your configuration, or new firmware for the screen using normal Windows.  You can also put the display into mass-storage mode and then access the card via the mini-usb-b connector that is in the upper left.

The display also supports 6 digital GPIOs (which you can see on the lower left of the picture).  It also has a piezoelectric buzzer and a haptic vibrator.

In order to talk to the display with your system controller you can use I2C, UART, SPI or USB.  It is interesting that you seem to be able to use multiple interfaces at the same time, which is pretty convenient for debugging.

The display requires a decent amount of juice.  Here is a picture on my desk, 5V and 375mA,  which is more than the development kit I was using will provide.  In fact it, will sort of work for a while off the devkit power supply, but when you push a button on the screen it will reboot the display.

GTT Designer

GTT Designer is a Windows GUI building tool.  It is straight forward to use.  When you start up the software it will give you a choice of displays to build for.  In the picture below you can see that it detected that I had a GTT43A attached to my computer via USB.

 

After setting up the name of my project and clicking “New Project” I am given the choice of customizing the global settings for the display.

Once the project is setup, you are now given the ability to configure the display settings.  On this screen you can setup a number of things, including the I2C address of the display.  Also, on the screen below you can see “Default Channel” is set to none.  What this means is that any GUI thing that happens will send messages to the default channel.  In this case none.  But that isnt what I want so I change it to I2C (next picture)

What I really want it all of the output to go to the I2C.  But given that the screen is an I2C slave, and it cant send out data, what does that really mean?  What it means is that all of the output goes into a buffer, that slowly fills up until you read the data out of it via I2C.  If you setup the Default channel as Serial, the data will go directly out via UART.

Once the display settings are done you will end up with a screen that looks like this.  On the left side of the screen you can pick out the different GUI elements (buttons, text labels, sliders, images etc) and the drag them onto the screen.  Notice that there are four tabs, Tools, Legacy Tools, Assets, Overview.  At some point very recently Matrix Orbital did a massive re-engineering project to make things simpler to interact with the screen.  When they did this, the created a who new set of widgets called “GTT2.5” widgets.  You can still use the old widgets which they now call “Legacy”

On the screen below you can see that I placed a bunch of different GUI elements for my test project.  When you click on an element, the right hand side of the screen will let you update properties of the object e.g. color, size, name.  You can also create events (more on that in the next article)

One you have drawn your screen you then want to build and program the project.  Or in their language generate and deploy.  To do this you can either click generate then click deploy, or just click deploy.  When you do this it will first build all of your project into a directory on your computer called “Output”.  There are three interesting things in the output directory.

  1. autoexec.txt/bin – files that contains a script that runs when the display turns on (more on that in the next article)
  2. Report.txt – a file that contains information about the objects, names, ids etc (this is important for your software)
  3. GTTProject1 – a directory with all of the files required for your project.

When you look in the GTTProject1 directory you will see that it contains a directory for “Screen1”.  If I had made multiple screens it would have made multiple directories.  It also has a directory called “Fonts”, which big surprise, contains the Fonts that are used by the project.

In the “Screen1” directory you will see a bunch of bitmap files, text files etc.

Screen1.txt contains a textual version of the “program” that creates the screen.  Here is a snapshot of the top of the file.

And “Screen1.bin” which is the compiled version of the “Screen1.txt”- more on this in the next article.

When you click the deploy button, it sends a command to the screen to put it into mass storage mode which just turns the screen into a flash disk which can be written/read by your PC.  Here is what the screen looks like when it is in mass storage mode:

After the device is in mass storage mode, GTT Designer copies all of the file onto the sd-card of the display and the reboots the display to run the program.

When you are running GTT Designer you can switch the display back and forth between Mass Storage mode and Display mode on the Tools menu by selecting “Switch Mode”

In the next several articles Ill show you how to build firmware to talk to the screen.

You can "git" these projects from

https://github.com/iotexpert/GTT43A

And the driver library from 

https://github.com/iotexpert/GTT-Client-Library

Title
Matrix Orbital GTT43: A Cool Display
Matrix Orbital GTT43A: Serial Interface
Matrix Orbital GTT43A: GTT Scripts
Matrix Orbital GTT43A: A PSoC 4 Interface
Matrix Orbital GTT43A: Debugging the I2C
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: GTT Driver Library - Part 1
Matrix Orbital GTT43A: PSoC 6 using RTOS and the GTT Driver Library

The Lost Art of Assembly Language Programming

Cypress introduced it’s first mass market microcontroller in 2001. It used a Cypress designed 8 bit CISC processor running at 24 MHz, with as little as 4 KB Flash and 256 bytes RAM. Wrapped around that was a neat array of programmable analog and digital blocks. This may not sound like much, but with a creative mindset you could get these parts to do amazing things. For instance, I once implemented a complete ultrasonic ranging sensor with full wave analog demodulation in a single PSOC1 as shown below.

PSOC1 Ultrasonic Ranging

With CPU resources at a premium, you had to write tight, efficient code to get the most out of PSOC1. A single C library could consume the entire Flash. Consequently, I wrote a lot of assembly code. That’s not so bad, since I actually enjoy it more than C. There’s a certain elegance to well written, fully commented machine code. In the case of PSOC1, here’s what you had to work with: 5 registers, some RAM and Flash. That’s it. Real Men Write In Assembly.

M8C Architecture

 

We’ll start with simple machine code instruction to make the CPU do something. You can reference the M8C assembly language user guide here for more details. To get the M8C to execute 2+3=5 we write:

mov A,2       ;Load A with 2
add A,3       ;Add 3 to A. Result=5 is in A

We can get fancy by using variables. Let’s add R=P+Q. Assume P is at RAM location 0x20 and Q is at location 0x21, and R is at 0x22

;Initialize variables
mov [0x20],2  ;Load P with 2
mov [0x21],3  ;Load Q with 3

;Add variables
mov X,[0x20]  ;X <- P
mov A,[0x21]  ;A <- Q
adc [X],A     ;X <- P + Q
mov [0x22],X  ;R <- X

The fun thing about assembly is you can always dream up cool ways of doing things in less operations based on the machine’s instruction set. For example, we can simplify the above code as follows:

;Add variables
mov [0x20],[0x22]   ;R <- P
adc [0x22],[0x21]   ;R <- P + Q

In my experience, a good programmer with expert knowledge of the instruction set and CPU resources can always write better code than a compiler. There’s a certain human creativity that algorithms can’t match.

All that being said, I had not seen a good “machine code 101” tutorial for writing assembly in PSOC Creator on modern ARM M0 processors. So let’s walk through one now. We’ll use a CY8CKIT-145 and blink the LED. It’s just what happens to be laying around on the lab bench. Any PSOC4 kit will do.

CY8CKIT-145-40XX

We’ll start by creating a standard project in PSOC Creator, drop a Digital Output pin on the schematic and call it “LED”

Then open the .CYDWR file and drag pin LED to P2[5], since that’s where it is on the LED board. Yours may be in a different place on whatever board you are using.

Now under “Source Files” in the workspace directory you will delete main.c and replace with main.s

Now right clock on “Source Files”, select “Add New File” and select “GNU ARM Assembly File” in the dialog. Rename the file from GNUArmAssembly01.s to main.s

Your workspace ends up looking like this:

So far, so good. Now open main.s, delete everything if it’s not empty and add the following code. This sets up the IDE for M0 assembly architecture

// ==============================================
// ARM M0 Assembly Tutorial
//
// 01 – Blink LED
// ==============================================
.syntax unified
.text
.thumb

Next we need to include register definitions for the chip we are using. These are all from the PSOC4 Technical Reference Manual (TRM)

// ==============================================
// Includes
// ==============================================
.include “cydevicegnu_trm.inc”

Then we are going to do some .equ statements, same as #define in C. This identifies the Port 2 GPIO data register plus bits for the LED pin in on and off state

// ==============================================
// Defines
// ==============================================
.equ LED_DR,CYREG_GPIO_PRT2_DR          // LED data reg address
.equ LED_PIN,5                          // P2.5
.equ LED_OFF,1<<led_pin                 // 0010 0000
.equ LED_ON,~LED_OFF                    // 1101 1111

Now you add the right syntax to set up main()

// ==============================================
// main
// ==============================================
.global main
.func main, main
.type main, %function
.thumb_func

Finally we add the code for main, which is pretty simple:

main:
ldr r5,=LED_DR      // Load GPIO port addr to r5

loop0:
ldr r6,=LED_ON      // Move led data to r6
str r6,[r5]         // Write r6 data to r5 addr

ldr r0,=0xFFFFFF    // Argument passed in r0
bl CyDelayCycles    // Delay for N cycles

ldr r6,=LED_OFF     // Move led data to r6
str r6,[r5]         // Write r6 data to r5 addr

ldr r0,=0xFFFFFF    // Argument passed in r0
bl CyDelayCycles    // Delay for N cycles

b loop0             // Branch loop0

.endfunc            // End of main
.end                // End of code

One thing to note: The function CyDelayCycles is defined CyBootAsmGnu.s. Any function in assembly gets its arguments passed by the first 4 registers r0,r1,r2 and r3. Before calling the function you simply load r0 with the argument then do a bl (branch with link). This is also why I avoided the first 4 registers when messing with LED data. If you’re interested in doing more with ARM assembly, definitely read the Cortex M0+ Technical Reference Manual. It’s a great primer for the M0+ instruction set.

That’s it. End result is a blinking LED. Cool thing is you can use PSOC Creator with all it’s nice features, but sill access the power of machine code.

You can get the project ZIP file here.

Regards
Darrin Vallis

Bosch BMI160 w/PSoC 6 CY8CKIT-028-EPD

Summary

I have been working on a bunch of PSoC 6 projects in preparation for some new videos and for use at Embedded World.  For one of those project I need a motion sensitive remote control… and conveniently enough we put a Bosch BMI160 motion sensor onto the new CY8CKIT-028-EPD shield that comes with the CY8CKIT-062-BLE development kit.

In this article I will show you how to make a complete test system using PSoC 6 to talk to the BMI160.  The steps are:

  1. Clone the Bosch BMI160 Driver Library
  2. Create a new PSoC 6 project & add the driver library
  3. Create the HAL for the Bosch Driver
  4. Create the main firmware and test

Clone the Bosch BMI160 Driver Library

When I started this, I knew that the board had a motion sensor but I didnt know what kind.  I assumed that it was an I2C based sensor, so I attached the bridge control panel and probed the I2C bus.  But this is what it said:

Bridge Control Panel

So… what the hell?  Then I looked at the board to try to figure out what was going on… and low and behold… the board that I had was a prototype that was done before we added the motion sensor.  Here it is:

And here is a board with the sensor on it.

CY8CKIT-028-EPD with Bosch BMI160

When I plug in that board and test it with the Bridge Control Panel I get:

The next thing that I did was look at the schematics.  OK, you can see that the Inertial Measurement Unit (IMU) is a BMI160 that is connected to the I2C bus.  The other cool thing is that the devkit team hooked up the two interrupt lines.  These lines are typically used for the IMU to signal the PSoC 6 that something has happened (like maybe the user started moving).

After looking at the schematics, the next step was to look at the BMI160 datasheet and try to figure out how to interface with the device. Typically these devices have a boatload of registers with a mind boggling number of bit fields.  This is always the un-fun part of the process.  But this time when I went to the BMI160 device page on Bosch’s website, there was a button that says “Documents and Drivers” and when you click it, there is a link to GitHub with a BMI160 driver.  Score!

To make this work you just “git clone git@github.com:BoschSensortec/BMI160_driver.git”

Create New PSoC 6 project & with Bosch BMI160 driver library

So, lets get on with testing it.  First create a new PSoC 63 Project

Use a blank schematic

Give it a name

Add the Retarget I/O and FreeRTOS (from the build settings menu)

Add a UART and an I2C Master

To get the I2C to be a master you need to double click it and change it into a master

Then assign the pins

Run “Build -> Generate Application” to get all of the PDL firmware you need.

Edit stdio_user.h to use the UART (scan down the stdio_user.h to find the right place)

#include "project.h"
/* Must remain uncommented to use this utility */
#define IO_STDOUT_ENABLE
#define IO_STDIN_ENABLE
#define IO_STDOUT_UART      UART_1_HW
#define IO_STDIN_UART       UART_1_HW

Add the “BMI_driver” directory to the include path of the CM4 project.  (To get to this menu right click the project and pick “build settings”)

Add the Bosch Driver files to the project

 

Create the HAL for the Bosch driver

It is simple to use the Bosch driver.  All you need to do is update the HAL.

  1. Provide a function to write I2C registers
  2. Provide a function to read I2C registers
  3. Provide a function to delay for a specified number of milliseconds
  4. Create a structure to hold initialization information and function pointers

This device implements what Cypress calls the “EZI2C” protocol which is also known as an I2C EEPROM protocol.  The device is organized as an array of registers.  Each register has an address from 0->0xFF (a single byte of addresses).  To write to a register you need to

  1. Send an I2C Start
  2. Send the 7-bit I2C address
  3. Send a write bit (aka a 0)
  4. Send the register address you want to write to (dont confuse I2C address with the internal BMI160 address)
  5. Send the 8-bit value that you want to write
  6. Send a stop

A cool thing with EZI2C is that it keeps track of the address, and automatically increments the register address each time you write.  This means you can write a sequence of address without having to do a complete transaction for each address.

Given that introduction the write function is simple:

static int8_t BMI160BurstWrite(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
    
    Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
    Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
    for(int i = 0;i<len; i++)
    { 
        Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,data[i],0,&I2C_1_context);
    }
    
    Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
    
    return 0;
}

In order to read you do a similar transaction to write.  Specifically the steps are:

  1. Send an I2C Start
  2. Send the 7-bit I2c address
  3. Send a WRITE bit aka 0
  4. Send the address of the register you want to read
  5. Send an I2C re-start
  6. Read a byte
  7. Send a NAK
  8. Send a stop

The read transaction is similar to the write in that you can continue to read sequential bytes by sending an ACK.  The last byte you read should be NAK-ed to tell the remote device that you are done reading. Given that the code is also straight forward.

// This function supports the BMP180 library and read I2C Registers
static int8_t BMI160BurstRead(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
    
    Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
    Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
    Cy_SCB_I2C_MasterSendReStart(I2C_1_HW,dev_addr,CY_SCB_I2C_READ_XFER,0,&I2C_1_context);
    for(int i = 0;i<len-1; i++)
    {
        Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_ACK,&data[i],0,&I2C_1_context);
    }
    Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_NAK,&data[len-1],0,&I2C_1_context);
    
    Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
    
    
    return 0;
}

There is one error with both my read and write functions.  And that error is?  No error checking.  I have seen some intermittent weirdness in which the I2C bus gets locked that ends up requiring a reset to fix.  This could be prevented by checking error codes on the I2C functions.

Now that we have a read and write function we can setup our device:  To do this:

  1. Setup a structure of type bmi160_dev
  2. Initialize the function pointers
  3. Initialize the settings for the device
  4. Finally send the settings
static struct bmi160_dev bmi160Dev;

static void sensorsDeviceInit(void)
{

  int8_t rslt;
  vTaskDelay(500); // guess

  /* BMI160 */
  bmi160Dev.read = (bmi160_com_fptr_t)BMI160BurstRead;
  bmi160Dev.write = (bmi160_com_fptr_t)BMI160BurstWrite;
  bmi160Dev.delay_ms = (bmi160_delay_fptr_t)vTaskDelay;
  
  bmi160Dev.id = BMI160_I2C_ADDR;  // I2C device address

  rslt = bmi160_init(&bmi160Dev); // initialize the device
  if (rslt == 0)
    {
      printf("BMI160 I2C connection [OK].\n");
      bmi160Dev.gyro_cfg.odr = BMI160_GYRO_ODR_800HZ;
      bmi160Dev.gyro_cfg.range = BMI160_GYRO_RANGE_125_DPS;
      bmi160Dev.gyro_cfg.bw = BMI160_GYRO_BW_OSR4_MODE;

      /* Select the power mode of Gyroscope sensor */
      bmi160Dev.gyro_cfg.power = BMI160_GYRO_NORMAL_MODE;

      bmi160Dev.accel_cfg.odr = BMI160_ACCEL_ODR_1600HZ;
      bmi160Dev.accel_cfg.range = BMI160_ACCEL_RANGE_4G;
      bmi160Dev.accel_cfg.bw = BMI160_ACCEL_BW_OSR4_AVG1;
      bmi160Dev.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE;

      /* Set the sensor configuration */
      bmi160_set_sens_conf(&bmi160Dev);
      bmi160Dev.delay_ms(50);
    }
  else
    {
      printf("BMI160 I2C connection [FAIL].\n");
    }
}

Create the main firmware and test

Finally I test the firmware by running an infinite loop that prints out acceleration data.

void motionTask(void *arg)
{
    (void)arg;
    I2C_1_Start();
    sensorsDeviceInit();
    struct bmi160_sensor_data acc;

    while(1)
    {
        
        bmi160_get_sensor_data(BMI160_ACCEL_ONLY, &acc, NULL, &bmi160Dev);      
        printf("x=%4d y=%4d z=%4d\r\n",acc.x,acc.y,acc.z,);       
        vTaskDelay(200);
    }
}

Now you should have this:

And finally the whole program in one shot

#include "project.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include "bmi160.h"

static struct bmi160_dev bmi160Dev;

static int8_t BMI160BurstWrite(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
    
    Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
    Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
    for(int i = 0;i<len; i++)
    { 
        Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,data[i],0,&I2C_1_context);
    }
    
    Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
    
    return 0;
}

// This function supports the BMP180 library and read I2C Registers
static int8_t BMI160BurstRead(uint8_t dev_addr, uint8_t reg_addr,uint8_t *data, uint16_t len)
{
    
    Cy_SCB_I2C_MasterSendStart(I2C_1_HW,dev_addr,CY_SCB_I2C_WRITE_XFER,0,&I2C_1_context);
    Cy_SCB_I2C_MasterWriteByte(I2C_1_HW,reg_addr,0,&I2C_1_context);
    Cy_SCB_I2C_MasterSendReStart(I2C_1_HW,dev_addr,CY_SCB_I2C_READ_XFER,0,&I2C_1_context);
    for(int i = 0;i<len-1; i++)
    {
        Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_ACK,&data[i],0,&I2C_1_context);
    }
    Cy_SCB_I2C_MasterReadByte(I2C_1_HW,CY_SCB_I2C_NAK,&data[len-1],0,&I2C_1_context);
    
    Cy_SCB_I2C_MasterSendStop(I2C_1_HW,0,&I2C_1_context);
    
    
    return 0;
}


static void sensorsDeviceInit(void)
{

  int8_t rslt;
  vTaskDelay(500); // guess

  /* BMI160 */
  bmi160Dev.read = (bmi160_com_fptr_t)BMI160BurstRead;
  bmi160Dev.write = (bmi160_com_fptr_t)BMI160BurstWrite;
  bmi160Dev.delay_ms = (bmi160_delay_fptr_t)vTaskDelay;
  
  bmi160Dev.id = BMI160_I2C_ADDR;  // I2C device address

  rslt = bmi160_init(&bmi160Dev); // initialize the device
  if (rslt == 0)
    {
      printf("BMI160 I2C connection [OK].\n");
      bmi160Dev.gyro_cfg.odr = BMI160_GYRO_ODR_800HZ;
      bmi160Dev.gyro_cfg.range = BMI160_GYRO_RANGE_125_DPS;
      bmi160Dev.gyro_cfg.bw = BMI160_GYRO_BW_OSR4_MODE;

      /* Select the power mode of Gyroscope sensor */
      bmi160Dev.gyro_cfg.power = BMI160_GYRO_NORMAL_MODE;

      bmi160Dev.accel_cfg.odr = BMI160_ACCEL_ODR_1600HZ;
      bmi160Dev.accel_cfg.range = BMI160_ACCEL_RANGE_4G;
      bmi160Dev.accel_cfg.bw = BMI160_ACCEL_BW_OSR4_AVG1;
      bmi160Dev.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE;

      /* Set the sensor configuration */
      bmi160_set_sens_conf(&bmi160Dev);
      bmi160Dev.delay_ms(50);
    }
  else
    {
      printf("BMI160 I2C connection [FAIL].\n");
    }
}
#define MAXACCEL 8000
void motionTask(void *arg)
{
    (void)arg;
    I2C_1_Start();
    sensorsDeviceInit();
    struct bmi160_sensor_data acc;

    while(1)
    {
        bmi160_get_sensor_data(BMI160_ACCEL_ONLY, &acc, NULL, &bmi160Dev);
        printf("x=%4d y=%4d z=%4d\r\n",acc.x,acc.y,acc.z);
        
        vTaskDelay(200);
    }
}



int main(void)
{
    __enable_irq(); /* Enable global interrupts. */

    UART_1_Start();

    xTaskCreate( motionTask, "Motion Task",400,0,1,0);
    vTaskStartScheduler();

    while(1);
}

/* [] END OF FILE */

 

PSoC 4200M WDT Long Deep Sleep

Summary

Earlier this year I wrote an article about using the PSoC 4200M WDT Watch Dog Timers.  In it I described a bunch of things that you could do with the PSoC 4200M WDT to help you time events, reset the part etc.  Recently a user named JAGIR asked if I could generate interrupts slower than 2 seconds and have the PSoC 4200M in deep sleep.  The answer to both of those questions is yes.

In order to make this work you need to “cascade” two WDTs together, something you can only do with firmware.  I have updated my previous workspace with a new project called “LongDeepSleep” which you can “Git” on my GitHub website or you can “git@github.com:iotexpert/PSoC4-WDTExamples.git”

Configure the PSoC 4200M WDT Design

First add a digital output pin called “RED” to the schematic

PSoC 4200M WDT Schematic

Then assign it to P0[6]

PSoC 4200M WDT - DWR Pin Assignment

Go to the clocks tab of the design wide resources.  Then click on “Edit Clock”.

PSoC 4200M WDT Clocks

On the Low Frequency Clocks configuration page of the design wide resources turn on the two WDTs and setup the dividers.

PSoC 4200M WDT

System Reference Guide – PSoC 4200M WDT

All of the documentation for the PSoC 4200M WDT is in the “Low Frequency Clock aka cy_lfclk” of the system resources documentations.

PSoC 4200M WDT System Reference Guide

When you read a little bit down in the PSoC 4 Low Frequency clock documentation you will find “Note The EDT cascade options are not configurable using these panels but the APIs can be used to perform cascading of WDTs”

PSoC 4200M WDT

Then you search a little bit further down in the document and you will find the API CySysWDTSetCascade which will allow you to hook multiple 16-bit counters together to get 32 or more bits.

PSoC 4200M WDT - CySysWdtSetCascade

Write the PSoC 4200M WDT Firmware

The first example will blink the LED every 4 seconds.  Remember from the configuration above it has a 32KHz input clock with a divider of 1024 on WDT0 and a divider of 128 on WDT1.  That means you will get 32KHz / (1024 *128) = 0.25 Hz aka every 4 seconds.

#include "project.h"

void wdtCallback()
{
    RED_Write(~RED_Read());
    
}

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */
    CySysWdtSetInterruptCallback(CY_SYS_WDT_COUNTER1,wdtCallback);
    CySysWdtSetCascade(CY_SYS_WDT_CASCADE_01);
    
    for(;;)
    {
        CySysPmDeepSleep();
    }
}

You can also get different delays by changing the dividers using firmware.  In the example below it is 1Hz output.

#include "project.h"

void wdtCallback()
{
    RED_Write(~RED_Read());
}

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */
    CySysWdtSetInterruptCallback(CY_SYS_WDT_COUNTER1,wdtCallback);
    CySysWdtSetCascade(CY_SYS_WDT_CASCADE_01);
    
    CySysWdtSetMatch(0,512);
    CySysWdtSetMatch(1,64);
    // Total divide = 512*64 = 32768 
    
    for(;;)
    {
        CySysPmDeepSleep();
    }
}

 

Serial Wire View with PSOC4

I use PSOC4 to invent all kinds of unique solutions for customers. Usually, they want them field upgradeable to deploy new features or fix bugs. Fortunately Cypress has a great I2C boot loader to meet this need, so I use the heck out of it.

Cypress has a great debugger built into PSOC Creator which fully supports all the ARM Serial Wire Debug protocols such as breakpoints, single step, memory, register viewing etc. However, when you are running a boot loader the debugger does not work! Why not? Because with a boot loader there are two applications resident in PSOC4: The boot loader and application. This is not supported by Cypress implementation of SWD.

Where does this leave you, the intrepid code developer, when debugging a boot loader project? Personally, I have used all kinds of methods: debug UART interface, debug I2C interface, bang out states on pins, debug Bluetooth interface … and on and on. You get the idea. All these methods burn a communications interface and require extra pins on the chip. Sometimes that’s not possible.

The issue recently came to a head when a customer very nearly in production experienced a boot loader failure. One system out of a few thousand was “bricked” when they tried to field  update in the lab. Their pinout is frozen, they can’t add new hardware so how do we look inside PSOC4 and see what’s going on?

I woke up at 2 AM and thought “Ah Ha! SWV!” (Yes, I Am A Geek) Serial Wire View is an ARM native debug protocol that let’s you XRAY the insides of any ARM MCU with the right interface. SWV is a protocol which runs on the SWD pins (clock and data) but also needs the Serial Wire Output (SWO) pin. Cypress left the SWO pin and associated IP off of PSOC4 to save die cost, foiling my great idea. Brief interlude to drink and bang head on desk.

Fortunately, I don’t give up easily. At least my subconscious does not. Woke up the next night thinking “Ah Ha!” again. Wife was mildly annoyed, but tolerates my idiosyncrasies.

Cypress has a nice software UART transmitter implementation. I shamelessly stole it, modified for my purposes and created a custom component. (It’s pretty easy to do this by the way) Baud rate was modified to 230 KBps and the output pin forced to a specific pin with a control file.

Once the component is in place, you can use its _DView_Printf( ) API call to display any debug data. Here is an example:

More about that output pin. Cypress sells a tool for programming and debugging PSOC called CY8CKIT-002, aka MiniProg3. The programming connector consists of VDD, GND, reset, SWD clock and SWD data as shown below.

Since we can’t use SWD protocol for debugging anyway, we can change the pins from SWD to normal GPIO. The pins still function for programming. By default they are in SWD mode as shown.

Going to the system tab of the .CYDWR file, we can change them to GPIO.

Once we do that, the pins look like this. Here’s the trick. We now assign the TX output of our DTView component to pin 3[2], which is available  on the SWD programming header, pin 5.

Can you see where we are going with this? Printf( ) data is now coming out of PSOC4 on pin 3[2], easily accessible on our debug header. This is where MiniProg3 comes in. It can actually receive data as a 230 KBps RX UART on its XRES pin. Weird, right? By building a simple interface cable we can get the data from your debug header into MiniProg3.

MiniProg3 XRES —— SWD HEADER pin 5

MiniProg3   GND —— SWD HEADER pin 2

However, MiniProg3 does not show up as a COM port on your PC, so how do we the data? It needs to be accessed by a host application running the PP_COM API. This is documented under PSOC Programmer Component Object Model COM Interface Guide, Cypress specification 001-45209. If you installed PSOC Creator or Programmer, this document is actually on your PC under C:\Program Files (x86)\Cypress\Programmer\Documents. Engineers don’t like to read instructions. Amazing what you can find when you do.

I wrote a simple  console application which opens MiniProg3 using PP_COM, retrieves data from the serial RX pin via USB and displays it like a simple terminal program. Voila! You now have a serial debugger that works for any PSOC4 project using MiniProg3 as your USB to serial dongle.

Customer was really happy with this. We were able to immediately see his problem and fixed it in about 5 minutes.

Finally, here are all the source files

DTView Firmware : PSOC Creator example project and DTView component

DTViewer Binary : Installer for DTViewer console

ViewerSource : Complete source code for DTViewer console (Requires Visual Studio 2015)

That’s all. Have fun with the new debugging tool.

DTV