Lesson 9 – WICED Bluetooth: Classic Serial Port

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

Summary

In all of the previous examples I have been using Bluetooth Low Energy.  One of the great benefits of the Cypress CYW20719 is that it is a Combo Radio.  Combo means that it can use both Bluetooth Low Energy as well as Bluetooth Classic.  I am late to the Bluetooth game but as best I can tell Bluetooth Classic and Bluetooth Low Energy are exactly the same … except everything is different.  When Bluetooth was originally conceived, one of the principal functions was to act as “serial port wire cutter”.  Everywhere you looked there were devices that used a serial port wire, e.g. mice, printers, keyboards etc.

For this lesson we are going to dip back into the snip directory to get a Bluetooth Classic program to start with.  The program snip.bt.spp implements the Serial Port Profile (SPP).  The SPP is emulates a classic serial port.  It has all of the uart wires that we know and love including rx,tx,cts etc.  What this allows you to do is open up a high speed (much faster than BLE) connection.

To implement this lesson I will perform the following steps

  1. Make a new folder in the wiced_bt_class folder
  2. Copy the files from apps/snip/bt/spp into my new folder
  3. Create a make target and program it
  4. Make a connection using a Bluetooth serial port on my Mac
  5. Look at where the pin is set
  6. Examine the spp setup code
  7. Modify it to print out all the data sent to the SPP

Implement the SPP

 

Set the folder name to “spp”

Copy and past the files from the folder apps.snip.bt.spp


And paste them into your new spp folder

Create a make target for your spp project

Program the board with your project

Now tell the computer to open a classic connection by running file->open bluetooth

Select the “spp test”

Now press some keys on the new terminal window… and look at the output window of the “spp test”

Type in the pin code which is 0000.

But, how did I know the pin code?  Look at the source code.

uint8_t pincode[4] = { 0x30, 0x30, 0x30, 0x30 };

Now, when you press keys on the “Bluetooth serial terminal” you will see a not very helpful message on the CYW920719Q40EVB-01 terminal window.

I think that it would be better to print out the characters that the person types.  So lets figure out how this works.  In the application init function on line 251 there is a call to wiced_bt_spp_startup

void application_init(void)
{
    wiced_bt_gatt_status_t gatt_status;
    wiced_result_t         result;


#if SEND_DATA_ON_INTERRUPT
    /* Configure the button available on the platform */
    wiced_platform_register_button_callback( WICED_PLATFORM_BUTTON_1, app_interrupt_handler, NULL, WICED_PLATFORM_BUTTON_RISING_EDGE);

    // init timer that we will use for the rx data flow control.
    wiced_init_timer(&app_tx_timer, app_tx_ack_timeout, 0, WICED_MILLI_SECONDS_TIMER);
#endif

    app_write_eir();


    // Initialize SPP library
    wiced_bt_spp_startup(&spp_reg);

That function takes a pointer to a structure with a bunch of interesting things in it.  Notice that there is a function called “spp_rx_data_callback” that is called every time that data come in from the SPP.

wiced_bt_spp_reg_t spp_reg =
{
    SPP_RFCOMM_SCN,                     /* RFCOMM service channel number for SPP connection */
    MAX_TX_BUFFER,                      /* RFCOMM MTU for SPP connection */
    spp_connection_up_callback,         /* SPP connection established */
    NULL,                               /* SPP connection establishment failed, not used because this app never initiates connection */
    NULL,                               /* SPP service not found, not used because this app never initiates connection */
    spp_connection_down_callback,       /* SPP connection disconnected */
    spp_rx_data_callback,               /* Data packet received */
};

Now look at the function spp_rx_data_callback.  All it does is print out a message saying how much data and the hex value of the data.

wiced_bool_t spp_rx_data_callback(uint16_t handle, uint8_t* p_data, uint32_t data_len)
{
    int i;
//    wiced_bt_buffer_statistics_t buffer_stats[4];

//    wiced_bt_get_buffer_usage (buffer_stats, sizeof(buffer_stats));

//    WICED_BT_TRACE("0:%d/%d 1:%d/%d 2:%d/%d 3:%d/%d\n", buffer_stats[0].current_allocated_count, buffer_stats[0].max_allocated_count,
//                   buffer_stats[1].current_allocated_count, buffer_stats[1].max_allocated_count,
//                   buffer_stats[2].current_allocated_count, buffer_stats[2].max_allocated_count,
//                   buffer_stats[3].current_allocated_count, buffer_stats[3].max_allocated_count);

//    wiced_result_t wiced_bt_get_buffer_usage (&buffer_stats, sizeof(buffer_stats));

    WICED_BT_TRACE("%s handle:%d len:%d %02x-%02x\n", __FUNCTION__, handle, data_len, p_data[0], p_data[data_len - 1]);

#if LOOPBACK_DATA
    wiced_bt_spp_send_session_data(handle, p_data, data_len);
#endif
    return WICED_TRUE;
}

So,  How about instead of that message we just print out the data.

    //WICED_BT_TRACE("%s handle:%d len:%d %02x-%02x\n", __FUNCTION__, handle, data_len, p_data[0], p_data[data_len - 1]);

    for(int i=0;i<data_len;i++)
        WICED_BT_TRACE("%c",p_data[i]);

Now when I run the project and type I see the characters that I type coming out on the serial port.

Lesson 8 – WICED Bluetooth: The Advertising Scanner

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

Summary

In the last lesson I showed you how to build a BLE Advertising Beacon.  In that lesson I used a program called the “AdvScanner” which ran on a CYW920719Q40EVB-01 and acted like a Bluetooth Sniffer.  In this lesson I’ll show you how to build a simpler version of that program to look for the L7_Advertiser we built in the last lesson.

The important concepts in this lesson are:

  1. BLE Scanning
  2. Parsing Advertising Packets

I am going to build a project that Scans for BLE Advertisers.  Then, I’ll add the ability to print out the advertising packet.  And finally, I will add filtering capability to only look for advertisers who are using the Cypress Manufacturers code.

The steps that we will follow are:

  1. Make a new project with WICED Bluetooth Designer called L8_Scanner
  2. Turn off the GATT Database
  3. Move it into your project folder
  4. Fix the WICED_BT_TRACE to use the PUART
  5. Create a make target and build it
  6. Add a new function that prints out Advertising Packets
  7. Update the l8_scanner_app_init function to remove Advertising
  8. Update the wiced_bt_config to never stop scanning
  9. Program the development kit and see what happens
  10. Update the newAdv function to print out the raw data in the advertising packet
  11. Program again and see all of the chaos
  12. Put a filter for the Advertisers using the Cypress MFG Code
  13. Program

Implement the Project

Create a new project called L8_Scanner using the Bluetooth Designer

Turn off the GATT Database and then press Generate Code

Move the project into the wiced_bt_class folder

Update the WICED_BT_TRACE to send output to the PUART

#if ((defined WICED_BT_TRACE_ENABLE) || (defined HCI_TRACE_OVER_TRANSPORT))
    /* Set the Debug UART as WICED_ROUTE_DEBUG_NONE to get rid of prints */
    //  wiced_set_debug_uart( WICED_ROUTE_DEBUG_NONE );

    /* Set Debug UART as WICED_ROUTE_DEBUG_TO_PUART to see debug traces on Peripheral UART (PUART) */
      wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_PUART );

    /* Set the Debug UART as WICED_ROUTE_DEBUG_TO_WICED_UART to send debug strings over the WICED debug interface */
    //wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_WICED_UART );
#endif

Modify the make target & program that was created by the BT Designer

Make a new function that will be called when WICED finds a new advertising packet.

void newAdv(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{
    WICED_BT_TRACE("Found device %B\n",p_scan_result->remote_bd_addr);

}

Remove the start advertising from l8_scanner_app_init

/*
 * This function is executed in the BTM_ENABLED_EVT management callback.
 */
void l8_scanner_app_init(void)
{
    /* Initialize Application */
    wiced_bt_app_init();

    /* Allow peer to pair */
    wiced_bt_set_pairable_mode(WICED_TRUE, 0);

    /* Set Advertisement Data */
    //l8_scanner_set_advertisement_data();

    /* Start Undirected LE Advertisements on device startup.
     * The corresponding parameters are contained in 'wiced_bt_cfg.c' */
    /* TODO: Make sure that this is the desired behavior. */
    //wiced_bt_start_advertisements(BTM_BLE_ADVERT_UNDIRECTED_HIGH, 0, NULL);
    wiced_bt_ble_scan(BTM_BLE_SCAN_TYPE_HIGH_DUTY,FALSE,newAdv);
}

Update wiced_bt_config.c to never stop scanning.

        .high_duty_scan_duration =          0,                                                          /**< High Duty Scan Duration in seconds (0 for infinite) */

Program your development kit and see what happens.

Now lets update the program to print the advertising packets.

void newAdv(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{

    uint8_t mfgLen;
    uint8_t* mfgData = wiced_bt_ble_check_advertising_data( p_adv_data,0xFF,&mfgLen);


    WICED_BT_TRACE("Found device %B ",p_scan_result->remote_bd_addr);

    uint8_t index=0;
    int fieldLength=p_adv_data[index];
    do {
        for(int i=0;i<=fieldLength;i++)
            WICED_BT_TRACE("%02X ",p_adv_data[index+i]);

        index = index + fieldLength + 1;
        fieldLength = p_adv_data[index];

    } while(fieldLength);

    WICED_BT_TRACE("\n");
}

Now program the development kit and see what happens.  Where I am sitting this is not very helpful because there are boatloads of advertisers.

Now let’s make one more change.  Instead of printing all of the packets let’s only look only at the ones that have Manufacturer data, the right length and the Cypress manufacturer id.

    uint8_t mfgLen;
    uint8_t* mfgData = wiced_bt_ble_check_advertising_data( p_adv_data,0xFF,&mfgLen);

    if(!(mfgData && mfgLen == 3 && mfgData[0] == 0x31 && mfgData[1]  == 0x01 ))
        return;

Now I only see my L7_Advertising project

Lesson 7 – WICED Bluetooth: Bluetooth Advertising

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

Everywhere you go there are bunches of Bluetooth devices that are acting as beacons.  Apple has a standard called iBeacon.  Google has a standard called Eddystone.  Some companies use those standards, and some companies make proprietary beacons.  In this lesson we will build a beacon.

The important concepts in this lesson are:

  1. Advertising packet formats
  2. wiced_bt_cfg.c

The steps I will follow are:

  1. Run BT Designer
  2. Setup the device as “no gatt database”
  3. Move the project into the wiced_bt_class folder
  4. Edit the make target
  5. Fix the WICED_BT_TRACE to go to the PUART
  6. Run it
  7. Edit the wiced_bt_cfg.c to never timeout
  8. Setup no random address changing
  9. Add the manufacturing data uint8_t array and include the Cypress company code
  10. Change the start advertising call to BTM_BLE_ADVERT_NONCONN_HIGH, BLE_ADDR_PUBLIC
  11. Update the length of the advertising packet
  12. Update the set advertising packet to have the manufacturing data
  13. Add a button interrupt function
  14. Register the button interrupt

BLE Concepts

The Advertising Packet is a string of 3-31 bytes that is broadcast at a configurable interval. The packet is broken up into variable length fields. Each field has the form:

  • Length in bytes (not including the Length byte)
  • Type
  • Optional Data

The minimum packet requires the <<Flags>> field which is a set of flags that defines how the device behaves (e.g. is it connectable?). Here is a list of the other field Types that you can add:

/** Advertisement data types */
enum wiced_bt_ble_advert_type_e {
    BTM_BLE_ADVERT_TYPE_FLAG                        = 0x01,                 /**< Advertisement flags */
    BTM_BLE_ADVERT_TYPE_16SRV_PARTIAL               = 0x02,                 /**< List of supported services - 16 bit UUIDs (partial) */
    BTM_BLE_ADVERT_TYPE_16SRV_COMPLETE              = 0x03,                 /**< List of supported services - 16 bit UUIDs (complete) */
    BTM_BLE_ADVERT_TYPE_32SRV_PARTIAL               = 0x04,                 /**< List of supported services - 32 bit UUIDs (partial) */
    BTM_BLE_ADVERT_TYPE_32SRV_COMPLETE              = 0x05,                 /**< List of supported services - 32 bit UUIDs (complete) */
    BTM_BLE_ADVERT_TYPE_128SRV_PARTIAL              = 0x06,                 /**< List of supported services - 128 bit UUIDs (partial) */
    BTM_BLE_ADVERT_TYPE_128SRV_COMPLETE             = 0x07,                 /**< List of supported services - 128 bit UUIDs (complete) */
    BTM_BLE_ADVERT_TYPE_NAME_SHORT                  = 0x08,                 /**< Short name */
    BTM_BLE_ADVERT_TYPE_NAME_COMPLETE               = 0x09,                 /**< Complete name */
    BTM_BLE_ADVERT_TYPE_TX_POWER                    = 0x0A,                 /**< TX Power level  */
    BTM_BLE_ADVERT_TYPE_DEV_CLASS                   = 0x0D,                 /**< Device Class */
    BTM_BLE_ADVERT_TYPE_SIMPLE_PAIRING_HASH_C       = 0x0E,                 /**< Simple Pairing Hash C */
    BTM_BLE_ADVERT_TYPE_SIMPLE_PAIRING_RAND_C       = 0x0F,                 /**< Simple Pairing Randomizer R */
    BTM_BLE_ADVERT_TYPE_SM_TK                       = 0x10,                 /**< Security manager TK value */
    BTM_BLE_ADVERT_TYPE_SM_OOB_FLAG                 = 0x11,                 /**< Security manager Out-of-Band data */
    BTM_BLE_ADVERT_TYPE_INTERVAL_RANGE              = 0x12,                 /**< Slave connection interval range */
    BTM_BLE_ADVERT_TYPE_SOLICITATION_SRV_UUID       = 0x14,                 /**< List of solicitated services - 16 bit UUIDs */
    BTM_BLE_ADVERT_TYPE_128SOLICITATION_SRV_UUID    = 0x15,                 /**< List of solicitated services - 128 bit UUIDs */
    BTM_BLE_ADVERT_TYPE_SERVICE_DATA                = 0x16,                 /**< Service data - 16 bit UUID */
    BTM_BLE_ADVERT_TYPE_PUBLIC_TARGET               = 0x17,                 /**< Public target address */
    BTM_BLE_ADVERT_TYPE_RANDOM_TARGET               = 0x18,                 /**< Random target address */
    BTM_BLE_ADVERT_TYPE_APPEARANCE                  = 0x19,                 /**< Appearance */
    BTM_BLE_ADVERT_TYPE_ADVERT_INTERVAL             = 0x1a,                 /**< Advertising interval */
    BTM_BLE_ADVERT_TYPE_LE_BD_ADDR                  = 0x1b,                 /**< LE device bluetooth address */
    BTM_BLE_ADVERT_TYPE_LE_ROLE                     = 0x1c,                 /**< LE role */
    BTM_BLE_ADVERT_TYPE_256SIMPLE_PAIRING_HASH      = 0x1d,                 /**< Simple Pairing Hash C-256 */
    BTM_BLE_ADVERT_TYPE_256SIMPLE_PAIRING_RAND      = 0x1e,                 /**< Simple Pairing Randomizer R-256 */
    BTM_BLE_ADVERT_TYPE_32SOLICITATION_SRV_UUID     = 0x1f,                 /**< List of solicitated services - 32 bit UUIDs */
    BTM_BLE_ADVERT_TYPE_32SERVICE_DATA              = 0x20,                 /**< Service data - 32 bit UUID */
    BTM_BLE_ADVERT_TYPE_128SERVICE_DATA             = 0x21,                 /**< Service data - 128 bit UUID */
    BTM_BLE_ADVERT_TYPE_CONN_CONFIRM_VAL            = 0x22,                 /**< LE Secure Connections Confirmation Value */
    BTM_BLE_ADVERT_TYPE_CONN_RAND_VAL               = 0x23,                 /**< LE Secure Connections Random Value */
    BTM_BLE_ADVERT_TYPE_URI                         = 0x24,                 /**< URI */
    BTM_BLE_ADVERT_TYPE_INDOOR_POS                  = 0x25,                 /**< Indoor Positioning */
    BTM_BLE_ADVERT_TYPE_TRANS_DISCOVER_DATA         = 0x26,                 /**< Transport Discovery Data */
    BTM_BLE_ADVERT_TYPE_SUPPORTED_FEATURES          = 0x27,                 /**< LE Supported Features */
    BTM_BLE_ADVERT_TYPE_UPDATE_CH_MAP_IND           = 0x28,                 /**< Channel Map Update Indication */
    BTM_BLE_ADVERT_TYPE_PB_ADV                      = 0x29,                 /**< PB-ADV */
    BTM_BLE_ADVERT_TYPE_MESH_MSG                    = 0x2A,                 /**< Mesh Message */
    BTM_BLE_ADVERT_TYPE_MESH_BEACON                 = 0x2B,                 /**< Mesh Beacon */
    BTM_BLE_ADVERT_TYPE_3D_INFO_DATA                = 0x3D,                 /**< 3D Information Data */
    BTM_BLE_ADVERT_TYPE_MANUFACTURER                = 0xFF                  /**< Manufacturer data */
};

Here is an example of the advertising packet that we are going to generate

Implement the Project

Run BT Designer and create a new project called “L7_Advertising”

Turn off the GATT Database

Move the project into the wiced_bt_class folder

Edit the make target

Setup the the WICED_BT_TRACE to use the PUART

#if ((defined WICED_BT_TRACE_ENABLE) || (defined HCI_TRACE_OVER_TRANSPORT))
    /* Set the Debug UART as WICED_ROUTE_DEBUG_NONE to get rid of prints */
    //  wiced_set_debug_uart( WICED_ROUTE_DEBUG_NONE );

    /* Set Debug UART as WICED_ROUTE_DEBUG_TO_PUART to see debug traces on Peripheral UART (PUART) */
      wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_PUART );

    /* Set the Debug UART as WICED_ROUTE_DEBUG_TO_WICED_UART to send debug strings over the WICED debug interface */
    //wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_WICED_UART );
#endif

Run it

Now that we know it is working, Ill edit the wiced_bt_cfg.c to never timeout

        .high_duty_nonconn_duration =       0,                                                         /**< High Duty Non-Connectable Advertising Duration in seconds (0 for infinite) */

Setup no random address changing

    .rpa_refresh_timeout =                  WICED_BT_CFG_DEFAULT_RANDOM_ADDRESS_NEVER_CHANGE,         /**< Interval of random address refreshing - secs */

Now edit the L7_Advertising.c to add the manufacturing data uint8_t array

uint8_t manuf_data[] = {0x31,0x01,0x00};

Switch to non-connectable advertising

    wiced_bt_start_advertisements(BTM_BLE_ADVERT_NONCONN_HIGH, BLE_ADDR_PUBLIC, NULL);

Update the l7_advertising_set_advertisement_data function to have three elements in the advertising packet

    wiced_bt_ble_advert_elem_t adv_elem[3] = { 0 };

Add the Manufacturer information to the advertising packet

    /* Advertisement Element for Manufacturer Data */
    adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_MANUFACTURER;
    adv_elem[num_elem].len = sizeof(manuf_data);
    adv_elem[num_elem].p_data = manuf_data;
    num_elem++;

Add a button interrupt function

void buttonISR(void *data, uint8_t port_pin )
{

    manuf_data[2] += 1;
    l7_advertising_set_advertisement_data();
    WICED_BT_TRACE("Manufacturer Data = %d\n",manuf_data[2]);
}

Register the button interrupt

 wiced_hal_gpio_register_pin_for_interrupt( WICED_GPIO_PIN_BUTTON_1, buttonISR, NULL );
    wiced_hal_gpio_configure_pin( WICED_GPIO_PIN_BUTTON_1, ( GPIO_INPUT_ENABLE | GPIO_PULL_UP | GPIO_EN_INT_FALLING_EDGE ), GPIO_PIN_OUTPUT_HIGH );

Test using the AdvScanner

I have given you a project called the “AdvScanner”.  You can run it by creating a make target.

When I run the L7_Advertising project and press the buttons a few times my terminal will look like this

And when I look at the output of the scanner program you can see the advertising packet for the this project.  Notice that the last three bytes are 31 01 03.  The 03 is the count of button presses.

Lesson 6 – WICED Bluetooth: The Peripheral Comes Alive

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

Summary

In the last lesson we built our first Bluetooth design using BT Designer.  In that lesson I showed you how to

  1. Build a project using BT Designer
  2. Start Advertising
  3. Get connected Central –> Peripheral
  4. Read & Write the data from the Central to the  Peripheral

In this lesson we are going to answer the question how does the Peripheral write data back to the Central by adding a button to our last project.  When the button is pressed it will send the state of the button (0 or 1) back to Central.

The important concepts in this lesson are

  1. BLE Notifications
  2. BLE Client Configuration Characteristic Descriptor
  3. How to manually modify the Gatt DB

I will follow these steps:

  1. Copy the project L5_BluetoothLED into L6_BluetoothLEDButton
  2. Rename all of the files to be L6_BluetoothLEDButton….
  3. Fix the makefile
  4. Create a make target and make sure that it still works
  5. Modify L6_BluetoothLEDButton_db.h to add UUID and Handles for the new Button characteristic
  6. Modify L6_BluetoothLEDButton_db.c to add the Button characteristic to the GATT Database
  7. Add initial value arrays for the Button characteristic, CCCD and User Description
  8. Add the Button values to the GATT lookup table
  9. Add connection id uint16_t connection_id
  10. Modify the connection handler l5_bluetoothled_connect_callback
  11. Create a button callback function
  12. Register the button callback

BLE Concepts

A Bluetooth Peripheral is allowed to send Notifications to a Central that a value in the GATT Database has changed.  However, it is only allowed to do this when the Client Characteristic Configuration Descriptor (CCCD) is set.  In other words a Central can register with a Peripheral that it is interested in seeing changes of Characteristics by writing a 0x01 into the CCCD.  The CCCD is just another value in the attribute database.

To setup a Characteristic for Notifications you need to modify the GATT Database by

  1. Adding the notification property to the Characteristic in the GATT Database
  2. Adding the CCCD to the GATT database

In the your program, when a value is changed, you should check to see if the CCCD is set, then send a notification if it is set.

Implement the Project

I am going to build this project on top of the code from L5_BluetoothLED.  So, start this project by copying the project L5_BluetoothLED into L6_BluetoothLEDButton by doing copy/paste

Rename all of the files to be L6_BluetoothLEDButton…. your project should look like this.

Fix the makefile.mk (because the file names have changed)

#
# This file has been automatically generated by the WICED 20719-B1 Designer.
#

APP_SRC = L6_BluetoothLEDButton.c
APP_SRC += L6_BluetoothLEDButton_db.c
APP_SRC += wiced_bt_cfg.c

C_FLAGS += -DWICED_BT_TRACE_ENABLE

# If defined, HCI traces are sent over transport/WICED HCI interface
C_FLAGS += -DHCI_TRACE_OVER_TRANSPORT

Create a make target and make sure that it still works

Modify L6_BluetoothLEDButton_db.h to create a UUID for the new Button characteristic

#define __UUID_L5SERVICE_BUTTON               0x2A, 0xbf, 0x86, 0xa6, 0xc8, 0x6c, 0x4e, 0xa5, 0xaa, 0x56, 0xbd, 0xac, 0x72, 0x80, 0x93, 0xa9

Modify L6_BluetoothLEDButton_db.h to add Handles for the  new Button characteristic

#define HDLC_L5SERVICE_BUTTON                      0x0030
#define HDLC_L5SERVICE_BUTTON_VALUE                0x0031
#define HDLD_L5SERVICE_BUTTON_USER_DESCRIPTION     0x0032
#define HDLD_L5SERVICE_BUTTON_CLIENT_CONFIGURATION 0x0033

Modify L6_BluetoothLEDButton_db.c to add the Button characteristic to the GATT Database

               /* Characteristic 'BUTTON' */

                CHARACTERISTIC_UUID128(HDLC_L5SERVICE_BUTTON, HDLC_L5SERVICE_BUTTON_VALUE,
                        __UUID_L5SERVICE_BUTTON, LEGATTDB_CHAR_PROP_READ | LEGATTDB_CHAR_PROP_NOTIFY,
                    LEGATTDB_PERM_READABLE ),

                    /* Descriptor 'Characteristic User Description' */
                    CHAR_DESCRIPTOR_UUID16 (HDLD_L5SERVICE_BUTTON_USER_DESCRIPTION,
                        UUID_DESCRIPTOR_CHARACTERISTIC_USER_DESCRIPTION, LEGATTDB_PERM_READABLE),


                /* Descriptor CCCD */
                CHAR_DESCRIPTOR_UUID16_WRITABLE(HDLD_L5SERVICE_BUTTON_CLIENT_CONFIGURATION,
                        UUID_DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION,
                        LEGATTDB_PERM_READABLE | LEGATTDB_PERM_WRITE_REQ ),

Add initial value arrays for the Button characteristic, CCCD and User Description into L6_ButtonLED.c

uint8_t l5_bluetoothled_l5service_button[]                  = {0x01};
uint8_t l5_bluetoothled_l5service_button_user_description[] = "Button Value";
uint8_t l5_bluetoothled_l5service_button_cccd[]      = {0x00,0x00};

Add the Button values to the GATT lookup table

    {HDLC_L5SERVICE_BUTTON_VALUE,              1,                                                      1,                                                      l5_bluetoothled_l5service_button},
    {HDLD_L5SERVICE_BUTTON_USER_DESCRIPTION,   sizeof(l5_bluetoothled_l5service_button_user_description)-1, sizeof(l5_bluetoothled_l5service_button_user_description)-1, l5_bluetoothled_l5service_button_user_description},
    {HDLD_L5SERVICE_BUTTON_CLIENT_CONFIGURATION, 2,      2,      l5_bluetoothled_l5service_button_cccd},

Add connection id uint16_t connection_id

uint16_t connection_id=0;

Modify the connection handler l5_bluetoothled_connect_callback.  When you get a connection save it.

            connection_id = p_conn_status->conn_id;

and when you get a disconnect put it back to 0

          connection_id = 0;

Here is what the whole handler looks like now

/* GATT Connection Status Callback */
wiced_bt_gatt_status_t l5_bluetoothled_connect_callback( wiced_bt_gatt_connection_status_t *p_conn_status )
{
    wiced_bt_gatt_status_t status = WICED_BT_GATT_ERROR;

    if ( NULL != p_conn_status )
    {
        if ( p_conn_status->connected )
        {
            // Device has connected
            WICED_BT_TRACE("Connected : BDA '%B', Connection ID '%d'\n", p_conn_status->bd_addr, p_conn_status->conn_id );

            /* TODO: Handle the connection */
            connection_id = p_conn_status->conn_id;

        }
        else
        {
            // Device has disconnected
            WICED_BT_TRACE("Disconnected : BDA '%B', Connection ID '%d', Reason '%d'\n", p_conn_status->bd_addr, p_conn_status->conn_id, p_conn_status->reason );

            /* TODO: Handle the disconnection */
            connection_id = 0;

            /* restart the advertisements */
            wiced_bt_start_advertisements(BTM_BLE_ADVERT_UNDIRECTED_HIGH, 0, NULL);
        }
        status = WICED_BT_GATT_SUCCESS;
    }

    return status;
}

Create a button callback function

void buttonISR(void *data, uint8_t port_pin )
{

    l5_bluetoothled_l5service_button[0] = wiced_hal_gpio_get_pin_input_status(WICED_PLATFORM_BUTTON_1);

    if(l5_bluetoothled_l5service_button_cccd[0] & GATT_CLIENT_CONFIG_NOTIFICATION)
              {
                  wiced_bt_gatt_send_notification(connection_id, HDLC_L5SERVICE_BUTTON_VALUE, sizeof(l5_bluetoothled_l5service_button), l5_bluetoothled_l5service_button );

                  WICED_BT_TRACE( "Sent Button %d\n",l5_bluetoothled_l5service_button[0]);
   }
}

In the function l5_bluetoothled_app_init you need to register the button callback to trigger when the button is pressed

    wiced_hal_gpio_register_pin_for_interrupt( WICED_GPIO_PIN_BUTTON_1, buttonISR, NULL );
    wiced_hal_gpio_configure_pin( WICED_GPIO_PIN_BUTTON_1, ( GPIO_INPUT_ENABLE | GPIO_PULL_UP | GPIO_EN_INT_BOTH_EDGE ), GPIO_PIN_OUTPUT_HIGH );

Test using CySmart

 

Click on the “Unknown Service”

Click on the second characteristic.  (we know that is the one because it is Read and Notify)

Now you can press “Read”

 

After clicking the read button and you can see that the value is 0x01 (because you are not pressing the button).  If you were pressing it you would see 0x00

Now press Notify and you should see the value change each time you press the button

 

Lesson 5 – WICED Bluetooth: Bluetooth Designer – Turn up the Radio!

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

Summary

In this lesson we are going to build the simplest project that I could think of… turning an LED on/off with Bluetooth Low Energy.

The important BLE concepts are

  1. What is a Central / Peripheral
  2. What is Advertising
  3. What is a GATT Database

The important WICED Bluetooth Concepts are:

  1. How do you run WICED Bluetooth Designer
  2. What is the structure of a WICED Bluetooth Project
  3. What is a Callback
  4. How is the GATT Database Implemented
  5. How to run CySmart

The steps that we will follow are:

  1. Run BT Designer
  2. Create a project called L5_BluetoothLED
  3. Go to the characteristics page
  4. Add a vendor specific service
  5. Name the Service L5Service
  6. Add an optional characteristic that is vendor specific
  7. Name it RED
  8. Make it 1 byte with an initial value of 01
  9. Set it up for host write
  10. Add a user description to the characteristic
  11. Generate the code
  12. Move the folder to the wiced_bt_class folder
  13. Fix the three include problems
  14. Reset the debug UART to PUART
  15. When there is a write, change the value of the WICED_LED_1 GPIO
  16. Test

BLE Concepts

In the world of BLE there are two sides of every connection

  • The Central – typically a cellphone
  • The Peripheral – your WICED device

Centrals listen for Peripherals that are Advertising.  Advertising is a periodic packet of up to 31 bytes of information that a Peripheral will send out to make it presence known.  When a Central hears the Advertising packets of a Peripheral that is “interesting” it can initiate a connection.

Once a connection is made, how do you exchange information?  The answer is that a Peripheral has a database running inside of it.  The database is called a “GATT database”.   A Central can perform “Service Discovery” to find all of the legal stuff in the database.  The GATT database is organized into one or more “Services” that have one or more “Characteristics”.  For instance a Heart Rate Monitor might have a “Heart Rate Service” with two characteristics, one for heart rate and one for battery level.

There are two types of Services.  Ones that are specified by the Bluetooth SIG, like heartrate.  And vendor specific custom services.

Run Bluetooth Designer

The Bluetooth Designer is a GUI tool that we built into Eclipse.  It allows you to configure some of the fundamental Bluetooth feature (like the GATT Database) and then automatically generate the code.  Start Bluetooth Designer by running File->New->WICED Bluetooth Designer.

Since this is Lesson 5 and we are going to write and LED… call the project “L5_BluetoothLED”

Once you start BT Designer, you screen should look like this.  The project is going to be a BLE only project.

The Characteristics button lets you setup the GATT database.

Add a service by selecting vendor specific service and then hitting the “+”

I’ll call the service “L5Service”

Next add a characteristic by selecting “vendor specific characteristic” and pressing “+”

Change the name to “RED”, Make the device role “Host write to or reads from service”.  Make the size 1 byte and set the initial value to 01 (it must be 01 not 1 or 001)

When we are looking at this remotely you would like to be able to see the user description.  So click that tab and give it a description.

Press Generate Code button.  You will end up with a folder in the top level apps directory.  I don’t like this, so lets move it into our class projects folder.  You can do this by dragging the folder to the wiced_bt_class folder.  Now it should look like this:

Unfortunately, there are three little issues that this creates which need to be fixed.  First, you need to fix L5_BluetoothLED.c as this include is wrong.

#include "../wiced_bt_class/L5_BluetoothLED/L5_BluetoothLED_db.h"

And change it to:

#include "L5_BluetoothLED_db.h"

Next edit L5_BluetoothLED_db.h and add the #include “wiced.h”

#include "wiced.h"

Finally edit the L5_BluetoothLED_db.c to fix the same include problem.

#include "../wiced_bt_class/L5_BluetoothLED/L5_BluetoothLED_db.h"

It should be like this.

#include "L5_BluetoothLED_db.h"

Now edit the make target that was created by the BT Designer and change it to:

Remember in the earlier lesson I showed you about the WICED HCI UART and the WICED PUART.  Well by default the WICED_BT_TRACE is setup to go to the HCI UART.  So, lets fix the output of BT_TRACE to go to the PUART by changing the file “L5_BluetoothLED.c”

#if ((defined WICED_BT_TRACE_ENABLE) || (defined HCI_TRACE_OVER_TRANSPORT))
    /* Set the Debug UART as WICED_ROUTE_DEBUG_NONE to get rid of prints */
    //  wiced_set_debug_uart( WICED_ROUTE_DEBUG_NONE );

    /* Set Debug UART as WICED_ROUTE_DEBUG_TO_PUART to see debug traces on Peripheral UART (PUART) */
      wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_PUART );

    /* Set the Debug UART as WICED_ROUTE_DEBUG_TO_WICED_UART to send debug strings over the WICED debug interface */
    //wiced_set_debug_uart( WICED_ROUTE_DEBUG_TO_WICED_UART );
#endif

The last thing that we want to do is fix it so that when the Central writes a new value into the RED LED characteristic we should write the GPIO to the new value.  In L5_BluetoothLED.c make this change.

     case HDLC_L5SERVICE_RED_VALUE:
                       WICED_BT_TRACE("LED = %d\n",l5_bluetoothled_l5service_red[0]);
                       wiced_hal_gpio_set_pin_output(WICED_GPIO_PIN_LED_2,l5_bluetoothled_l5service_red[0]);
                       break;

Now build the project and see what happens.  The first testing step will be to open CySmart.  You can see that a device called “L5_BluetoothLED” is advertising.

When I click it, you can see that there is a GattDB.

When I click on the database, I can see that there is only one service (which makes sense as we setup only one)

Click on the Service and you can see that there is only one characteristic in the service… and its value is 01.

When you click the descriptor button you can see that there is a Characteristic User Description

 

And finally the value is “Red LED Value”.  That is what we setup.

When you click back … then click on the write it will bring up this window where I can send a new value.

Now the value is 0x00 and the RED LED is on (remember from earlier that it is active low so that makes sense)

 

And when I look at the terminal I can see two writes (I wrote again before I took this screen shot)

A Tour of the Source Code

The GATT Database is in the file L5_BluetoothLED_db.c

const uint8_t gatt_database[] = // Define GATT database
{
    /* Primary Service 'Generic Attribute' */
    PRIMARY_SERVICE_UUID16 (HDLS_GENERIC_ATTRIBUTE, UUID_SERVICE_GATT),

    /* Primary Service 'Generic Access' */
    PRIMARY_SERVICE_UUID16 (HDLS_GENERIC_ACCESS, UUID_SERVICE_GAP),

        /* Characteristic 'Device Name' */
        CHARACTERISTIC_UUID16 (HDLC_GENERIC_ACCESS_DEVICE_NAME, HDLC_GENERIC_ACCESS_DEVICE_NAME_VALUE,
            UUID_CHARACTERISTIC_DEVICE_NAME, LEGATTDB_CHAR_PROP_READ,
            LEGATTDB_PERM_READABLE),

        /* Characteristic 'Appearance' */
        CHARACTERISTIC_UUID16 (HDLC_GENERIC_ACCESS_APPEARANCE, HDLC_GENERIC_ACCESS_APPEARANCE_VALUE,
            UUID_CHARACTERISTIC_APPEARANCE, LEGATTDB_CHAR_PROP_READ,
            LEGATTDB_PERM_READABLE),

    /* Primary Service 'L5Service' */
    PRIMARY_SERVICE_UUID128 (HDLS_L5SERVICE, __UUID_L5SERVICE),

        /* Characteristic 'RED' */
        CHARACTERISTIC_UUID128_WRITABLE (HDLC_L5SERVICE_RED, HDLC_L5SERVICE_RED_VALUE,
            __UUID_L5SERVICE_RED, LEGATTDB_CHAR_PROP_READ | LEGATTDB_CHAR_PROP_WRITE,
            LEGATTDB_PERM_READABLE | LEGATTDB_PERM_WRITE_REQ),

            /* Descriptor 'Characteristic User Description' */
            CHAR_DESCRIPTOR_UUID16 (HDLD_L5SERVICE_RED_USER_DESCRIPTION,
                UUID_DESCRIPTOR_CHARACTERISTIC_USER_DESCRIPTION, LEGATTDB_PERM_READABLE),

};

Each row in the Database table has a unique “Handle” that is defined in the L5_BluetoothLED_db.h

#define __UUID_L5SERVICE                      0x30, 0x9d, 0x7f, 0x29, 0x73, 0xca, 0x4f, 0xfd, 0xa5, 0x68, 0x17, 0xd8, 0x90, 0x67, 0x7f, 0x35
#define __UUID_L5SERVICE_RED                  0x29, 0xbf, 0x86, 0xa6, 0xc8, 0x6c, 0x4e, 0xa5, 0xaa, 0x56, 0xbd, 0xac, 0x72, 0x80, 0x93, 0xa9

// ***** Primary Service 'Generic Attribute'
#define HDLS_GENERIC_ATTRIBUTE                0x0001

// ***** Primary Service 'Generic Access'
#define HDLS_GENERIC_ACCESS                   0x0014
// ----- Characteristic 'Device Name'
#define HDLC_GENERIC_ACCESS_DEVICE_NAME       0x0015
#define HDLC_GENERIC_ACCESS_DEVICE_NAME_VALUE 0x0016
// ----- Characteristic 'Appearance'
#define HDLC_GENERIC_ACCESS_APPEARANCE        0x0017
#define HDLC_GENERIC_ACCESS_APPEARANCE_VALUE  0x0018

// ***** Primary Service 'L5Service'
#define HDLS_L5SERVICE                        0x0028
// ----- Characteristic 'RED'
#define HDLC_L5SERVICE_RED                    0x0029
#define HDLC_L5SERVICE_RED_VALUE              0x002A
// ===== Descriptor 'User Description'
#define HDLD_L5SERVICE_RED_USER_DESCRIPTION   0x002B

Each characteristic value is held in one of the uint8_t arrays found in “L5_BluetoothLED.c”

/*******************************************************************
 * GATT Initial Value Arrays
 ******************************************************************/
uint8_t l5_bluetoothled_generic_access_device_name[]     = {'L','5','_','B','l','u','e','t','o','o','t','h','L','E','D'};
uint8_t l5_bluetoothled_generic_access_appearance[]      = {0x00,0x00};
uint8_t l5_bluetoothled_l5service_red[]                  = {0x01};
uint8_t l5_bluetoothled_l5service_red_user_description[] = {'R','E','D',' ','L','e','d',' ','V','a','l','u','e'};

/*******************************************************************
 * GATT Lookup Table
 ******************************************************************/

/* GATT attribute lookup table                                */
/* (attributes externally referenced by GATT server database) */
gatt_db_lookup_table l5_bluetoothled_gatt_db_ext_attr_tbl[] =
{
    /* { attribute handle,                  maxlen,                                                 curlen,                                                 attribute data } */
    {HDLC_GENERIC_ACCESS_DEVICE_NAME_VALUE, 15,                                                     15,                                                     l5_bluetoothled_generic_access_device_name},
    {HDLC_GENERIC_ACCESS_APPEARANCE_VALUE,  2,                                                      2,                                                      l5_bluetoothled_generic_access_appearance},
    {HDLC_L5SERVICE_RED_VALUE,              1,                                                      1,                                                      l5_bluetoothled_l5service_red},
    {HDLD_L5SERVICE_RED_USER_DESCRIPTION,   sizeof(l5_bluetoothled_l5service_red_user_description), sizeof(l5_bluetoothled_l5service_red_user_description), l5_bluetoothled_l5service_red_user_description},
};

 

 

Lesson 4 – WICED Bluetooth: Using Snips

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

Summary

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

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

To modify the hal_i2c_master snip I will

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

Run hal_gpio

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

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

At the beginning it sets up a timer

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

The timer calls this function each time the timer expires.

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

    wiced_seconds++;

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

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

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

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

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

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

Run hal_i2c_master

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

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

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

Then make the make target.

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

Modify the hal_i2c_master.c Create a Better Project

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

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

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

Then select the L4_Accelerometer folder and pick paste.

Create a make target for the L4_Accelerometer

Build it to make sure it still works.

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

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

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

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

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

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

Here is the whole function initialize_app together

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

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


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

    current_speed = wiced_hal_i2c_get_speed();

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

    /*Start a timer for POLL_TIMER seconds*/

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

}

Next I need to modify the comboread_cb callback.  It will

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

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

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

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

    if(I2CM_SUCCESS == status) {

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

}

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

Lesson 3 – WICED Bluetooth: The Super Mux Tool

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

Summary

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

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

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

The steps we are going to follow are

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

Copy L2_HelloWorld –> L3_SuperMux

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

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

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

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

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

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

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

C_FLAGS += -DWICED_BT_TRACE_ENABLE

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

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

Fix it to be “L3_SuperMux” like this:

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

Platform Files

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

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

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

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

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

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

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

SuperMux Tool

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

Configure the Clock and the PWM

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

First add includes for the ACLK and PWM driver.

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

Then startup the Clock, Pin and PWM.

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

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

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

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

Documentation

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

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

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

Summary

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

Kernighan & Ritchie

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

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

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

To make this first project the steps are:

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

Lets do this!

DO NOT DO File->New Project

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

Hello World & Blinking LED

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

Give it the name “wiced_bt_class”

Create a folder to hold the first project called L2_HelloWorld

Call the folder L2_HelloWorld

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

Give it the name L2_HelloWorld.c

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

and giving it the name makefile.mk

Add some code to the L2_HelloWorld.c

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

APPLICATION_START()
{
    wiced_set_debug_uart(WICED_ROUTE_DEBUG_TO_PUART);

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

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

Add the secret incantation to the makefile.mk

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

C_FLAGS += -DWICED_BT_TRACE_ENABLE

Create a make target

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

directory.directory.appname-platform download

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

Connect the Development Kit To Your Computer

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

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

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

    wiced_set_debug_uart(WICED_ROUTE_DEBUG_TO_PUART);

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

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

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

When I run Serial and then to open a Port

You can see the two UARTs.

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

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

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

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

Program your Development Kit

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

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

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

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

And you should also see the blinking LED!!!

Start the Bootloader

If you get this message there are three posibilites

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

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

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

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

Lesson 1 – WICED Bluetooth: A Tour of the Resources

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

A Tour of the Resources

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

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

Bluetooth BR+EDR Connectivity Solutions Page

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

WICED CYW20719 Product Page

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

CYW20719 Product Guide

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

CYW20719 Datasheet

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

CYW20719 Software Features

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

And it goes on and on and on from here.

WICED Module Selection Guide

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

CYW920719Q40EVB-01 Development Kit

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

CYW920719Q40EVB-01 Product Page

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

CYW920719 Quick Start

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

CYW920719Q40EVB-01 Evaluation Board User Guide

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

Cypress Community

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

WICED Studio Bluetooth Community

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

WICED Studio Bluetooth Forums

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

WICED Studio

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

WICED Studio Bluetooth Example Projects

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

WICED Studio Documentation

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

WICED Bluetooth API Guide

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

WICED Studio README.txt

WICED Studio Release Notes

 

WICED Studio Technical Brief

WICED Bluetooth 101

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

Lesson 0 – A Two Hour WICED Bluetooth Class

Summary

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

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

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

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

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

You will need a few things for the class:

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

Todays virtual workshop is going to go like this:

WICED Bluetooth Using the CYW20719

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

Source code: 

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

 

WICED Studio 6.2.1

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

CYW920719Q40EVB-01

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

Matrix Orbital GTT43A: 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

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:

 

The Lost Art of Assembly Language Programming

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

PSOC1 Ultrasonic Ranging

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

M8C Architecture

 

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

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

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

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

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

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

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

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

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

CY8CKIT-145-40XX

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

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

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

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

Your workspace ends up looking like this:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

b loop0             // Branch loop0

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

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

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

You can get the project ZIP file here.

Regards
Darrin Vallis

Bosch BMI160 w/PSoC 6 CY8CKIT-028-EPD

Summary

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

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

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

Clone the Bosch BMI160 Driver Library

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

Bridge Control Panel

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

And here is a board with the sensor on it.

CY8CKIT-028-EPD with Bosch BMI160

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

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

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

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

Create New PSoC 6 project & with Bosch BMI160 driver library

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

Use a blank schematic

Give it a name

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

Add a UART and an I2C Master

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

Then assign the pins

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

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

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

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

Add the Bosch Driver files to the project

 

Create the HAL for the Bosch driver

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

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

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

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

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

Given that introduction the write function is simple:

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

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

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

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

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

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

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

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

static void sensorsDeviceInit(void)
{

  int8_t rslt;
  vTaskDelay(500); // guess

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

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

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

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

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

Create the main firmware and test

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

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

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

Now you should have this:

And finally the whole program in one shot

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

static struct bmi160_dev bmi160Dev;

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

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


static void sensorsDeviceInit(void)
{

  int8_t rslt;
  vTaskDelay(500); // guess

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

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

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

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

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

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



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

    UART_1_Start();

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

    while(1);
}

/* [] END OF FILE */

 

PSoC 6 Low Power MCWDT

Summary

In the last article I wrote about using the PSoC 4 Watch Dog Counter as a deep sleep timer.  It seems kind of obvious that I should probably mostly do PSoC 6 articles.  So, in this article, Ill show you how to use the PSoC 6 Low Power MCWDT as a deep sleep timer.  What is a “MCWDT?”, well, the MC stands for Multi-Counter.  And as best I can tell, is exactly the same as the PSoC 4 WDT, except there are two of them in the PSoC 63 instead of the 1 in the PSoC 4.  There is also a dedicated 16-bit WDT (which I will write about in a few days).

Specifically in this article I will show you:

  • The PSoC 6 Low Power MCWDT Multi-Counter WatchDog Timer
  • How to configure the Low Frequency Clock Sources
  • Configuring the Clk_LF Source in Firmware
  • Overall Project Schematic Configuration for the PSoC 6 Low Power MCWDT
  • Configuring the Interrupts
  • Configuring the PSoC 6 Low Power MCWDT
  • The PSoC 6 Low Power MCWDT Firmware

The Multi-Counter WatchDog Timer (MCWDT)

The PSoC 6 Low Power MCWDT is almost exactly the same as the PSoC 4 WDT – except it is in 40nm instead of 130nm.  It has 2x 16-bit counters and 1x 32-bit counter.  The three counters can be cascaded to create long period timers.  Each counter can be configured to clear on match, or free-run.  Each counter can also be configured to cause a device reset if the interrupt is not processed.  The MCWDT works in Active, Low Power Active, Sleep, Low Power Sleep and Deep Sleep power modes.

The design intent is that one MCWDT would be “assigned” to each of the MCUs (i.e. the M4, M0+), but that is not required.  The picture below is a snapshot from the PSoC 63 TRM and explains pretty well how one of the PSoC 6 Low Power MCWDT work.

PSoC 6 Low Power MCWDT

Low Frequency Clock Sources (CLK_LF)

From the picture above you can see that the MCWDT uses the LFLK as the source clock for the counters.  Fortunately or unfortunately there is an inconsistency in the PSoC 63 TRM and PSoC Creator.  The PSoC Creator team decided to unify the names of all of the clocks by calling them all “Clk_*”.  Which is inconsistent with the TRM which calls it “LFLK”.  The LFCLK in the TRM is called the “Clk_LF” in the PSoC Creator GUIs and it is called “CLK_LF” or “ClkLf” in the firmware (confused yet?).  If not then you should be as I was the first time I saw it.

The Clk_LF can be driven by one of three source oscillators.  The PILO, ILO or the WCO.  Look at the upper left hand box where I have pulled down the selection menu.

PSoC 6 Clock Configuration

But what is WCO, PILO and ILO?  These are clock sources which you can select the Clk_LF clock source on the “Source Clocks” page.  It can be:

  • The Internal Low Speed Oscillator (ILO) which is a super low power 32KHz RC Oscillator (but not very accurate)
  • The Precision Internal Low Speed Oscillator (PILO) is a more accurate, trim-able RC? oscillator, and presumably higher power (though I don’t know how much).
  • The Watch Crystal Oscillator (WCO) which is a very accurate crystal oscillator that gives you accurate timing at a higher power cost.

With the “Configure System Clocks” window in the DWR you can configure the behavior of the Clock Sources.  Notice that I have turned on all three of the Clock sources (ILO, PILO and WCO) something which you probably wouldn’t actually do.

PSoC 6 Source Clock Configuration

When you click those buttons, PSoC Creator will create a function called “ClockInit” in the file “cyfitter_cfg.c” that is called by the PSoC startup code (and runs before your main() ).  You can see that the firmware enables the PILO and WCO (as well as the ILO which is on by default)

static void ClockInit(void)
{
	uint32_t status;

	/* Enable all source clocks */
	Cy_SysClk_PiloEnable();
	Cy_SysClk_WcoEnable(900u);
	Cy_SysClk_ClkLfSetSource(CY_SYSCLK_CLKLF_IN_PILO);

	/* Configure CPU clock dividers */
	Cy_SysClk_ClkFastSetDivider(0u);
	Cy_SysClk_ClkPeriSetDivider(1u);
	Cy_SysClk_ClkSlowSetDivider(0u);

Configuring the Clk_LF Source in Firmware

You can also configure the Clk_LF sources in your firmware.  In the firmware below, I check to see if the WCO is running.  If it is, then I set it to be the source of the Clk_LF.  If it is not running, then I try to start it.  And, finally, I print out the current source of the Clk_LF.

    // Is the Watch Crystal Osc running?
    if(Cy_SysClk_WcoOkay())
    {
        printf("Switching ClkLf to WCO\n");
        Cy_SysClk_ClkLfSetSource(CY_SYSCLK_CLKLF_IN_WCO);
    }
    else
    {
        printf("WCO Not functioning attempting a start\n");
        Cy_SysClk_WcoEnable(0); // come back immediately
        for(int i=0;i<100;i++) 
        {
            CyDelay(10);
            if(Cy_SysClk_WcoOkay())
            {
                printf("Suceeded in starting WCO in %dms\n",i*10);
                Cy_SysClk_ClkLfSetSource(CY_SYSCLK_CLKLF_IN_WCO);
                break;
            }
        }
        if(!Cy_SysClk_WcoOkay())
        {
            printf("Unable to start WCO in 1000ms\n");
        }
    }
                     
    // What is the clock source of the ClkLf?
    switch(Cy_SysClk_ClkLfGetSource ())
    {
        case CY_SYSCLK_CLKLF_IN_ILO:
            printf("Clk LF = ILO\n");
            break;
        case CY_SYSCLK_CLKLF_IN_ALTLF:
            printf("Clk LF = ALTLF\n");
        break;
        case CY_SYSCLK_CLKLF_IN_PILO:
            printf("Clk LF = PILO\n");
        break;
        case CY_SYSCLK_CLKLF_IN_WCO:
            printf("Clk LF = WCO\n");
        break;
    }

Overall Project Schematic Configuration

To demonstrate the PSoC 6 Low Power MCWDT, I start by creating a schematic.  It has the three color LEDs pins (RED, GREEN and BLUE), a UART to print out debugging information and finally the MCWDT connected to an interrupt.

PSoC 6 Low Power MCWDT Schematic

The pin assignment is chosen to match the pins on my CY8CKIT-062-BLE Development kit. (look on the back)

PSoC 6 Pin Configuration

CY8CKIT-062-BLE

Configuring the Interrupts

When you place an interrupt component in PSoC Creator (which I did in the above schematic), it will then appear in the DWR on the interrupt page.  Here you can assign the interrupt to either of the MCU Cores, or I suppose both, though I think that is  probably a horrible idea.

PSoC Creators “fitter” sees that the interrupt is connected to a MCWDT.  It then does the job of attaching the interrupt to the correct interrupt number, in this case 19

PSoC 6 Interrupts

All the screen above does is create a little block of code in the firmware file cyfitter_sysint_cfg.c.  Look at the structure called cy_stc_sysint_t SysInt_1_cfg in the automatically generated code:

/*******************************************************************************
* File Name: cyfitter_sysint_cfg.c
* 
* PSoC Creator  4.2 Nightly Build 543
*
* Description:
* 
* This file is automatically generated by PSoC Creator.
*
********************************************************************************
* Copyright (c) 2007-2017 Cypress Semiconductor.  All rights reserved.
* You may use this file only in accordance with the license, terms, conditions, 
* disclaimers, and limitations in the end user license agreement accompanying 
* the software package with which this file was provided.
********************************************************************************/

#include "cyfitter_sysint.h"
#include "cyfitter_sysint_cfg.h"

/* ARM CM4 */
#if (((__CORTEX_M == 4) && (CY_CORE_ID == 0)))

    /* SysInt_1 */
    const cy_stc_sysint_t SysInt_1_cfg = {
        .intrSrc = (IRQn_Type)SysInt_1__INTC_NUMBER,
        .intrPriority = SysInt_1__INTC_CORTEXM4_PRIORITY
    };

    /* UART_SCB_IRQ */
    const cy_stc_sysint_t UART_SCB_IRQ_cfg = {
        .intrSrc = (IRQn_Type)UART_SCB_IRQ__INTC_NUMBER,
        .intrPriority = UART_SCB_IRQ__INTC_CORTEXM4_PRIORITY
    };

#endif /* ((__CORTEX_M == 4) && (CY_CORE_ID == 0)) */

So, where does SysInt_1__INTC_NUMBER get set?  That is done by the fitter in cyfitter_sysint.h.  Here is the automatically generated code:

/*******************************************************************************
* File Name: cyfitter_sysint.h
* 
* PSoC Creator  4.2 Nightly Build 543
*
* Description:
* 
* This file is automatically generated by PSoC Creator.
*
********************************************************************************
* Copyright (c) 2007-2017 Cypress Semiconductor.  All rights reserved.
* You may use this file only in accordance with the license, terms, conditions, 
* disclaimers, and limitations in the end user license agreement accompanying 
* the software package with which this file was provided.
********************************************************************************/

#ifndef INCLUDED_CYFITTER_SYSINT_H
#define INCLUDED_CYFITTER_SYSINT_H
#include "cy_device_headers.h"

/* SysInt_1 */
#define SysInt_1__INTC_CORTEXM4_ASSIGNED 1
#define SysInt_1__INTC_CORTEXM4_PRIORITY 7u
#define SysInt_1__INTC_NUMBER 19u
#define SysInt_1_INTC_CORTEXM4_ASSIGNED 1
#define SysInt_1_INTC_CORTEXM4_PRIORITY 7u
#define SysInt_1_INTC_NUMBER 19u

/* UART_SCB_IRQ */
#define UART_SCB_IRQ__INTC_CORTEXM4_ASSIGNED 1
#define UART_SCB_IRQ__INTC_CORTEXM4_PRIORITY 7u
#define UART_SCB_IRQ__INTC_NUMBER 46u
#define UART_SCB_IRQ_INTC_CORTEXM4_ASSIGNED 1
#define UART_SCB_IRQ_INTC_CORTEXM4_PRIORITY 7u
#define UART_SCB_IRQ_INTC_NUMBER 46u

#endif /* INCLUDED_CYFITTER_SYSINT_H */

To make all of this work, you simply need to install the interrupt handler by calling Cy_SysInt_Init in your main.c.

    // install ISR...
    Cy_SysInt_Init(&SysInt_1_cfg,myWDT);
    NVIC_EnableIRQ(SysInt_1_INTC_NUMBER);

Configuring the PSoC 6 Low Power MCWDT with the GUI

As with all (or almost all) PSoC components, you can configure them in the GUI.  In the picture below you can see that you can set

  1. Enable/Disable
  2. The Match Value
  3. The Mode (interrupt, watchdog, none)
  4. Free Running or Clear on Match
  5. The Cascade (meaning connect the counters together)

PSoC 6 Low Power MCWDT Configuration GUI

All of those configuration items will end up in the file MCWDT_1_PDL.c in a structure that looks like this:

/** The instance-specific configuration structure. This should be used in the 
*  associated MCWDT_1_Init() function.
*/ 
const cy_stc_mcwdt_config_t MCWDT_1_config =
{
    .c0Match     = MCWDT_1_C0_MATCH,
    .c1Match     = MCWDT_1_C1_MATCH,
    .c0Mode      = MCWDT_1_C0_MODE,
    .c1Mode      = MCWDT_1_C1_MODE,
    .c2ToggleBit = MCWDT_1_C2_PERIOD,
    .c2Mode      = MCWDT_1_C2_MODE,
    .c0ClearOnMatch = (bool)MCWDT_1_C0_CLEAR_ON_MATCH,
    .c1ClearOnMatch = (bool)MCWDT_1_C1_CLEAR_ON_MATCH,
    .c0c1Cascade = (bool)MCWDT_1_CASCADE_C0C1,
    .c1c2Cascade = (bool)MCWDT_1_CASCADE_C1C2
};

Then you can either call the MCWDT_1_Start() function or you can call the PDL function Cy_MCWDT_Init(&MCWDT_1_config);

PSoC 6 Low Power MCWDT Firmware

You can also configure the PSoC 6 Low Power MCWDT in your firmware as I have done below:

   Cy_MCWDT_Unlock(MCWDT_STRUCT0);
    // Turn off all of the counters
    Cy_MCWDT_Disable(MCWDT_STRUCT0,CY_MCWDT_CTR0 | CY_MCWDT_CTR1 | CY_MCWDT_CTR2,100);
    Cy_MCWDT_ResetCounters(MCWDT_STRUCT0,CY_MCWDT_CTR0,100);
    Cy_MCWDT_ResetCounters(MCWDT_STRUCT0,CY_MCWDT_CTR1,100);
    
    Cy_MCWDT_SetMode(MCWDT_STRUCT0,0,CY_MCWDT_MODE_NONE); // 0=Counter 0
    Cy_MCWDT_SetMode(MCWDT_STRUCT0,1,CY_MCWDT_MODE_INT);  // 1=Counter 1
    
    Cy_MCWDT_SetCascade(MCWDT_STRUCT0,CY_MCWDT_CASCADE_C0C1 );
    
    Cy_MCWDT_SetInterruptMask(MCWDT_STRUCT0,CY_MCWDT_CTR1); // Only take ints from counter 1
    
    // delay = 32768*16 = 8 seconds & 32khz
    Cy_MCWDT_SetMatch(MCWDT_STRUCT0,0,32768,100);
    Cy_MCWDT_SetMatch(MCWDT_STRUCT0,1,16,100);
       
    Cy_MCWDT_SetClearOnMatch(MCWDT_STRUCT0,0,1);
    Cy_MCWDT_SetClearOnMatch(MCWDT_STRUCT0,1,1);
    
    Cy_MCWDT_Enable(MCWDT_STRUCT0,CY_MCWDT_CTR0|CY_MCWDT_CTR1,100);

The Interrupt Service Routine (ISR) for the MCWDT is pretty simple.  It just reads the cause of the interrupt, which could be any one of the counters in the MCWDT (or more than one of them).  Then it inverts either the Red, Green or Blue LED.  And finally it clears the interrupt source inside of the MCWDT.

void myWDT(void)
{
    uint32_t cause;
    cause = Cy_MCWDT_GetInterruptStatusMasked(MCWDT_STRUCT0);
    if(cause & CY_MCWDT_CTR0)
    {
        Cy_GPIO_Inv(RED_PORT,RED_NUM);
    }
    
    if(cause & CY_MCWDT_CTR1)
    {
        Cy_GPIO_Inv(GREEN_PORT,GREEN_NUM);
    }
    
    if(cause & CY_MCWDT_CTR2)
    {
        Cy_GPIO_Inv(BLUE_PORT,BLUE_NUM);
    }
    
    Cy_MCWDT_ClearInterrupt(MCWDT_STRUCT0,cause);
}

When I run this program I get a slowly blinking Green LED in Low Power mode:

PSoC 6 Low Power MCWDT