Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob

Creating a Bluetooth LE Peripheral using ModusToolbox #4

Creating a Bluetooth LE Peripheral using ModusToolbox #4

markgsaunders
Employee
Employee
50 sign-ins 10 solutions authored 5 solutions authored

If you followed along with yesterday's blog, you should have a working BLE peripheral today. You should be able to send commands from your phone to the peripheral with wild abandon. It's good to be the King! Today we are going to turn things around and send messages from the peripheral to the phone.

Before we jump in, though, you may have noticed that the CySmart App has a tendency to remember a little too much about the connected peripheral. You program in a new application but the app keeps showing the old configuration. For example, you might have struggled to get the FindMe profile to appear when you connect. In today's example we are going to add another profile so it's a good time to give you a "clear your cache" tip. CySmart remembers the peripheral name and ID after a connection. As a result it thinks it knows that the device can do... but it has changed. To force it to refresh, go to your BLE settings on the phone and clear/delete the device. If the device is not there (exact behavior depends upon the phone and OS version) just give BLE the good old off-n-on treatment. On my phone I just swipe the wee green slider widget left, and then right again, and I am good to go.

OK, so let's add the Battery service to our phone. Things start out the same way as they did yesterday. Open the Configurator and add a "Battery" service in the "Server", alongside the Immediate Alert. Then click on the "Battery Level" characteristic and make a couple of edits. For value, write "100", which corresponds to a full battery (it is a percentage). The check the "Notify" box. That last edit is the cunning part...

ModusToolbox BLE Configurator setting up a Battery service

Save your changes and go back to editing main.c. The reason why this checkbox is important is that it enables the peripheral to update the phone without the phone expressly asking for the battery level. Woooo-hoooo! We are going to write a few lines of code that run when you press the button on the kit. That code will check that there is a connection, read the current battery level, decrement it, write the value back to the GATT database, and then tell the phone what happened.

Start by creating a global to hold the connection information. We need this because BLE supports multiple connections and it is important to only send messages to the right phone. When the transferred data is bit more important that the battery level, this becomes a big deal!

static cy_stc_ble_conn_handle_t conn_handle;

Next, we need to set the variable on a connection and clear it upon disconnect. Yes, you are right, I am being a little bit lazy by not maintaining an array of handles. But my application only supports one connection at a time. And I am really lazy person. So deal with it! Here's the code in stack_handler() - notice how the eventParam for the connection event is the handle itself.

  case CY_BLE_EVT_GATT_CONNECT_IND:
    Cy_SCB_UART_PutString( KIT_UART_HW, "Connected\r\n" );
    conn_handle = *(cy_stc_ble_conn_handle_t *)eventParam;
  break;

  case CY_BLE_EVT_GAP_DEVICE_DISCONNECTED:
    Cy_SCB_UART_PutString( KIT_UART_HW, "Disconnected\r\n" );
    Cy_BLE_GAPP_StartAdvertisement( CY_BLE_ADVERTISING_FAST, CY_BLE_PERIPHERAL_CONFIGURATION_0_INDEX );
    alert( CY_BLE_NO_ALERT );
    conn_handle.attId = 0;
    conn_handle.bdHandle = 0;
  break;

Just like the Immediate Alert, we should have an attribute change handler. All mine does is print the state of the notification attribute.

void BAS_handler( uint32_t event, void* eventParam )
{
  Cy_SCB_UART_PutString( KIT_UART_HW, "Notification " );
  Cy_SCB_UART_PutString( KIT_UART_HW, ( CY_BLE_EVT_BASS_NOTIFICATION_ENABLED == event ) ? "on\r\n" : "off\r\n" );
}

 

Register the handler in the same way as before, in main().

 

  Cy_BLE_RegisterEventCallback( stack_handler );

  Cy_BLE_IAS_RegisterAttrCallback( IAS_handler );

  Cy_BLE_BAS_RegisterAttrCallback( BAS_handler );

 

One last thing before playtime. Update the button_isr() function to update the GATT and notify the phone.

  1. Check for a valid connection with Cy_BLE_GetConnectionState()
  2. Read the current battery level from GATT with Cy_BLE_BASS_GetCharacteristicValue() - note that the function takes a size and a pointer argument for the data to be read
  3. Simulate a depleting battery by decrementing the level (and wrap back to 100 should you press the button 100 times)
  4. Write the new level to the database
  5. Read the notify characteristic from GATT with Cy_BLE_BASS_SetCharacteristicValue()
  6. If it is set then push a message up to the phone with Cy_BLE_BASS_SendNotification()

Here is the code to do that. Add it after you clear the interrupt. It's a little daunting the first time but I think that the function names become obvious pretty quickly and you soon get the hang of the defines for the service index and characteristics.

 

  if( Cy_BLE_GetConnectionState( conn_handle ) == CY_BLE_CONN_STATE_CONNECTED )
  {
    uint8_t battery_level;
    uint8_t notify;

    /* Read the current battery level from GATT */
    Cy_BLE_BASS_GetCharacteristicValue( CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, sizeof( battery_level ), &battery_level );

    /* Decrement the level */
    battery_level--;
    if( battery_level == (uint8_t)(-1) )
      battery_level = 100;

    /* Write the new level back to GATT */
    Cy_BLE_BASS_SetCharacteristicValue( CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, sizeof( battery_level ), &battery_level );

    /* Is notification enabled? If yes, tell the central */
    Cy_BLE_BASS_GetCharacteristicDescriptor( conn_handle, CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, CY_BLE_BAS_BATTERY_LEVEL_CCCD, sizeof( notify ), &notify );

    if( notify )
      Cy_BLE_BASS_SendNotification( conn_handle, CY_BLE_BATTERY_SERVICE_INDEX, CY_BLE_BAS_BATTERY_LEVEL, sizeof( battery_level ), &battery_level );
  }

 

Done typing? Good, program that bad boy!

 

When you connect now, you should see a third profile - Battery Service. It shows a full battery (because we set the value to 100 in the Configurator). And there are two buttons - Read and Notify. If you press Read it looks like nothing happens because it is just reading the same value every time. Press the button on the kit and read again... it should come back with 99. Now press the notify button and repeatedly hit that kit button. Watch as the battery level steadily decreases... you can go all the way down to zero and wrap around if you wish. In fact I know you will. You cannot stop yourself!

CySmart Battery service

Well done - you have now created a peripheral device that can send and receive messages. Pretty cool no? But there is the slight problem that any Tom, Disk or Harry can make a connection. Tomorrow, I'll wrap up this series of blogs by adding a passkey to control the pairing of phone and device. As before, the updated main.c file is attached.

476 Views
4 Comments
Authors