How to turn a Nordic Semiconductor nRF51-DK into a discoverable beacon using mbed

Aaron ArdiriBlogs, Tutorials

Tweet about this on TwitterShare on FacebookShare on LinkedInShare on Google+

nRF51-DKThe Nordic Semiconductor nRF51-DK, an mbed enabled micro-controller is getting a lot of attention from us lately – we first showed you how to get started with mbed and how to connect an Arduino Ethernet Shield to give the device internet connectivity. It was only a matter of time before we started to explore the Bluetooth Smart (BLE) capabilities of mbed and actually write an application to interact with it.

Hardware and Software checklist

In order to walk through this tutorial on your own, you’ll need at least the following equipment:

  • Computer (Windows, Mac OSX, Linux)
  • Nordic Semiconductor nRF51-DK
  • a copy of Evothings Studio installed on your computer
  • a mobile phone or device running Evothings Client
  • microUSB communications cable
  • limited skill set with a command line prompt for testing/debugging

This tutorial will consist of two parts. First of all, we will explain a few details about BLE on mbed and writing an mbed application for the nRF51-DK. In the second part, we will launch an application using Evothings Studio that will allow two-way communication with the nRF51-DK from a mobile device.

The full source code for this tutorial is available for download (nRF51-dk-ble.zip).

The primary focus for this tutorial will be on explaining how BLE works within mbed and get an example running on the nRF51-DK device. While a mobile application is provided in the source package – we will not be going into the development specifics and simply use the it as-is.

nRF51-DK: firmware and mbed development

Creating a foundation for our mbed OS application

Much like we did in the previous blog post, we need to create ourselves a foundation to work from.

The simplest starting point that does not bring in too many dependencies is to find a hello world type application – simply press the “Import” button and search for anything of this nature, you should be presented with a list similar to what is shown below. A number of results will be suitable – we only need to have the structure and we’ll be replacing the main.cpp file with our own shortly anyhow.

nrf41-dk-ethernet-import-program

We used the most popular example – clicking on the big “Import” button we are presented with:

nrf41-dk-bledevice-import-program-helloworld

This is where we can specify the name of our application (mbed-nordic_bledevice) and import it as a program. It is very important to select the checkbox where it asks to update the libraries to the latest revision – failure to do so could result in compiler errors for no reason.

Integrating an library to support the Bluetooth Smart (BLE) chipset

We will be using the high level abstraction Bluetooth Low Energy library provided within the mbed development environment and will also need to pair it with the nRF51822 bluetooth stack and driver for the nRF51-DK hardware.

Use the “Import” button like earlier and search for the “BLE_API” library.

nrf41-dk-bledevice-import-library-1

It should be prominently displayed – clicking on the big “Import” button we are presented with:

nrf41-dk-bledevice-import-library-ble_api

This is where we can select which program to import it into (mbed-nordic_bledevice) and import it as a library. It is very important to select the checkbox where it asks to update the sub-libraries to the latest revision again – failure to do so could result in compiler errors for no reason.

Use the “Import” button again and search for the “nRF51822” library from Nordic Semiconductor.

nrf41-dk-bledevice-import-library-2

It should be prominently displayed – clicking on the big “Import” button we are presented with:

nrf41-dk-bledevice-import-library-nrf51822

This is where we can select which program to import it into (mbed-nordic_bledevice) and import it as a library. It is very important to select the checkbox where it asks to update the sub-libraries to the latest revision again – failure to do so could result in compiler errors for no reason.

Nordic Semiconductor also has put together a simple beacon-like library called Nordic Pucks, however in this tutorial we wanted to keep it as simple as possible and focus on the bare basics. The library was only recently updated and we were having some small issues with it – so we simplified things by doing barebones BLE.

Creating your first BLE device in mbed

If you are not up-to-date with how Bluetooth Smart (BLE) works, then it would definitely pay to do a little bit of reading up on the topic. We posted an introduction to BLE a little while ago and it is a great pre-cursor to this tutorial as it covers everything we need to know.

Great – lets get our hands dirty!

To get us started; we’ll need to define a name for the device and a service and a number of characteristics that we will need to expose on the device.

// name of the device 
const static char DEVICE_NAME[] = "nRF51-DK";

// GATT service and characteristic UUIDs
const UUID nRF51_GATT_SERVICE     = UUID((uint8_t *)"nRF51-DK        ");
const UUID nRF51_GATT_CHAR_BUTTON = UUID((uint8_t *)"nRF51-DK button ");
const UUID nRF51_GATT_CHAR_LED    = UUID((uint8_t *)"nRF51-DK led    ");

The BLE library provides a great little class called UUID that takes an array of 16 bytes and converts them into UUID objects we can use. It is nice we can define whatever we like here – the resulting human readable UUID’s created are:

nRF51_GATT_SERVICE:     6e524635-312d-444b-2020-202020202020
nRF51_GATT_CHAR_BUTTON: 6e524635-312d-444b-2062-7574746f6e20
nRF51_GATT_CHAR_LED:    6e524635-312d-444b-206c-656420202020

We will need to know these for when we create our mobile application a little later on. You could use any string – just as long as they are unique. Effectively the class simply takes the 16 byte values and concatenates them to create a 128bit UUID.

Our goal is to create a single service, with two characteristics – one of them to show the state of the buttons on the device and the other to control the four LEDs present on the board. It gives us the perfect use case to cover both reading a characteristic and writing a a value to a characteristic.

We do need some global variables for this task however:

#define CHARACTERISTIC_BUTTON 0
#define CHARACTERISTIC_LED    1
#define CHARACTERISTIC_COUNT  2

// our bluetooth smart objects
BLEDevice           ble;
GattService        *gatt_service;
GattCharacteristic *gatt_characteristics[CHARACTERISTIC_COUNT];
uint8_t             gatt_char_value[CHARACTERISTIC_COUNT];

We have defined an object for the BLE device itself, a GattService reference and an array of GattCharacteristic references – it has been done like this for simplicity later (which you will see). In this tutorial we are only going to be dealing with characteristics that are single byte – however, you are able to use more if you need. The data will be stored within the array gatt_char_value.

To create these objects, we simply perform the following:

    // create our button characteristic (read, notify)
    gatt_characteristics[CHARACTERISTIC_BUTTON] = 
      new GattCharacteristic(
            nRF51_GATT_CHAR_BUTTON, 
            &gatt_char_value[CHARACTERISTIC_BUTTON], 1, 1,
            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | 
            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);

    // create our LED characteristic (read, write)
    gatt_characteristics[CHARACTERISTIC_LED] = 
      new GattCharacteristic(
            nRF51_GATT_CHAR_LED, 
            &gatt_char_value[CHARACTERISTIC_LED], 1, 1,
            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | 
            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | 
            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);

    // create our service, with both characteristics
    gatt_service = 
      new GattService(nRF51_GATT_SERVICE, 
                      gatt_characteristics, CHARACTERISTIC_COUNT);

The characteristic nRF51_GATT_CHAR_BUTTON is one byte long and has both read and notify properties associated with it. This allows anyone to read the value and also subscribe for notifications when the value is changed. The characteristic nRF51_GATT_CHAR_LED is also one byte long and has read and write properties associated with it – this allows anyone read the value and change it.

Finally we create the service using a reference to the previously defined characteristics.

The next step is to create our BLEDevice object instance and do the necessary house-keeping so the device correctly advertises itself and is connectable by name.

    // initialize our ble device
    ble.init();
    ble.setDeviceName((uint8_t *)DEVICE_NAME);

    // configure our advertising type, payload and interval
    ble.accumulateAdvertisingPayload(
          GapAdvertisingData::BREDR_NOT_SUPPORTED | 
          GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.accumulateAdvertisingPayload(
          GapAdvertisingData::COMPLETE_LOCAL_NAME, 
          (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.setAdvertisingInterval(160); // 100ms

    // configure our callbacks
    ble.onDisconnection(onDisconnection);
    ble.onConnection(onConnection);
    ble.onDataWritten(onDataWritten);

    // add our gatt service with two characteristics
    ble.addService(*gatt_service);

    // start advertising
    ble.startAdvertising();

    // let the device do its thing
    for (;;)
    {
      ble.waitForEvent();
    }

This is the bare bones setup for the most simplest of BLE devices – it is however important that we have some callbacks defined to handle connection, disconnection and have our application be notified when data is read or written to the device. It is important to start advertising again when a device disconnects – otherwise it wont be visible.

The final step is to put the device in an infinite loop and let the BLE subsystem deal with handling the various events. It is possible to do other things within this loop – it is only important to periodically call waitForEvent() to allow the device to do its thing.

Defining hooks for monitoring button states and toggling the LEDs

To monitor the button states; we needcall to utilize a Ticker object once setup periodically calls a function that does all the leg work and changes the characteristic to reflect the button state.

    // start monitoring the buttons and posting new values
    Ticker ticker;
    ticker.attach(monitorButtons, 0.1);  // every 10th of a second

We can place this just before the infinite loop, but not before we start advertising the device as we’ll need to have the device active when the callback is called.

// define our digital in values we will be using for the characteristic
DigitalIn button1(P0_17);
DigitalIn button2(P0_18);
DigitalIn button3(P0_19);
DigitalIn button4(P0_20);

uint8_t button_new_value = 0;
uint8_t button_old_value = button_new_value;

void monitorButtons() 
{
    // read in the buttons, mapped into nibble (0000 = all off, 1111 = all on)
    button_new_value = 0;
    button_new_value |= (button1.read() != 1); button_new_value <<= 1;
    button_new_value |= (button2.read() != 1); button_new_value <<= 1;
    button_new_value |= (button3.read() != 1); button_new_value <<= 1;
    button_new_value |= (button4.read() != 1); 
      
    // set the updated value of the characteristic if data has changed
    if (button_new_value != button_old_value)
    {
        ble.updateCharacteristicValue(
              gatt_characteristics[CHARACTERISTIC_BUTTON] -> getValueHandle(),
              &button_new_value, sizeof(button_new_value));
        button_old_value = button_new_value;
    }
}

The buttons are represented by the digital pins P0_17, P0_18, P0_19 and P0_20 – we simply configure some DigitalIn objects to interface with them and read their values and create an integer from the various states. When none of the buttons are pressed, this value will be zero (0000 in binary) and when all of them are pressed it will be fifteen (1111 in binary) – a total of sixteen possibilities depending on which buttons are pressed simultaneously.

Publishing the value over BLE only needs to be done when it changes from its current value and it simply involves invoking the updateCharacteristicValue function using the characteristic handle, providing the new value and its size in bytes.

The LED handling is not much different:

DigitalOut led1(P0_21);
DigitalOut led2(P0_22);
DigitalOut led3(P0_23);
DigitalOut led4(P0_24);

uint8_t led_value = 0;

void onLedDataWritten(const uint8_t* value, uint8_t length) 
{
    // we only care about a single byte
    led_value = value[0];

    // depending on the value coming through; set/unset LED's
    if ((led_value & 0x01) != 0) led1.write(0); else led1.write(1);
    if ((led_value & 0x02) != 0) led2.write(0); else led2.write(1);
    if ((led_value & 0x04) != 0) led3.write(0); else led3.write(1);
    if ((led_value & 0x08) != 0) led4.write(0); else led4.write(1);
}

In this case; we are using the DigitalOut object and the digital pings P0_21, P0_22, P0_23 and P0_24. Depending on the individual bits that are set or not in the value, we can determine the state the LED needs to be (ON or OFF).

The question your asking now is: “When does the onLedDataWritten callback function get called?”

We mentioned earlier the notion of creating some callback functions that occur when particular events occur on the device – one of them was to respond when data has been written to the device:

void onDataWritten(const GattCharacteristicWriteCBParams *context) 
{
  // was the characteristic being written to nRF51_GATT_CHAR_LED? 
  if (context -> charHandle == 
      gatt_characteristics[CHARACTERISTIC_LED] -> getValueHandle())
  {
    onLedDataWritten(context -> data, context -> len);
  } 
}

We simply need to check the context of the event and determine if the characteristic that was modified was in fact the one represented by nRF51_GATT_CHAR_LED. If it is; then the onLedDataWritten function is called with the data and length of the information provided.

Compiling and flashing the program

Within the compiler view on the mbed.org developer site select the target platform – in this case it will be the platform named “Nordic nRF51-DK” and then press the “Compile” button within the web based IDE. If everything goes ok; it should offer you the following file for download:

mbed-nordic_bledevice_NRF51_DK.hex

Simply connect the nRF51-DK to the computer and copy the hex file to the external storage medium that is presented. We covered the deployment process in our previous post with the nRF51-DK. Now that the application has been flashed to the nRF51-DK it is time to see it in action!

Verification and making sure it is working!

Ensure that the nRF51-DK is freshly connected to the USB port of your computer; the easiest way to make sure is to disconnected it and reconnected it so that we do not run into any conflicts with the device being busy. After a few seconds, our application should be executing.

The easiest way to find out if the device has established a network connection is to look at the debugging logs our program has written to the console – to do this, we must establish a connection to the USB port using a serial program; such as screen within a Terminal session.

We should see something like the following:

$ screen /dev/tty.usbmodem1411

>> nRF51-DK start
>> initialized device 'nRF51-DK'
>> configured advertising type, payload and interval
>> registered for callbacks
>> added GATT service with two characteristics
 : 6e524635-312d-444b-2020-202020202020
  :: 6e524635-312d-444b-2062-7574746f6e20
  :: 6e524635-312d-444b-206c-656420202020
>> device advertising
>> monitoring button state
>> waiting for events 

Your nRF51-DK may have a different name to /dev/tty.usbmodem1411 however it will be very similar to this; if you are on Mac OSX or Linux they have this type of name, on Windows of course this would be a COMxx port and you could also use a program like HyperTerminal (instead of screen) to establish communication with the board.

The nRF51-DK should now be ready for use – to validate it is discoverable and connectable, you can use any BLE discovery application on compatible hardware – we even have an example that does just this.

nRF51-DK: mobile application in Evothings Studio

Install Evothings Studio

The Evothings Studio Starter Kit explains how to install and use Evothings Studio. For the quick version just download the Studio package for your computer and while it’s loading you pick up the Evothings Client free from the app stores.

Deploy the nRF51-DK Evothings application

Locate the index.html file within the source code bundle you downloaded earlier and drag it into the Evothings Workbench window. The program Nordic Semiconductor nRF51-DK should appear in the list of applications available from the workbench.

Start the Evothings Client application on your mobile device and scan and connect to the workbench. Once connected you can then press the RUN button to deploy the application on your mobile device.

nRF51-dk-ble

At this point you can connect to the nRF51-DK device, turn on and off each of the LEDs and press the hardware buttons to your hearts content and see the values being reflected on the mobile device. There is also some logging on the console when a button is pressed or the LED characteristic is written to.

We have kept the coverage of the mobile application development to a minimum – the focus was to figure out BLE on mbed with the nRF51-DK – you can take a look at a few examples we have done previously. You are more than willing to poke around the source code and see how it is put together.

If you have any questions, do not hesitate to post them in our forum.

Happy tinkering!