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

Internet Insanity

Summary

It was pure Internet Insanity at my house this weekend.  On Saturday I was looking at one of my Meraki MR18 access points and realized that it was not plugged into  the LAN via Ethernet.  In other words it was operating as a mesh repeater.  That situation really sucked given that it was only about 20 feet (through a wall) to the main LAN switch.

But it was even worse than it seemed because I had 20 years of networking equipment stacked up in my wiring closet.  Yes, my house has a wiring closet.  Doesn’t yours?  The first order of business was to yank it all out except the current stuff.  Check out this pile of old stuff.

Apparently over the years I have used about 7 different WiFI and Ethernet solutions.  How is that given that there hasn’t been that many 802.11s?  Hell if I know.  But this turned into a major major job.  Everywhere I looked there was a pile of wires.

And this insanity … you wouldn’t believe how many ethernet cables there were hidden in the cabinet…

So Nicholas and I headed to Lowes (actually 4 times) and got shelfs, wire zip ties etc.  Then I put him to work. My wiring closet is actually under the stairs, so he first installed rubber tiles on the floor.  Then a shelf, and finally reinstalled all of the networking gear.

Now things are much better on my network.  I use Meraki MR18’s which are 802.11ac radios.  They also have the ability to mesh with each other.  The three radios that you see in the picture with the green circle (not filled) are meshed into the network.

And my gigabit ethernet switch.

The MR18s can look at the spectrum usage… looks like 5ghz is basically empty.

While I was cleaning I found two of this cable… Ill bet you don’t know what it is…

 

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

PSoC 6 BLE – Find Me Profile (Target)

Summary

I have been working on making some new videos for Cypress about the PSoC 6 BLE.  Everyone always likes to start with an easy BLE example, and the go to example is the “Find Me”.  I started by building a Find Me Profile example, but when I looked my example I decided that I wanted to trace the actual application all the way back to the Bluetooth Special Interest Group (SIG) specification.  So, that is exactly what I do for this article.

  • Bluetooth SIG Find Me Profile
  • Bluetooth SIG Immediate Alert Service
  • Configure PSoC 6 BLE Schematic & Pins
  • Setup FreeRTOS and Retarget I/O
  • PSoC 6 BLE Firmware Architecture
  • Firmware for the alertTask
  • Firmware for the Immediate Alert Service Callback
  • Firmware for the BLE Callback
  • Firmware for the BLE Task
  • Firmware to start the system

Bluetooth SIG Find Me Profile

The Bluetooth SIG defines a bunch of standard profiles.  A standard Profile is just some combination of standardized Services and Characteristics.  One of those Profiles is the Find Me Profile.  The concept behind the Find Me Profile was that you could connect to a device with the Find Me Profile, and then send it an alert, at which point it would start “alerting” (presumably blinking or beeping).  You could imagine a tag attached to your car keys for instance.  You can get the Find Me Profile Specification from the Bluetooth SIG website.  The spec is a 10ish page pdf that says a Find Me Profile is just a device with an Immediate Alert Service server or client.  Here is a picture from the spec:

The other interesting part of the specification defines how the advertising is supposed to work

Immediate Alert Service

But, what is an Immediate Alert Service?  Well, you can get the Immediate Alert Service specification from the Bluetooth SIG website as well.  It basically says that there is one Service called Immediate Alert with a UUID of 0x1802 and that Service has one Characteristic called “Alert Level” with a UUID of 0x2A06. Here is a screen shot.

Unfortunately the spec doesnt have the UUIDs in it, and you have to follow the [1] to the website.  On the Bluetooth SIG GATT Services UUID definition webpage you can see the UUID of the Immediate Alert Service (0x1802)

And on the Bluetooth SIG GATT Characteristics definition webpage you can see the UUID of the Alert Level (0x2A06)

It also says the alert characteristic is writable with three values (No, Mild, High) and if you click on Alert Level you can see the definition of those:

Next you can see information about the Alert Characteristic.

Configure PSoC 6 BLE Schematic & Pins

Now, lets build the project.  Create a new PSoC 6 BLE project and edit the schematic to have a BLE, UART, and two digital output pins (red, led9).

Assign the pins to the correct place on the CY8CKIT-062-BLE development kit.  I am going to use led9 to show when the device is connected (or not)

Change the build settings to add FreeRTOS (Heap 4) and Retarget I/O

Configure the BLE to be dual core and a peripheral

Add the Find Me Target (GATT Server) profile

Once that is done you will see the Immediate Alert Service (with the correct UUID from above)

And the Alert Level with the correct UUID

Next give the device a name (“FindMe”)

The spec calls for the advertising setup to be as follows

But I have to admit that I dont like to wait… so I configured it like this:

Next, configure the advertising packet to have the name of the device and the fact that it has a IAS Service

Setup FreeRTOS and Retarget I/O

Run “Generate Application” to bring in all of the BLE and PDL firmware.  Then edit “stdio_user.h” to setup the stdio support.

#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

Next, modify FreeRTOSConfig.h to include support for semaphores.

#define configUSE_COUNTING_SEMAPHORES           1

and a much bigger stack

#define configTOTAL_HEAP_SIZE                   (48*1024)

And finally the interrupts to support BLE

/* Put KERNEL_INTERRUPT_PRIORITY in top __NVIC_PRIO_BITS bits of CM4 register */
#define configKERNEL_INTERRUPT_PRIORITY         0xFF
/* Put MAX_SYSCALL_INTERRUPT_PRIORITY in top __NVIC_PRIO_BITS bits of CM4 register */
//#define configMAX_SYSCALL_INTERRUPT_PRIORITY    0xBF
#ifdef __NVIC_PRIO_BITS
      /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
      #define configPRIO_BITS                   __NVIC_PRIO_BITS
#else
      #define configPRIO_BITS                   4        /* 15 priority levels */
#endif    

#define configMAX_SYSCALL_INTERRUPT_PRIORITY    ( 1 << (8 - configPRIO_BITS) )

Firmware Architecture

There will be two tasks

  1. The alertTask which will be responsible for setting the state of the red LED.  To set the state of the LED any other task can set the EventGroup bits.
  2. The bleTask which will handle running the BLE and the generic BLE callback and the IAS callback.

Firmware for the alertTask

The alert task will

  1. Set things up including eventBits which I call “alertState”
  2. Turn off the red led
  3. Go into an infinite loop
  4. The loop will either 1) wait until the end of time or 2) timeout at 500ms)
  5. Then it will determine the cause of the timeout and set the state of the red LED
// These BITs are used to set the state of the red LED
EventGroupHandle_t alertState;
#define ALERT_NO_MASK   1<<0
#define ALERT_MILD_MASK 1<<1
#define ALERT_HIGH_MASK 1<<2

/*****************************************************************************\
 * Function:    alertTask
 * Input:       FreeRTOS Template - unused argument
 * Returns:     void
 * Description: 
 *     This funtion is the mainloop for the alertTask.  It manages the state of
 *     the RED led.  Other tasks communitcate with this task using the alertState
 *     event bits.
\*****************************************************************************/
void alertTask(void *arg)
{
    (void)arg;
    printf("Alert Task Started\r\n");
    TickType_t delay=portMAX_DELAY;
    EventBits_t currentBits;
    
    alertState = xEventGroupCreate();
    xEventGroupSetBits(alertState,ALERT_NO_MASK);
   
    Cy_GPIO_Write(red_PORT,red_NUM,LED_OFF);
    
    while(1)
    {
        currentBits = xEventGroupWaitBits(alertState,ALERT_HIGH_MASK|ALERT_MILD_MASK|ALERT_NO_MASK,
            pdTRUE,pdFALSE,delay);
        switch(currentBits)
        {
            case ALERT_NO_MASK:
                delay = portMAX_DELAY;
                Cy_GPIO_Write(red_PORT,red_NUM,LED_OFF);
            break;
            case ALERT_HIGH_MASK:
                delay = portMAX_DELAY;
                Cy_GPIO_Write(red_PORT,red_NUM,LED_ON);
            break;
            case 0: // case 0 means timer expired & no bits set.   
            case ALERT_MILD_MASK:
                delay = 500;
                Cy_GPIO_Inv(red_PORT,red_NUM);
            break;
        }   
    }
}

Firmware for the Immediate Alert Service Callback

Cypress setup a bunch of APIs that know how to handle a bunch of the Bluetooth SIG Profiles/Services.  First lets look at the PSoC 6 BLE Middleware PDL Documentation to find the IAS Service.  It is in the “BLE Service-Specific API” section.

We want to implement a “GATT Server”… meaning our device has the IAS Server running on it so that a GATT Client can write into our database.  When I click on “IAS Server Functions” it takes me to this section of the documentation.  Basically what you do in your firmware is

  1. Register a callback with Cy_BLE_IAS_RegisterAttrCallback
  2. Setup the function that will be called back when the Alert Level characteristic is written.

Here is the documentation for the callback.  You can see that you need to make a function that matches the prototype of “cy_ble_callback_t”)

When you are called back you will get a void * which you can then cast into a pointer of type “cy_stc_ble_ias_char_value_t *”.  This structure will contain the value of the alert in the “cy_stc_ble_gatt_value_t *value” member.

If you look at the “cy_stc_ble_Gatt_value_t” structure you will find that it contains a pointer to an array of uint8_t (actually one one)

Now to actually write the callback function.  It just:

  1. Looks and sees if it is a write callback
  2. finds the value using the “Cy_BLE_IASS_GetCharacteristicValue” function.
  3. Then sends a message to the alertTask
/*****************************************************************************\
 * Function:    iasCallback
 * Input:       BLE IAS Service Handler Function: 
 *      - eventCode (which only can be CY_BLE_EVT_IASS_WRITE_CHAR_CMD
 *      - eventParam which is a pointer to  (and unused)
 * Returns:     void
 * Description: 
 *   This is called back by the BLE stack when there is a write to the IAS
 *   service.  This only occurs when the GATT Client Writes a new value
 *   for the alert.  The function figures out the state of the alert then
 *   sends a message to the alertTask usign the EventGroup alterState
\*****************************************************************************/
void iasCallback(uint32_t eventCode, void *eventParam)
{
    (void)eventParam;
    uint8_t alertLevel;
    
    if(eventCode == CY_BLE_EVT_IASS_WRITE_CHAR_CMD)
    {
        /* Read the updated Alert Level value from the GATT database */
        Cy_BLE_IASS_GetCharacteristicValue(CY_BLE_IAS_ALERT_LEVEL, 
            sizeof(alertLevel), &alertLevel);
        
        // The value of the characteristic could also be gotten like this:
        //switch(((cy_stc_ble_ias_char_value_t *)eventParam)->value->val[0])
        
        switch(alertLevel)
        {
            case CY_BLE_NO_ALERT:
                printf("No alert\n");
                xEventGroupSetBits(alertState,ALERT_NO_MASK);               
            break;
            case CY_BLE_MILD_ALERT:
                printf("Medium alert\n");
                xEventGroupSetBits(alertState,ALERT_MILD_MASK);               
            break;
            case CY_BLE_HIGH_ALERT:        
                printf("High alert\n");
                xEventGroupSetBits(alertState,ALERT_HIGH_MASK);               
            break;
        }   
    }   
}

Instead of calling the function to get the values, you could have also been done this:

switch(((cy_stc_ble_ias_char_value_t *)eventParam)->value->val[0])

Firmware for the Ble Event Handler

The BLE event handler is really simple,  it just prints out some debugging information depending on the event.  It also starts the advertising when the stack starts or when it has been disconnected.

/*****************************************************************************\
 * Function:    customEventHandler
 * Input:       Cy_BLE Event Handler event and eventParameter
 * Returns:     void
 * Description: 
 *   This funtion is the BLE Event Handler function.  It is called by the BLE
 *   stack when an event occurs 
\*****************************************************************************/
void customEventHandler(uint32_t event, void *eventParameter)
{
    (void)eventParameter; // not used
    switch (event)
    {
        case CY_BLE_EVT_STACK_ON:
            printf("Stack Started\r\n");
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX);
        break;

        case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
            printf("Disconnected\r\n");
            Cy_GPIO_Write(led9_PORT,led9_NUM,LED_OFF); // Turn the LED9 Off
            Cy_BLE_GAPP_StartAdvertisement(CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX);
        break;

        case CY_BLE_EVT_GATT_CONNECT_IND:
            printf("Connected\r\n");
            Cy_GPIO_Write(led9_PORT,led9_NUM,LED_ON); // Turn the LED9 On             
        break;
                
        default:
        break;
    }
}

Firmware for the bleTask

The bleTask has two functions

  1. an ISR that is called whenever an IPC event happens (so that it can unlock the semaphore to process events)
  2. a main function which starts the system, registers the callback and runs process events at the right time.
/*****************************************************************************\
 * Function:    bleInterruptNotify
 * Input:       void (it is called inside of the ISR)
 * Returns:     void
 * Description: 
 *   This is called back in the BLE ISR when an event has occured and needs to
 *   be processed.  It will then set/give the sempahore to tell the BLE task to
 *   process events.
\*****************************************************************************/
void bleInterruptNotify()
{
    BaseType_t xHigherPriorityTaskWoken;
    xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(bleSemaphore, &xHigherPriorityTaskWoken); 
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

/*****************************************************************************\
 * Function:    bleTask
 * Input:       A FreeRTOS Task - void * that is unused
 * Returns:     void
 * Description: 
 *  This task starts the BLE stack... and processes events when the bleSempahore
 *  is set by the ISR.
\*****************************************************************************/

void bleTask(void *arg)
{
    (void)arg;
    
    printf("BLE Task Started\n");

    bleSemaphore = xSemaphoreCreateCounting(UINT_MAX,0);
    
    Cy_BLE_Start(customEventHandler);
    Cy_BLE_IPC_RegisterAppHostCallback(bleInterruptNotify);
    
    while(Cy_BLE_GetState() != CY_BLE_STATE_ON) // Get the stack going
    {
        Cy_BLE_ProcessEvents();
    }
    
    Cy_BLE_IAS_RegisterAttrCallback (iasCallback);
    for(;;)
    {
        xSemaphoreTake(bleSemaphore,portMAX_DELAY);
        Cy_BLE_ProcessEvents();
    }
}

Main Firmware

Finally the main firmware just starts all of the tasks.

// Starts the system
int main(void)
{
    __enable_irq(); 
    
    UART_1_Start();
    setvbuf( stdin, NULL, _IONBF, 0 ); 
    setvbuf( stdout, NULL, _IONBF, 0 ); 
    printf("System Started\r\n");

    xTaskCreate(bleTask,"bleTask",8*1024,0,2,&bleTaskHandle);
    xTaskCreate(alertTask,"AlertTask",configMINIMAL_STACK_SIZE,0,1,0);
    
    vTaskStartScheduler();
    for(;;)
    {
    }
}

Test

After doing all of this, you can run CySmart to test the system:

 

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 */

 

FreeRTOS Command Line Interface (CLI)

Summary

In a previous article, I gnashed my teeth about my implementation of the FreeRTOS FAT SL.  Specifically, I wasn’t very happy about my command line interface.  What I did was cheap and easy, which sometimes is a good thing, but given that the FreeRTOS Command Line Interface comes as part of the FreeRTOS installation, it sure seemed like that would have been a better way to go.  Now that I have finished the FreeRTOS Command Line Interface implementation, I am not totally in love with how it works.  But, it was worth the time to figure it out. In this article In this article I will make a copy of the PSoC Real Time Clock Project, add the FreeRTOS Command Line Interface and explain how the CLI works.  In the next article I will give the project the ability to set and read the time from the RTC component in the PSoC.

While I was working on the implementation I discovered that the example code used FreeRTOS_write and FreeRTOS_read which depend on a Peripheral Driver Library (don’t confuse that with the Cypress Peripheral Driver Library) and are part of FreeRTOS+IO.  I did very a very simple implementation of those commands to make the FreeRTOS Command Line Interface work, and I will talk more about the FreeRTOS+IO in a future article.

FreeRTOS Command Line Interface

The FreeRTOS Command Line Interface is pretty straightforward to use.  Basically you:

  1. Integrate FreeRTOS_CLI.h and .c into your project
  2. Create one or more command functions and register them for use by the CLI by calling FreeRTOS_CLIRegisterCommand
  3. Read input from the terminal UART into a buffer
  4. Call the CLI (when the users presses enter) and display the output

Integrate FreeRTOS Command Line Interface C Files

I start this whole thing by copying the project “RTC-Example” from the previous article into a new project called “RTC-Example-CLI”.  Inside of the directory FreeRTOSv9.0.0/FreeRTOS-Plus/Source/FreeRTOS-Plus-CLI you will find FreeRTOS_CLI.h and FreeRTOS_CLI.c.  These two files have all of the FreeRTOS Command Line implementation.  To make this work I copied them into my project, and then did “Add –> Existing Item” to get PSoC Creator to make them part of the project.

Integrate FreeRTOS Command Line Interface C Files

Create a Command

To use the FreeRTOS Command Line Interface you need to create one or more functions which the CLI will then callback when it detects that the user has typed that command.   Each command takes three input parameters

  1. A pointer to a character buffer where the command can store text to be printed on the screen
  2. The size of that buffer
  3. A pointer to a character buffer that holds the whole string that the user typed

The function is then responsible for

  1. Doing the command
  2. Writing some output into the output buffer (up to the length of the buffer)
  3. Returning pdFALSE (if the command has completed writing the output) or pdTRUE (if it needs to do some more processing or more output)

For example if you want to create a command to clear the screen you would create a function called “clearCommand”  That command would

  1. Ignore the pcCommandString (there are no parameters)
  2. Keep track of the number of characters written into the buffer using a static int
  3. Copy the VT100 Clear String (AKA “\033[2J\033[H”) into the output buffer
  4. Null terminate the output string
  5. If you wrote less characters than the string then return pdTrue
  6. Otherwise return false
/*****************************************************************************\
 * Function:    clearCommand
 * Input:       char *pcWriteBufer,size_t xWriteBufferLen,const char *pcCommandString
 * Returns:     BaseType_t
 * Description: 
 *     This function clears the screen. It is run by the CLI interpreter
\*****************************************************************************/

BaseType_t clearCommand( char *pcWriteBuffer,size_t xWriteBufferLen, const char *pcCommandString )
{
    (void)pcCommandString;
    static int processed = 0;
    
    char *clearScreen = VT100_CLEARSCREEN;
    // Only allowed to write up top xWriteBufferLen bytes ... 
    strncpy(pcWriteBuffer,&clearScreen[processed],xWriteBufferLen-1);
    pcWriteBuffer[xWriteBufferLen-1]=0;

    processed = processed + xWriteBufferLen-1;
    if(processed < (int)strlen(clearScreen))
        return pdTRUE;
    
    processed = 0;
    return pdFALSE;
}

Once you have created the command function you then need to register it with the FreeRTOS Command Line Interface.  To do that you create a structure of type “CLI_Command_Definition_t” which has 4 members.

  1. The ascii string which the user can type to trigger the command
  2. The help message
  3. A function pointer to the command function
  4. The number of arguments that the CLI should accept
    static const CLI_Command_Definition_t clearCommandStruct =
    {
        "clear",
        "clear: Clear Screen by sending VT100 Escape Code\n",
        clearCommand,
        0
    };

Finally you need to register the command.  You should be aware that this function uses malloc.

FreeRTOS_CLIRegisterCommand( &clearCommandStruct );

Call the CLI and Display

On the FreeRTOS Command Line Interface webpage they provided a sample task to handle the command line interpreter.  I copied this task into my project and then made a few little changes.

  1. My Mac sends a ‘\r’ when you press the return key so I changed line 164 to reflect that
  2. My Mac sends 0x7F when you press the “Delete” key.  So I changed the ‘\b’ (aka backspace) to be 0x7F aka “del”
  3. I fixed the stupid ‘\r\n’ stuff

One of the great annoyances in the world is the way that line breaks in text files and terminals are handled.  It is typical in “unix” to use just a newline “\n”.  However, in “DOS” it is typical to use a carriage return/newline “\r\n”.  All over FreeRTOS it uses DOS mode.  I typically like to use unix mode… so you will find that I made that changes in the FreeRTOS CLI code.

The cliTask function does a number of things.

  1. Initializes the IO System, RTC and Command Line Interface (lines 154-163)
  2. Gets 1 character from the UART using FreeRTOS_read
  3. If the character is a carriage return (\r) then it calls the CLI until there is no more output (lines 167-196).   Remember that when you implement a command function for the CLI, if you return pdFALSE it means that you have no more output, and if you return pdTRUE then you have more output.
  4. If the character is the delete key (0x7F) then erase the character from the input buffer)
  5. Otherwise add it to the input buffer… assuming that you still have room.  (lines 227-232)
/*****************************************************************************\
 * Function:    cliTask
 * Input:       void *arg  ... unused
 * Returns:     void
 * Description: 
 *     This function is the inifite loop for the command line intepreter.. it
 *     reads characters using the FreeRTOS_read function then sends them to the
 *     cli when there is a \r 
\*****************************************************************************/
void cliTask(void *arg)
{
    (void)arg;

    char pcOutputString[ MAX_OUTPUT_LENGTH ], pcInputString[ MAX_INPUT_LENGTH ];
    int8_t cRxedChar, cInputIndex = 0;
    BaseType_t xMoreDataToFollow;    

    FreeRTOS_open( (const int8_t *)"/uart",0 );
    clearScreen();
    #define INTRO_STRING "Command Line & RTC Demo\n"
    FreeRTOS_write(0,INTRO_STRING,strlen(INTRO_STRING));

    RTC_Start();

    FreeRTOS_CLIRegisterCommand( &clearCommandStruct );	
    FreeRTOS_CLIRegisterCommand( &setTimeCommandStruct );	
    FreeRTOS_CLIRegisterCommand( &timeCommandStruct );	
    while(1)
    {
        FreeRTOS_read( 0, &cRxedChar, sizeof( cRxedChar ) );
        if( cRxedChar == '\r' )
        {
            /* A newline character was received, so the input command string is
            complete and can be processed.  Transmit a line separator, just to
            make the output easier to read. */

            FreeRTOS_write(0,&cRxedChar,1);

            /* The command interpreter is called repeatedly until it returns
            pdFALSE.  See the "Implementing a command" documentation for an
            exaplanation of why this is. */
            do
            {
                /* Send the command string to the command interpreter.  Any
                output generated by the command interpreter will be placed in the
                pcOutputString buffer. */
                
                xMoreDataToFollow = FreeRTOS_CLIProcessCommand
                              (
                                  pcInputString,   /* The command string.*/
                                  pcOutputString,  /* The output buffer. */
                                  MAX_OUTPUT_LENGTH/* The size of the output buffer. */
                              );

                /* Write the output generated by the command interpreter to the
                console. */
                    
                FreeRTOS_write( 0, pcOutputString, strlen( pcOutputString ) );

            } while( xMoreDataToFollow != pdFALSE );

            /* All the strings generated by the input command have been sent.
            Processing of the command is complete.  Clear the input string ready
            to receive the next command. */
            cInputIndex = 0;
            memset( pcInputString, 0x00, MAX_INPUT_LENGTH );
        }
        else
        {
            /* The if() clause performs the processing after a newline character
            is received.  This else clause performs the processing if any other
            character is received. */

            if( cRxedChar == 127 ) // delete character
            {
                FreeRTOS_write(0,&cRxedChar,1);
                /* Backspace was pressed.  Erase the last character in the input
                buffer - if there are any. */
                if( cInputIndex > 0 )
                {
                    cInputIndex--;
                    pcInputString[ cInputIndex ] = '
/*****************************************************************************\
* Function:    cliTask
* Input:       void *arg  ... unused
* Returns:     void
* Description: 
*     This function is the inifite loop for the command line intepreter.. it
*     reads characters using the FreeRTOS_read function then sends them to the
*     cli when there is a \r 
\*****************************************************************************/
void cliTask(void *arg)
{
(void)arg;
char pcOutputString[ MAX_OUTPUT_LENGTH ], pcInputString[ MAX_INPUT_LENGTH ];
int8_t cRxedChar, cInputIndex = 0;
BaseType_t xMoreDataToFollow;    
FreeRTOS_open( (const int8_t *)"/uart",0 );
clearScreen();
#define INTRO_STRING "Command Line & RTC Demo\n"
FreeRTOS_write(0,INTRO_STRING,strlen(INTRO_STRING));
RTC_Start();
FreeRTOS_CLIRegisterCommand( &clearCommandStruct );	
FreeRTOS_CLIRegisterCommand( &setTimeCommandStruct );	
FreeRTOS_CLIRegisterCommand( &timeCommandStruct );	
while(1)
{
FreeRTOS_read( 0, &cRxedChar, sizeof( cRxedChar ) );
if( cRxedChar == '\r' )
{
/* A newline character was received, so the input command string is
complete and can be processed.  Transmit a line separator, just to
make the output easier to read. */
FreeRTOS_write(0,&cRxedChar,1);
/* The command interpreter is called repeatedly until it returns
pdFALSE.  See the "Implementing a command" documentation for an
exaplanation of why this is. */
do
{
/* Send the command string to the command interpreter.  Any
output generated by the command interpreter will be placed in the
pcOutputString buffer. */
xMoreDataToFollow = FreeRTOS_CLIProcessCommand
(
pcInputString,   /* The command string.*/
pcOutputString,  /* The output buffer. */
MAX_OUTPUT_LENGTH/* The size of the output buffer. */
);
/* Write the output generated by the command interpreter to the
console. */
FreeRTOS_write( 0, pcOutputString, strlen( pcOutputString ) );
} while( xMoreDataToFollow != pdFALSE );
/* All the strings generated by the input command have been sent.
Processing of the command is complete.  Clear the input string ready
to receive the next command. */
cInputIndex = 0;
memset( pcInputString, 0x00, MAX_INPUT_LENGTH );
}
else
{
/* The if() clause performs the processing after a newline character
is received.  This else clause performs the processing if any other
character is received. */
if( cRxedChar == 127 ) // delete character
{
FreeRTOS_write(0,&cRxedChar,1);
/* Backspace was pressed.  Erase the last character in the input
buffer - if there are any. */
if( cInputIndex > 0 )
{
cInputIndex--;
pcInputString[ cInputIndex ] = '\0';
}
}
else
{
/* A character was entered.  It was not a new line, backspace
or carriage return, so it is accepted as part of the input and
placed into the input buffer.  When a \n is entered the complete
string will be passed to the command interpreter. */
if( cInputIndex < MAX_INPUT_LENGTH )
{
FreeRTOS_write(0,&cRxedChar,1);
pcInputString[ cInputIndex ] = cRxedChar;
cInputIndex++;
}
}
}
}
}
'; } } else { /* A character was entered. It was not a new line, backspace or carriage return, so it is accepted as part of the input and placed into the input buffer. When a \n is entered the complete string will be passed to the command interpreter. */ if( cInputIndex < MAX_INPUT_LENGTH ) { FreeRTOS_write(0,&cRxedChar,1); pcInputString[ cInputIndex ] = cRxedChar; cInputIndex++; } } } } }

As always you can find all of this code on the IoT Expert GitHub website or your can git clone git@github.com:iotexpert/PSoC-FileSystem.git

JST Connector Crimping Insanity

[Edit] Since I published this article, Tom Nardi at Hackaday wrote an article entitled “The unnecessary? Art of Connector Crimping” about my article where he pointed out that I am not the first person to come to that conclusion.  He made reference to several useful things

  1. JST is not a Connector
  2. BRADLEY GAWTHROP: WHAT YOU NEED TO KNOW ABOUT WIRING

[Edit] The Wikipedia article on JST has a nice table.

Summary

Last week I was using a CY8CKIT-062-BLE PSoC 6 development kit with a Digilent PMOD-HB5 connected to the PMOD port.  Specifically, I was using the PMOD HB5 as a solid state switch to drive a higher voltage, higher current than the GPIO on the PSoC 6 can drive.  In order to do this I needed to connect to the “6-pin JST connector for direct connection” which is on the right side of the board in the picture below.

Digilent PMOD HB5

But, what I might ask, is a JST Connector?  And, how might you make a connection to it.  Well, this where the insanity starts.  The first thing that you will discover is that “JST” stands for Japan Solderless Technology and that they make about 50,000 different types of connectors.  The next thing that you will discover is that all around the internet on the maker websites you will find people referring to connections as “JST” and acting like there is only one type of JST connector.  Then you will discover that there are tons of youtube videos that “show” you how to crimp JST connectors, and that most of them are absolute crap, particularly if you are 50 years old can barely see the freaking crimp connectors.  Finally, you will discover that there are a boatload of crimping tools that range in price from $10 (for a crap pair of pliers) to $500 (for the OEM JST Crimpers)

For this article I am going:

  1. Tour the common version of the JST connectors, where they are used.
  2. Consider not crimping
  3. Show pictures of a proper crimp
  4. Show some JST crimping tools
  5. Take you through my crimping procedure – which seems to work
  6. Some other videos/resources

Honestly the whole thing is pretty annoying.  The crimps are a bit hard to make and there is this inherent assumption everywhere that you should have just “known” how to do this.

I will also observe that the “JST” problem extends to some other crimp connectors including “Molex” and “Dupont” (which has a crazy history).  I will write about these other two types later on.

The JST Connector Series

Series Pitch Wire Connector Use
PH 2.0 24 JST PH Connector Digilent PMOD HB5
SM 2.5 28 JST SM Connector Adafruit Adafruit NEOLED
SH 1.0 28 JST SH Connector Spark Fun QWIIC 
XH 2.5 22 JST XH Connector Some batteries
ZH 1.5 26 JST ZH

Consider not Crimping

The first thing that I will say about the crimping process is that you should consider not doing it.  It is possible that purchase pre-crimped wires which will then easily slip inside of the connector housing to create almost any combination you might want.  Here is a pile of the the raw wires with crimps on one end:

JST PH Connector

And here are some that are pre-made into 6-pin connections.

JST Connector

A Good Crimp

So… you really want to make your own crimps?  OK.  Before I tell you HOW to do a good crimp, I want to show you what you are trying to do.  When you buy the crimp connectors, they will come on a metal strips which are meant to go through a machine that automatically crimps wires in China (obviously we are going to do it manually).

JST Connector

Each crimp connector has two sets of wings, which you will bend during the process.  One set holds the wire and the other holds the insulation.  Here is a zoom of some of the connectors where you can see the wings.

JST Connector

In the picture below you can see that there are two sets of wings.  The set in the middle crimps the raw wire.  The set that is near the strip is for the insulation.

JST Connector

Here is a picture of what we are trying to achieve with the crimp.  You can see that the inner crimp grabs all of the wires and the outer crimp grabs the insulation on the wire.

JST Connector

Once you have the wires crimped they will snap into a plastic housing that gangs them together.  In the picture below you can see that on the backside of the crimp connector there is a little piece that is bent up.  That will snap under the little plastic tab on the housing.  (it is a 2-pin housing)

JST Connector

In this picture you can see another view of the piece of metal that is bent up to grab the plastic.

JST Connector

Once you stick the crimp connector into the housing it will look something like this.  When you push the wire into the housing you will get a very satisfying little click (assuming you haven’t destroyed the crimp connector metal too much)

JST Connector

Crimping Tools

I bought a range of tools, but these tools from a Japanese company called “Engineer” seemed to be the best.  These tools are less “efficient” because you have to crimp twice, once for the wires and once for the insulation, but they seem to be easier not to screw up with.  The difference between PA-09 and PA-20 is the range of crimp sizes that you can do.

Engineer PA-20 & PA-09

I bought these two tools from Amazon for about $20… and they crimp both sets of wings at one time… but I have not had good luck with them.

IWISS Crimper

The actual JST crimper is really cool, but it had better be for $470.  I haven’t tried it because the Engineer PA-09 worked so well.  It can crimp both sets of wings at the same time and automatically positions the crimp connector to the exact right place.  But it also only works for one type of connector, in this case the JST-PH

JST WC-240

JST Connector Crimping Procedure

I would not say that my process is canonical, but it works.

(1) Start by stripping the end of your wire, then giving it a little twist.  I use a stripping tool called a “Knipex 12 42 195”.  The strip should be about 2-3mm

Knipex Stripper

(2) Then break off a crimp connector from the strand.  Hold it in your left hand and stick the wire wings into the 1.6mm section of the tool.  The wings should point into the crimper (look at the picture) so that when you crimp, that they bend back on themselves.  You want to make sure that the outer insulation wings are not in the crimper, meaning we are only going to crimp the inside wings on the first crimp.

JST Connector

Here is a picture where you can see that things are all lined up.

JST Connector

Dont crimp yet, but push down a little bit to hold the crimp connection in place while you use your left hand to pick up the wire.

JST Connector

(3) Insert the wire into the crimp connection.  The plastic insulation should end at the edge of the crimper (it should not stick into the connection).  In my experience, the insulation is too big to go into the connection and the side of the crimp tool keeps it from going in.

  JST Connector

(4) Then crimp it… and you will have something that looks like this.  You can see that the strands of the wire are under the newly folded wings… and the outer wings are still open.

JST Connector

(5) Use the end of the crimper to bend the out wings in just a little bit so that it can fit into the crimp tool.  Just make them so that they are parallel.

JST Connector

(6) Next put it in the crimp connection into the 1.9mm slot (from the other side) & crimp.

JST Connector

Now you should have a crimped connection

And here is a short video of me completing a JST Connector crimp

In my experience the thing that go wrong when crimping a JST Connector are

  1. I over crimp and bend the crap out of the connection
  2. I strip either too much or not enough wire
  3. I dont push the wire in far enough, which ends up with me not crimping insulation.