Prototyping BLE apps on the nRF52840 USB Dongle (Part B)

As a continuation of last week’s tutorial (Prototyping BLE apps on the nRF52840 USB Dongle (Part A)), where we covered the following:

  • Adding header rows to the USB dongle
  • Mounting it on a breadboard
  • Connecting an external LED and an external push button to the dongle
  • Reuse and modify a Nordic template BLE peripheral example to assign the correct pins to the external LED and button
  • Flashing the USB dongle with the firmware (via the Programmer app part of nRF Connect for the desktop)

If you haven’t gone through last week’s tutorial, I highly encourage you to do so before continuing with this post (you can read it here).

In today’s tutorial, we will get to do some of the more fun stuff!

We’ll go through:

  • Overview of the hardware design and connections
  • Implementing a custom Service comprised of two Characteristics:
    • A characteristic used to allow turning the LED on and off
    • A characteristic used to report the state of the button (pushed vs. released)
  • Implement the button handler to report push and release events via Notifications to the Client (the BLE Central in our case)
  • Implement the handler for writes to the LED characteristic to turn on/off the external LED
  • Testing functionality by connecting to the dongle from a BLE mobile app (such as nRF Connect or LightBlue)

Hardware Overview

Let’s take a look back at the schematic for our project and review our peripheral connections:

Figure 1: Schematic

  • The LED is connected to Pin #1.10
  • The push button is connected to Pin #0.24

And here’s a look at what the circuit looks like on a breadboard:

Figure 2: Complete Circuit

We could take this a step forward and make the hardware layout more compact. What I’ve done is stick together the two tiny breadboards back-to-back and run some rigid copper wires to reduce the space and make the hardware more stable physically:

Figure 3: Compact hardware layout

Now that we’ve gotten the hardware up and ready, let’s get working on the firmware to fully implement the project.

Custom GATT Design

In our project, we mentioned that we have one custom service and two custom characteristics. The first step in any custom GATT design is assigning UUIDs to each custom service or characteristic. In BLE, user-assigned UUIDs are always 128 bits. The easiest way is to use an online GUID generator like https://www.guidgenerator.com/ (or you could simply assign a manual UUID).

We go over the details of designing custom services in a previous blog post. Check it out here:
Bluetooth GATT: How to Design Custom Services & Characteristics [MIDI device use case]

  • Once you have the GUID generator website launched, check the Uppercase option, enter in the field “How many GUIDs do you want (1-2000):”.
    The reason we will be generating only one UUID is that we can use it as a base and increment specific bytes within it and assign the services and characteristics accordingly. It also makes it easier to relate a specific characteristic to another one and to the parent service associated with this characteristic.
  • To better explain this, let’s start by generating a UUID:

    Figure 4: Generating a UUID via www.guidgenerator.com

     

  • Next, take that value and “zero out” the 3rd and 4th significant bytes (shown in bold):
    B3045432-E8DF-4E40-A8EC-5EB89C80E29D.
  • Now, this value will be the base UUID for our service and its characteristics:
    B3040000-E8DF-4E40-A8EC-5EB89C80E29D
  • This also makes it easier to work with within the nRF5 SDK, since it requires custom (also called vendor-specific) UUIDs to be added in two parts: a base and an offset.
    This will make a lot more sense when we go over the source code for adding the custom UUIDs.
  • So, given this base UUID, here are the UUID values we assign to each of the service and characteristics:
    • Simple service: B3040001-E8DF-4E40-A8EC-5EB89C80E29D
      • LED characteristic: B3040002-E8DF-4E40-A8EC-5EB89C80E29D
      • Button characteristic: B3040003-E8DF-4E40-A8EC-5EB89C80E29D
  • Now that we have the custom UUIDs assigned, it’s time to define the permissions for the different characteristics. The main aspects we’re concerned with are: Read, Write, Notifications, Indications.
    • The LED characteristic:
      • Write: enabled.
        We need to be able to write to this characteristic in order to turn on and off the LED.
      • Read: enabled.
        This is optional for us, but it would be nice to be able to read the status of the LED (on vs. off), so we will enable it.
      • Notifications: disabled.
        We do not need any notifications since, per the system operation, we will only be controlling the LED from the client side (Central side).
      • Indications: disabled.
        Not needed for the same reason we do not need Notifications.
    • The Button characteristic:
      • Write: disabled.
        It does not make sense for us to enable writes for this characteristic since the button changes status based on local physical contact at the server.
      • Read: enabled.
        We want to be able to read the status of the button (whether it’s pressed or released).
      • Notifications: enabled.
        We want to be notified at the Central side whenever the button is pressed and whenever it’s released.
      • Indications: disabled.
        Not needed in this case since we have Notifications enabled.
  • The next step is for us to implement the application in firmware.

Firmware Implementation

Full source code is available for download at the bottom of the post. Click here.

Note: Make sure you go through the software requirements and installation steps in the previous post: Prototyping BLE apps on the nRF52840 USB Dongle (Part A) before continuing with this tutorial.

We will have three main files in our application:

  • main.c
  • simple_service.h
  • simple_service.c

Let’s start with the implementation of the service, which we’ll call Simple Service.

Service and Characteristics


simple_service.h

Let’s go through each line of the header file for our Simple service.

First, the usual conditional #define  needed to avoid duplicate #include of the same header file.

Then we include the files needed:

The following source code is used to define a macro that can be used to instantiate the Simple service. This technique is implemented by other BLE services that are included in the nRF5 SDK. One of the benefits is that allows the application to instantiate the service and not have to worry about handling the BLE events manually (since the event handler gets assigned in the macro and the SoftDevice passes the BLE events back to this event handler).

The following defines the base UUID and the offset we mentioned earlier. We use the same base for the service and the two characteristics to make it easier to add the UUIDs to the SoftDevice database. Notice that the bytes within the base UUID need to be listed in reverse order since they are stored in little-endian format.
Little Endian means: the least significant byte of the data is placed at the byte with the lowest address, and so on. 

Next, we define the events that we want to report back to the application. These events are ones that we need to act upon at the application level. For example, to turn on/off an LED or record the occurrence of an event (such as in the case of notifications being enabled/disabled). It is good practice to leave these kinds of decisions and actions to happen at the application level. We want to have the service be as dumb as possible so that it can be reused by other applications without having to change the service’s implementation.

The following is a definition for a custom data structure that will hold an event tied to a specific connection (via the unique connection handle variable conn_handle ).

We use forward declarations such as the following to be able to reference a custom type before its implementation. In this case, the type ble_simple_service_t  is referenced by the upcoming event handler function before ble_simple_service_t  is fully defined.

The following is the prototype definition for the Simple service event handler function.

The following is the definition of the initialization data structure used by the application to initialize the Simple service. It contains a pointer to an event handler function that gets implemented at the application level to handle the custom events we defined in the enum  ble_simple_service_evt_type_t above, a value to hold the initial LED state, a value to hold the initial button state.

Here we define the main Simple service data structure that holds information such as the connection handle, the service handle, application event handler, UUID type, LED and Button characteristic handles.

The following is the function used by the application to initialize the Simple service. It takes in two arguments: a pointer to a Simple service object (which gets instantiated by calling the BLE_SIMPLE_SERVICE_DEF  macro) and a pointer to the initialization data structure.

The following is the event handler function that is referenced by the macro BLE_SIMPLE_SERVICE_DEF  and is the function that gets called by the SoftDevice to report any relevant BLE events.

Lastly, we define the function that is used to update the Button state characteristic value. This function also handles sending a notification to the Client if it has subscribed to Notifications.

We end the file by closing out the conditional #define  for the header file.


simple_service.c

Now, let’s go over the implementation of the Simple service in the source file simple_service.c.

First, we include the necessary header files, and most importantly simple_service.h.

Next, we define the strings for the two characteristics: the LED characteristic and the Button characteristic.

The following two functions handle assigning and clearing the connection handle depending on whether a connection was established or lost.

For the Write event, we need to handle two events:

  • A Write to CCCD of the Button characteristic, which indicates enabling or disabling Notifications.
  • A Write to the LED characteristic value, which causes the LED to turn on/off.

In both cases we need to report the event back to the application level by calling its event handler p_simple_service->evt_handler().

The following is the function used to add the LED characteristic to the Simple service.

There are a few important things happening in this function:

  • Setting the permissions on the Attribute (enabling Writes and Reads). Notice that both Notifications or Indications are not enabled.
  • Setting the User Descriptor to a human-readable string “LED State”.
  • Defining the UUID for the characteristic (using the base UUID and offset).
  • Assigning the initial value of the LED state.
  • Finally, adding the characteristic object to the database.

The following is the function used to add the Button characteristic to the Simple service.

There are a few important things happening in this function:

  • Setting the permissions on the Attribute (enabling Reads and disabling Writes).
  • Setting the CCCD permissions to enable both Write and Read permissions. Enabling Write permission on the CCCD enables Notifications or Indications. Specifically, only Notifications are then allowed via the char_md.char_props.notify = 1;  assignment.
  • Setting the User Descriptor to a human-readable string “Button State”.
  • Defining the UUID for the characteristic (using the base UUID and offset).
  • Assigning the initial value of the Button state.
  • Finally, adding the characteristic object to the database.

The following is the function that allows the application to initialize the Simple service and add it to the database.

The two most important operations in this function are:

  • Defining the UUID comprised of the base UUID and the offset.
  • Calling the functions to add both the LED and Button characteristics.

The following function is the event handler that gets called by the SoftDevice whenever a relevant BLE event needs to be reported to the Simple service.

The function handles the following events:

  • The Connection event.
  • The Disconnection event.
  • The Write event.

The following function handles sending a Notification with the Button state value to a Client that has subscribed to these Notifications.

The last function in this file handles updating the Button state characteristic value.

This gets called from the application level whenever the button gets pressed or released, but only if the Client (BLE Central) has enabled Notifications.

The two most important operations in this function are:

  • Updating the database with the new value that’s passed in.
  • Calling the function that handles sending Notifications and passing it the updated value.

Main Application


main.c

For the main.c source code, we will focus on the implementation specific to our project and leave out the parts that are common to BLE peripheral applications. Of course, the full source code is available for download and includes all the code and project files needed to build it and flash it yourself (you can download the full source code from here).

Now let’s go over the relevant source code.

First, we make sure to include the header file for the Simple service.

We modify the Device Name that’s used in the Advertisements to make it easier to find our device.

Next, we define the macro to be used for our external button which is connected to Pin 0.24.

The Button detection delay is needed by the App_Button module to determine when button presses occur.

Next, we define the macro for the two LEDs that we are using:

  • The internal LED which indicates connectivity (flashing when advertising/not-connected and solid when connected). This is the Green LED onboard the USB dongle.
  • The external LED which is controlled via the LED characteristic as part of our custom Simple service. This LED is connected to Pin 1.10.

The timer defined below is needed for flashing our internal LED (aka Connectivity LED).

Every time the timer gets triggered, we switch the state of the LED (on –> off and off –> on) causing the flashing effect.

This call instantiates the Simple service object.

We use a variable to hold the state of the Notifications (whether enabled or disabled). This helps us avoid calls to send Notifications when they are not enabled (subscribed to) by the Client.

The following two functions handle:

  • Initializing the Timer module and creating a timer to handle the Connectivity LED flashing operation.
  • The Connectivity LED timeout handler (to invert the LED state).

This is the event handler function that gets passed during the initialization of the Simple service.

The four events handled are:

  • Button characteristic value Notification enabled. In this case, we set the m_button_notification_enabled  variable.
  • Button characteristic value Notification disabled. In this case, we reset the m_button_notification_enabled  variable.
  • LED characteristic value written to turn On LED. In this case, we reset the GPIO assigned to the LED (LED active state is 0).
  • LED characteristic value written to turn Off LED. In this case, we set the GPIO assigned to the LED (LED active state is 0).

The following function is used to initialize the services in our application. We only have one service, the Simple service, so that’s the only one that gets initialized.

As part of the initialization, the event handler is assigned and the initial values for both the Button and LED states are set.

In the Advertising event handler function, we toggle the state of the Connectivity LED to start flashing when Advertising starts (by starting the corresponding timer).

In the main BLE event handler function, we handle a few events. Of importance are the Disconnected and Connected events.

  • Disconnected event: we clear the m_button_notification_enabled  variable.
  • Connected event: We stop the flashing timer, turn on the Connectivity LED, and clear the m_button_notification_enabled  variable.

Here we have the function responsible for initializing the LEDs.

We use the BSP module for the onboard LEDs and configure our external LED via the nrf_gpio APIs.

We also clear the external LED (turn it off) by setting the GPIO pin to 1 (since the LED is active state 0).

We also define a function for handling the Button events.

In the case where the event is related to the pin assigned to our external button, we check for the following:

  • If the connection handle is valid, the button was pressed/pushed, and Notifications are enabled, then we update the button characteristic value with a value of 0x01.
  • If the connection handle is valid, the button was released, and Notifications are enabled, then we update the button characteristic value with a value of 0x00.

The last function we want to look at is the function for initializing the buttons.

In this function, we create an array to hold the buttons we’re interested in initializing. In our case, we are only interested in initializing our external button.

In the initialization, we also pass in the delay that we defined at the top of the source file.

Finally, we call the a pp_button_enable()  API to enable the buttons.

Full source code available for download at the bottom of the post. Click here.

Building and Flashing the Firmware to the nRF52840 USB Dongle

We covered the complete steps to build and flash the firmware to the nRF52840 USB Dongle.

You can find the complete steps here: Building and Flashing the Firmware to the Dongle.

Testing the Application

Finally, we want to test the functionality of our application. We do so in the following video:

Summary

This concludes our implementation for a simple project using the Nordic Semiconductor nRF52840 USB Dongle.

In this series of tutorials, we went over a few important aspects including:

  • Hardware preparation and connecting components
  • Software requirements and installation
  • How to build the firmware and flash it to the nRF52840 USB Dongle
  • Designing our own custom GATT including one service and two characteristics
  • Implementing the GATT service and its characteristics
  • Implementing the code to handle the external button presses and releases
  • Implementing the code to turn on/off the external LED
  • Testing of the functionality using a mobile app such as nRF Connect

If you found this tutorial useful then please share with others and let me know your thoughts in the comment section below.

…and don’t forget to enter your email below to get access to the full source code and be notified when the next tutorial goes live!

Download Full Source Code

If you would like to download the code used in this tutorial, simply enter your email address in the form below. You’ll get a .zip file containing the complete source code, and I will also send you a FREE Report on the Essential Bluetooth Developer Tools. In addition, you will receive a weekly newsletter covering the top BLE news and get notified when the next tutorial gets published.

8 Comments

  1. Avatar Kamil Palčo on February 12, 2019 at 1:44 pm

    Thanks Mohammad.

    Very usefull tutorial I passed ever.

    • Mohammad Afaneh Mohammad Afaneh on February 12, 2019 at 1:45 pm

      Thanks, Kamil! Glad you liked it.

  2. Avatar Badis on March 12, 2019 at 9:20 am

    hi, what should I do with the three codes main.c / simpleservice.c and simple_service.h ?

  3. Avatar Rich Powell on April 9, 2019 at 12:29 pm

    Hi,

    Did you get round to a BLE central project based on the 840?

  4. Avatar Eric on May 27, 2019 at 4:37 pm

    Hi Mohammad,
    Build failed with Segger Embedded Studio for ARM 4.16.

    Building ‘nRF52 USB Dongle Example’ from solution ‘nRF52 USB Dongle Example’ in configuration ‘Release’
    Compiling ‘hardfault_implementation.c’
    Compiling ‘nrf_assert.c’
    Compiling ‘nrf_atfifo.c’
    Compiling ‘nrf_atflags.c’
    Compiling ‘fds.c’
    fds.c
    ‘FDS_VIRTUAL_PAGES_RESERVED’ undeclared (first use in this function); did you mean ‘FDS_VIRTUAL_PAGE_SIZE’?
    in expansion of macro ‘FDS_PHY_PAGES_RESERVED’
    each undeclared identifier is reported only once for each function it appears in
    in expansion of macro ‘FDS_PHY_PAGES_RESERVED’
    Compiling ‘nrf_atomic.c’
    Compiling ‘nrf_fprintf.c’
    Compiling ‘nrf_balloc.c’
    Build failed

    Could you help me, newbie with nRF52 (I need to test nRF52840 Dongle) and Segger ?

    Thanks,
    Regards.

  5. Avatar Oussema Kessentini on June 13, 2019 at 5:31 am

    Hi Mohammad,

    thanks for the tutorial very useful.

    there is tutorial for nRF52840-Dongle in BLE Mesh ?

    cause when i tried anf flash it all the led is on and the dongle is blocked

    thank you

  6. Avatar vicky jhonson on June 27, 2019 at 4:51 am

    hello,
    It’s a pleasure for all of us that a Guid generator tool has been found that we use it very much. I used those tools to use it.
    https://codebeautify.org/guid-generator

Leave a Comment