Summary

Last week I got a nice email from a Cypress customer in Italy (I think).  He asked about implementing a PSoC4 BLE Central.  I replied that it was pretty easy.  Over the weekend I thought about it a little bit more and realized that I had never implemented the Central a.k.a. the “other” side of a BLE connection in a PSoC.  I had always built the Central side in an iPhone application written in Swift or Android application written in Java.  So it seemed like a good thing to do to implement it in C.  It turned out to not be quite as easy I had hoped because of some problems in the Cypress documentation.  In the next several articles I am going to show you how to implement a PSoC4 BLE Central.

I am going to use the project from the video series called “capsenseled” as the GAP Peripheral and then I am going to show you how to write the GAP Central code.  You can learn about the GAP Peripherals in the video series called “PSoC® 4 BLE 101 Video Tutorial Series: How To Create Products Using PSoC 4 BLE“.  Here is the first video from YouTube.

In that series of videos I showed you how to build a custom GATT profile for Capsense and an LED.  The GAP Peripheral has an LED that you can control from the GAP Central, and it has a Capsense slider that you can read its value from the GAP Central.  You can “git” all of the source for this project from the IoTExpert Github website or git@github.com:iotexpert/PSoC4BLE-Central.git

In this article I will show you:

  • The GAP Peripheral and GATT Database
  • How to build the schematic for the GAP Central
  • How to configure the BLE component for the GAP Central functionality
  • The steps required in the firmware to read and write the GAP Peripheral

The GAP Peripheral and GATT Database

To start the process I will “git” the source code for this GAP Peripheral from the IOT Expert Github site.  Then, I open the project called “capsenseled” in PSoC Creator.  The schematic has:

  • The Capsense component which controls the slider
  • A PWM that drives the blue LED to serve as a notification of the state of the BLE (blinking means advertising)
  • A red LED which can be turned on/off from the GAP Central Side
  • The BLE Component

PSoC4 BLE CapSense LED Peripheral

The BLE Component is configured to be:

  • A GAP Peripheral
  • with a GATT Server

PSoC4 BLE Peripheral Configuration

The GATT Database is setup to have:

  • A custom service called “ledcapsense”
  • A custom characteristic called “led” (a uint8)
  • A custom characteristic called “capsense” (a uint16)

PSoC4 BLE Peripheral GATT Configuration

The advertising packet is setup to advertise

  • The name of the device “capled”
  • The fact that it has a GATT Service called “ledcapsense”

PSoC4 BLE Peripheral Advertising Packet

When you program this project you will get a blinking blue LED (that says the BLE is advertising).  When I run “lightblue” I can see the “capled” advertising:
LightBlue BLE Explorer

After I connect the device I can see the UUID of the custom service & the two characteristics:

LightBlue BLE Explorer

Finally I can read/write the led characteristic.

LightBlue BLE Explorer

PSoC4 BLE Central Schematic

To make my PSoC4 BLE Central I build a project for the CY8CKIT-042-BLE  (you can see all of the source code in the project called “centralled”

CY8CKIT-042-BLE

Start by building a schematic with

  • A debug UART (I will print to the terminal program)
  • The red/blue/green led pins
  • The mechanical button pin (called SW1)
  • A PWM to drive the green LED (it will vary the brightness of the LED based on the capsense value on the GAP Peripheral)
  • The BLE Component

PSoC4 BLE Central Schematic

Configure the PWM (to vary between 0 and 100% brightness) with inverted logic (because the LED on the board is active low)

PSoC4 BLE Central PWM

Assign the Pins:

PSoC4 BLE Central Pin Assignment

You will need to configure the BLE to be a:

  • A GATT Client
  • A GAP Central

PSoC4 BLE Central BLE Configuration

If you tell the BLE Component what the GATT Server configuration looks like (on the GAP Peripheral side) it will make the service discovery firmware much simpler.  To do this, go to the GAP Peripheral project and export the “ledcapsense” service from the Peripheral into an xml file (see the Save Selected Service…)

PSoC4 BLE Central GATT Database

Then go back to your PSoC4 BLE Central project and add a new service “From file” (see at the bottom of the menu)

PSoC4 BLE Central - Load Service

After all that you will end up with something that looks like this.  You can see the “ledcapsense” service is listed under the “client” section of the GATT configuration.

PSoC4 BLE Central GATT Database

PSoC4 BLE Central Firmware

The firmware for the PSoC4 BLE Central needs to:

  1. Initialize the PSoC
  2. Scan for GAP Peripherals that are advertising the “ledcapsense” service
  3. Connect to the GAP Peripheral & Discover its services
  4. Turn on notifications for the Capsense characteristic
  5. Loop
    1. If the users changes the state of the button then send over a LED on/off
    2. If you receive a notification of a change in capsense then update the PWM Compare value (to change the intensity)

Most of the Cypress BLE APIs will create an “event” that you will need to process in the BLE loop.  The interesting events are:

Event Description
CYBLE_EVT_STACK_ON The BLE Stack has turned on and you can start advertising
CYBLE_EVT_GAP_DEVICE_DISCONNECTED The GAP Peripheral you were attached to has disconnected.  Start scanning for another GAP Peripheral to attach to
CYBLE_EVT_GAP_DEVICE_CONNECTED You have successfully completed a connection to a GAP Peripheral
CYBLE_EVT_GAPC_SCAN_PROGRESS_RESULT You received an advertising packet from a GAP Peripheral
CYBLE_EVT_GAPC_SCAN_START_STOP You either stopped or started scanning for advertising packets from GAP Peripherals
CYBLE_EVT_GATT_CONNECT_IND The GATT Connection is complete.  This event occurs before the CYBLE_EVT_GAP_DEVICE_CONNECTED EVENT so you don't need to do anything
CYBLE_EVT_GATTC_DISCOVERY_COMPLETE The service discovery is complete.  You can now set the CCCD and turn on the PWM
CYBLE_EVT_GATTC_HANDLE_VALUE_NTF You received a notification of a change in the Capsense from the GAP Peripheral.
CYBLE_EVT_GATTC_WRITE_RSP: Your write succeeded.  For this application there is nothing to do

To make all of this work, I created a state machine using an enumerated datatype.

// Modes for a statemachine
typedef enum SystemMode {
    SM_INITIALIZE,          // Startup state
    SM_SCANNING,            // Looking for a Peripheral
    SM_CONNECTING,          // I have issued connected and waiting for it to complete
    SM_SERVICEDISCOVERY,    // Started the service discovery and waiting for it to complete
    SM_CONNECTED            // Connection complete... 
} SystemMode_t;

static SystemMode_t systemMode = SM_INITIALIZE; // Starting mode of statemachine

1. Initialize the PSoC4 BLE Central

The project starts with the normal turn on the PSoC components and stuff.

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */
    UART_Start();
    PWM_Start();
    CyBle_Start( CyBle_AppCallback );

2. Scan for Peripherals that are advertising the “ledcapsense” service

Most everything that happens in the BLE sub-system triggers a callback to your “AppCallback” which you registered when you started up the BLE component.  The AppCallback has two parameters, which event and a generic void* style argument which can be one of a bunch of things.

(Lines 73-79)  After you turn on the power to your central and the BLE subsystem you will need to start scanning for peripherals that are advertising.  To do this you call “CyBle_GapcStartScan”.  The “Gapc” part of the API name means GAP Central.  The other time you need to start scanning is when you have been disconnected.

(Lines 81-93)  When you receive an advertising packet you can look in the packet and figure out if it is a GAP Peripheral that you want to attach too.  In this case I:

  • Only look at packets that are 29 bytes long (which I looked up in the BLE GAP Peripheral project)
  • And.. if the packet has the UUID of the capsenseled service
  • Then I save the Bluetooth Device Address (BD address) and stop scanning.  When the scanning is stopped the BLE subsystem will issue and event called “CYBLE_EVT_GAPC_SCAN_START_STOP” (the next section)
       case CYBLE_EVT_STACK_ON:
        case CYBLE_EVT_GAP_DEVICE_DISCONNECTED:
            systemMode = SM_SCANNING;
            enabledCapsenseNotifications = 0;
            CyBle_GapcStartScan(CYBLE_SCANNING_FAST); // Start scanning for peripherals
            PWM_WriteCompare(0); PWM_Stop();          // Turn off the LED
        break;

        CYBLE_GAPC_ADV_REPORT_T* scanReport;
        case CYBLE_EVT_GAPC_SCAN_PROGRESS_RESULT:                     // Advertising packet
            scanReport = (CYBLE_GAPC_ADV_REPORT_T*)eventParam;
            if(scanReport->dataLen != 29)                             // Number of bytes in ledcapsense advertising packet
                break;
            if(memcmp(&CapLedService,&scanReport->data[11],sizeof(CapLedService))) // if service is in packet
                return;
                  
            // Setup for the connection
            remoteDevice.type = scanReport->peerAddrType;          // setup the BD addr
            memcpy(&remoteDevice.bdAddr,scanReport->peerBdAddr,6); // 6 bytes in BD addr
            systemMode = SM_CONNECTING;
            CyBle_GapcStopScan();                                  // stop scanning for peripherals
            
        break;

        

 

3. Connect to the Peripheral & Discover the Services

(lines 97-100) When the BLE either starts or stops scanning it issues the “CYBLE_EVT_GAPC_SCAN_START_STOP” event.  I use the state machine to determine what to do.  Basically if the mode of my system is “SM_CONNECTING” then I attempt to make a connection to the remote device.

(lines 102-105)  Once the device is connected you need to do a service discovery to find out what services are on the GAP Peripheral and more importantly what are the handles of the characteristics.  The CyBle_GattcStartDiscovery issues requests to the GAP Peripheral to tell the services and characteristics on the peripheral, to which it responds with events.  Cypress provides code in BLE_custom.h and BLE_custom.c to process those responses.  Once the discovery is complete you are then ready to talk to the device.

        case CYBLE_EVT_GAPC_SCAN_START_STOP: // If you stopped scanning to make a connection.. then launch connection
            if(systemMode == SM_CONNECTING ) 
                CyBle_GapcConnectDevice(&remoteDevice);
        break;

        case CYBLE_EVT_GAP_DEVICE_CONNECTED:              // Connection request is complete
            CyBle_GattcStartDiscovery(cyBle_connHandle);  // Discover the services on the GATT Server
            systemMode = SM_SERVICEDISCOVERY;
        break;

4. Turn on notifications for the Capsense characteristic

We are interested in getting notified when the Capsense characteristic on the GAP Peripheral changes.  To enable getting a notification you need to write a “0x01” into the Client Characteristic Configuration Descriptor or CCCD.  In order to accomplish this you need to know the handle of that descriptor.  The handle is discovered automatically by the BLE_custom.c functions.  If you look in BLE_custom.h you will find #define(s) that are the indexes of the things you need to know from the service discovery array.

/* Below are the indexes and handles of the defined Custom Services and their characteristics */
#define CYBLE_CUSTOMC_LEDCAPSENSE_SERVICE_INDEX   (0x00u) /* Index of ledcapsense service in the cyBle_customCServ array */
#define CYBLE_CUSTOMC_LEDCAPSENSE_LED_CHAR_INDEX   (0x00u) /* Index of led characteristic */
#define CYBLE_CUSTOMC_LEDCAPSENSE_LED_CHARACTERISTIC_USER_DESCRIPTION_DESC_INDEX   (0x00u) /* Index of Characteristic User Description descriptor */
#define CYBLE_CUSTOMC_LEDCAPSENSE_CAPSENSE_CHAR_INDEX   (0x01u) /* Index of capsense characteristic */
#define CYBLE_CUSTOMC_LEDCAPSENSE_CAPSENSE_CAPSENSECCCD_DESC_INDEX   (0x00u) /* Index of capsensecccd descriptor */
#define CYBLE_CUSTOMC_LEDCAPSENSE_CAPSENSE_CHARACTERISTIC_USER_DESCRIPTION_DESC_INDEX   (0x01u) /* Index of Characteristic User Description descriptor */

To set the CCCD you create a “CYBLE_GATTC_WRITE_REQ_T” and fill it in with the information required.  Specifically, the value and the handle which you find on line 57.  Then you write it to the GAP Peripheral.

/***************************************************************
 * Function to set the Capsense CCCD to get notifications
 **************************************************************/
void updateCapsenseNotification()
{
    CYBLE_GATTC_WRITE_REQ_T 	tempHandle;
    uint8 cccd=1;  
    enabledCapsenseNotifications = 1;
    
    tempHandle.attrHandle = cyBle_customCServ[CYBLE_CUSTOMC_LEDCAPSENSE_SERVICE_INDEX]
    .customServChar[CYBLE_CUSTOMC_LEDCAPSENSE_CAPSENSE_CHAR_INDEX]
    .customServCharDesc[CYBLE_CUSTOMC_LEDCAPSENSE_CAPSENSE_CAPSENSECCCD_DESC_INDEX].descHandle;
    
  	tempHandle.value.val = (uint8 *) &cccd;
    tempHandle.value.len = 1;
    
    CyBle_GattcWriteCharacteristicValue(cyBle_connHandle,&tempHandle);
}

5. Main Loop

The main look is simple.  Basically if you are in the SM_CONNECTED state, then call the function which updates the state of the Remote LED.

   for(;;)
    { 
        switch(systemMode)
        {
            case SM_INITIALIZE:
            case SM_SCANNING:
            case SM_CONNECTING:
            case SM_SERVICEDISCOVERY:
            break;
            case SM_CONNECTED:
                updateLed();
            break;
        }
         
        CyBle_ProcessEvents();
        CyBle_EnterLPM(CYBLE_BLESS_DEEPSLEEP);    
    }
}

 

5. Main Loop (Write the state of the LED)

This function looks at the state of the switch, then if it has changed, sends the updates state to the Peripheral.  The switch is active low (a 0 means that it is pressed) but we want a “1” to mean turn on the LED.

Just like the updateCapsenseNotification() function, you look in the cycle_customCServ array to find the handle of the LED characteristic.

/***************************************************************
 * Function to update the LED state in the GATT database
 * Based on state of swtich
 **************************************************************/
void updateLed()
{
    static int previousState = 10;       // The first time it is called send the data
    uint8 state;                         // A place to hold the state of the switch
    CYBLE_GATTC_WRITE_REQ_T tempHandle;  // A handle to call the BLE API
    
    state = !SW1_Read();                 // Active low switch
    if(state == previousState)           // If nothing has changed dont send anythign
        return;
    
    previousState = state;
    tempHandle.attrHandle = cyBle_customCServ[CYBLE_CUSTOMC_LEDCAPSENSE_SERVICE_INDEX]
        .customServChar[CYBLE_CUSTOMC_LEDCAPSENSE_LED_CHAR_INDEX]
        .customServCharHandle;
        
  	tempHandle.value.val = (uint8 *) &state;
    tempHandle.value.len = 1;
    
    CyBle_GattcWriteCharacteristicValue(cyBle_connHandle,&tempHandle);
}

5. Main Loop (Capsense Notifications)

The last bit of code updates the state of the PWM driving the LED when you get a notification from the GAP Peripheral that it has been updated.

        CYBLE_GATTC_HANDLE_VALUE_NTF_PARAM_T *capsenseNTF;    
        case CYBLE_EVT_GATTC_HANDLE_VALUE_NTF:                                 // Capsense Notification Recevied
            capsenseNTF = (CYBLE_GATTC_HANDLE_VALUE_NTF_PARAM_T *)eventParam;
            if(capsenseNTF->handleValPair.value.val[0] == 0xFF)                // Turn off the LED in no touch
                PWM_WriteCompare(0);
            else
                PWM_WriteCompare(capsenseNTF->handleValPair.value.val[0]);
        break;
            
        case CYBLE_EVT_GATTC_WRITE_RSP: // Sucesfull write - nothing to do
        break;

In the next Article(s) I will show you how to use the CySmart dongle.  I am also planning on showing you the firmware to create a GATT Browser.

You can find all of the source code for these projects at github.com/iotexpert/PSoc4BLE-Central 

Recommended Posts

4 Comments

  1. Dear Sir, where I can get source code for “centralled”.

  2. Hi, I am having some problems understanding “write without response” setup.
    In the “capsenseled” server project “CYBLE_EVT_GATTS_WRITE_REQ” is used instead of “CYBLE_EVT_GATTS_WRITE_CMD_REQ” in the event handler, but “write without response” is selected in the component setup.
    According to this other project I am looking at ( https://www.digikey.com/eewiki/pages/viewpage.action?pageId=55574662 ) both are used with comments as follows

    case CYBLE_EVT_GATTS_WRITE_REQ: //Write with response
    case CYBLE_EVT_GATTS_WRITE_CMD_REQ: //Write without response

    Why is “CYBLE_EVT_GATTS_WRITE_CMD_REQ” NOT used in the capsenseled project, and does “CYBLE_EVT_GATTS_WRITE_REQ” still function in a write without response setup?
    Am I misunderstanding the definition of a response?
    Is the “response” referring to an event that occurs within the server device, or is a response defined as a reply from the client device?

    I am trying to set up a low power single button remote to report the state of the button, to be handled by the client which is connected to a larger power source and will possibly count edges, measure button press duration, or both, etc. Ideally the remote would be asleep except for when it needs to tell the client that the button has changed states, and would not spend any energy processing a response from the client.


Add a Comment

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