Bluetooth Low Energy (a.k.a Bluetooth LE, BTLE, Bluetooth Smart)

Getting started with Bluetooth Smart (Bluetooth low energy) on mbed

09 Nov 2014

This is a tutorial for people who are new to Bluetooth Smart (also known as Bluetooth low energy) and want to get started using the technology on mbed. As with everything new there are a few things that needs to be learned, but it is more fun to just get started. The theory will come later.

Build and run an application

A good application to start with is the BLE_HeartRate example, not too complicated, and it includes data transfer which is normally required for any application.

Program this example to your nRF51 mbed enabled kit by doing the following:

  1. Go to the BLE_HeartRate code page, then click Import this program.
  2. Click through the options and make sure the BLE_HeartRate project is selected. Check that you have selected a Bluetooth Smart enabled platform if you are working with several bed devices.
  3. Click Compile. A .hex file should download in your browser.
  4. Have your mbed kit connected; it should appear as a USB mass storage device. Drag or copy the file to this device. It programs and you are done on this side.

The easiest way to test the application is to use a phone app, Nordic Semiconductor has several examples for iOS and Android: nRFready Demo APPS. To connect with nRF Toolbox do the following:

  1. Start the nRF Toolbox app.
  2. Click the Heart/HRM icon.
  3. Click Connect.
  4. Look for Nordic_HRM (the default name) in the list. Touch it to select.
  5. Observe the data in the graph.

Congratulations! You have tested your first Bluetooth Smart device!

Walkthrough of the code

Let's see how this magic is achieved. The first thing to notice is the BLEDevice class, which encapsulates the Bluetooth low energy protocol stack. The following code shows how to use the BLEDevice object.

BLEDevice

#include "BLEDevice.h"
 
BLEDevice  ble;
 
void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
    ble.startAdvertising(); // restart advertising
}
 
int main(void)
{
    ble.init();
    ble.onDisconnection(disconnectionCallback);
 ...
    ble.startAdvertising();
 
    while (true) {
...
            ble.waitForEvent();
...
    }
}

The object is created in main.cpp and there is an init() method that must be called before using the object. the startAdvertising() method is called to advertise the device's presence allowing other devices to connect to it. With the onDisconnect method a function is added to restart advertising when the connection is lost. The waitForEvent() method should be called whenever the application is 'done' doing any work; it hands the control over to the protocol and lets you save power.

So when will waitForEvent() return? Basically whenever you have an application interrupt. In this example there is a Ticker object that calls a function every second. Whenever the ticker 'ticks' the periodicCallback is called, and then waitForEvent returns, resuming the execution in main.

Interrupt to trigger periodic actions

void periodicCallback(void)
{
    led1 = !led1; /* Do blinky on LED1 while we're waiting for BLE events */
 
    /* Note that the periodicCallback() executes in interrupt context, so it is safer to do
     * heavy-weight sensor polling from the main thread. */
    triggerSensorPolling = true;
}
 
int main(void)
{
    led1 = 1;
    Ticker ticker;
    ticker.attach(periodicCallback, 1);
...

Some more setup is needed, where one thing is the GATT Services. GATT, or the "Generic Attribute Profile" is the way data is organised in Bluetooth Smart. GATT services adopted by Bluetooth SIG can be found here. The services used in this example are already added to the BLE_API, and the code is found in the source files under BLE_API->services

Service setup

    /* Setup primary service. */
    uint8_t hrmCounter = 100;
    HeartRateService hrService(ble, hrmCounter, HeartRateService::LOCATION_FINGER);
 
    /* Setup auxiliary services. */
    BatteryService           battery(ble);
    DeviceInformationService deviceInfo(ble, "ARM", "Model1", "SN1", "hw-rev1", "fw-rev1", "soft-rev1");

The next thing is the advertiser payload. This is the data contained in the advertiser packets:

Advertiser setup

   ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_HEART_RATE_SENSOR);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.setAdvertisingInterval(1600); /* 1000ms; in multiples of 0.625ms. */

The first line is mandatory for Bluetooth Smart, and says that this device only supports Bluetooth low energy. The 'general discoverable' is the typical value to set when you want your device to be seen by other devices on order to connect. Next comes the ID for the heart rate sensor service and the name of the device.

After the payload is set the code sets the advertising type and the advertising interval. In Bluetooth Smart timing values are typically multiples of 625 us.

If you are new to Bluetooth Smart there are probably a lot of terms that are new to you. There is a lot of information about this on the internet, so how about doing some self studies? Try to answer the following:

  • What is GAP, GATT, and ATT?
  • What is a Bluetooth Smart peripheral?

Integrate a real sensor

The BLE_HeartRate example only gives a simulated example, so a next logical step is to use a real sensor. There are many choices here, and some might even have sample code on mbed.org. Otherwise you need to study the sensor and write your own code.

  • The Earclip Heart Rate Sensor is fairly simple and gives a pulse on a digital input pin every time it detects a heartbeat. Combine this with a timer to get the bp value.
  • Some sensors uses IR diode to emit light through your finger to a photosensor. Unfortunately the sensor picks up a lot more than just the light from the diode, so these are quite challenging to work with.

You may also use a completely different sensor, send the value as a heart rate measurement, and change the GATT service later.

Last advice is to not debug over the air as it takes long to connect, at least longer than connecting to a serial port. The following code sets up a serial port on the USB:

Serial pc(USBTX, USBRX);
pc.printf("Hello!\n");

For setup on the computer see the documentation.

06 Nov 2014

Still in progress, need a last part with suggestions on how to adapt the example to something else.

07 Nov 2014

This is brilliant! Can you please edit the above to also mention that the user may need to select the appropriate BLE target platform on their IDE (if needed as for platforms with single-chip solutions like Nordic). Eirik++!!

08 Nov 2014

What strikes me are the following three lines

while (true) {
            ble.waitForEvent();
    }

what if my application needs to do something else, e.g. read a sensors value at certain interval? Other blocking mbed APIs have a time-out parameter, but waitForEvent(void) does not. This makes me think that the typical design pattern should be to use a (background) Thread for BLE. If you agree, do you want to modify the example accordingly?

08 Nov 2014

waitForEvent() is functioning as a general purpose sleep() used typically in idle loops. It will be woken up by any system event; such as an interrupt. You can setup a ticker to go at regular intervals; and that will automatically cause a return from ble.waitForEvent(). The HeartRate example demonstrates this design pattern.

08 Nov 2014

Added the Ticker code to explain how to make waitForEvent() return, and mentioned that the correct platform must be set. Hopefully a bit better now.

I also suggested some self studies on Bluetooth Smart for better understanding. I think this is the best approach since learning where to find things is an important thing when working with Bluetooth any technology. I would appreciate some suggestions on other questions here; maybe someone discovered something particular that made things so much clearer? Please share :)

09 Nov 2014

@Guido: Check if it makes more sense now.

Now there is something that strikes me:

Guido Grassel wrote:

This makes me think that the typical design pattern should be to use a (background) Thread for BLE.

I'm not sure if you were really talking about OS thread, or just used to word to explain the idea, but at the moment the closest thing there is to a thread is a function running in interrupt context. (The mbed OS might include threads, but that is not out yet.) Anyway, for developers without experience on micro-controller development this might be something that merits a proper explanation. In particular I think it is important to understand this when it comes to power management.

I tried to see if there were some tutorials already, but couldn't find it on mbed. Perhaps Rohit knows?

10 Nov 2014

For BLE demos, Eirik is correct in pointing out that "at the moment the closest thing there is to a thread is a function running in interrupt context." Mbed-OS, however, will support muti-threading.

With mbed-OS, it will be possible to split applications into multiple tasks; for polling and other activities. Tasks for polling will wait on some message queue and be woken up by messages. The idle-task will be responsible for calling waitForEvent, but the current ble.waitForEvent() API is going to be replaced either with something like the following sequence:

    ble.beforeWFE();
    __WFE();
    ble.afterWFE();

or there could be a separate task responsible for executing ble.housekeeping() every time the system wakes up from WFE().

The above is because applications involving BLE can't be expected to revolve entirely around BLE. There could be other peripherals needing housekeeping before or after sleep. The idle task would then simply loop like the following:

    while (true) {
        WFE();
        ble.housekeeping();
        gps.housekeeping();
    }

That's my current vision. Comments are welcome.

11 Nov 2014

Eirik Midttun wrote:

@Guido: Check if it makes more sense now.

Yes, thank you, the discussion here is really good. I do agree as we see more complex applications, or even multi-purpose applications, it would be nice to divide things up into separate modules / classes. Serving everything from a single main loop can cause problems when trying to structure an application this way.

24 Nov 2014

Should not it be: ticker.attach(&periodicCallback, 1);

By the way, thanks a lot for the article!

24 Nov 2014

@A.Dem: I think you are right, and it is consistent with the API doc for Ticker. However, the code is copy-pasted from the BLE_HeartRate example and works as it is. Not a 100% sure, but it could be that periodCallback is anyway a reference and the & is therefore not needed.

24 Nov 2014

I personally prefer the '&', since it explicitly tells it to get a pointer to the function. But it is not required, it will take the pointer also if it is not included.

24 Nov 2014

The function name works as a synonym for its pointer.

27 Nov 2014

Hi,

I hope that this is the proper place for this question. I have the nRF51-DK board; I have installed the nRF51-DK and nRF51-DK FOTA compilers;

When making a custom BLE service, how do I get the names to show up in the nRF Master Control Panel? For example, in the DfuTarg, where does the text 'Device Firmware Update Service', 'DFU Packet', and 'DFU Control Point' come from? In the DefaultApp, where does the text 'Nordic UART Service', 'TX Characteristic' and 'RX Characteristic' come from?

Thanks,

Mordechai

28 Nov 2014

Quote:

In the DfuTarg, where does the text 'Device Firmware Update Service', 'DFU Packet', and 'DFU Control Point' come from?

Nordic's Master Control Panel app translates the UUIDs for these services/characteristics into meaningful names. If a Service/Characteristic uses one of the well-defined IDs, then it is easy to translate into names. Nordic's MCP happesn to know about these UUIDs because these services have been introduced by them.

App can configure a LOCAL_NAME to be sent out in advertisements:

    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));

In some cases, the GAP service is setup with a name for the device (Note: there is a service by the name of GAP; needs to be present in all GATT server databases). For the Nordic stack, the following code is used to setup a name in the GAP service (found in nRF51822/btle/btle_gap.cpp)

sd_ble_gap_device_name_set(&sec_mode, (const uint8_t *) CFG_GAP_LOCAL_NAME, strlen(CFG_GAP_LOCAL_NAME));
28 Nov 2014

Hi Rohit,

I was referring to the service names and to the characteristic names. How to specify these for custom services and characteristics?

Thanks,

Mordechai

28 Nov 2014

There is no standard way to specify names for cusom services or characterisitcs. Their UUIDs are supposed to get resolved into names either because they are well known UUIDs or because the phone apps are aware of them.

28 Nov 2014

OK, in that case how would I add the 'Characteristic User Description (UUID 0x2901)' to the characteristics? Thanks,

Mordechai

28 Nov 2014

The constructor for GattCharacteristic() takes an array of GattAttribtues as an optional argument. You can populate your User-Description into a GattAttribute and pass it along to the Characteristic.

28 Nov 2014

This works great! Learned a bunch including mbed USB serial for debug. Thank you!

FYI: Works fine on Android 5.0.

08 Jan 2015

Hi,

Is there a way to determine when the user has subscribed to notifications, i.e. client characteristic configuration 0x2902? Also, when reading a characteristic value, is it possible to have a callback function that can retrieve the value, as opposed to having to continuously call updateCharacteristicValue()? For example, if I want to read an analog value from the ADC at the moment the user requests it?

Thanks!

08 Jan 2015

a) Take a look at: - ble.onUpdatesEnabled(Callback_BLE_onUpdatesEnabled);PR: Occurs when host enables notify on a Characteristic that has Notify property set - ble.onUpdatesDisabled(Callback_BLE_onUpdatesDisabled); Reverse?

b) I didn't find an ble.onCharacteristicRead() feature, maybe there is an issue with response time? You could create the same effect by having a pair of operations: - ble.onConnection() to trigger a Notification (Possibly after a short timer period to let connection complete) - ble.onDataWritten() can carry set of bits for requested data options, then send Notification with that data when ready. i.e. both onConnection and onWrite can be used as onDemand to trigger Notifications containing desired Read data, so sensors aren't continuously read.

-----

c) FYI: This project has a bunch of the BLE callback functions enabled, you can see when they occur in the USB Serial Debug: - http://developer.mbed.org/users/prussell/code/bleIOv04_pr/ - You can use this with Nordic's Android App "Master Control Panel", iPhone App LightBlue, or the included matching Android project. - It includes BLE Custom Read/Notify/Write (Implemented as structures, not single items). - Feedback on this mbed project and matching app appreciated.

08 Jan 2015

Query: Where to start with pairing/bonding?

09 Jan 2015

Hi,

The Nordic SDK comes with a module called 'bond manager'. This needs to be enabled before pairing and bonding become available. I will be having a discussion with Nordic regarding this in the coming weeks; and will update.

09 Jan 2015

@Mordechai Brodt: There's a new API called setReadAuthorizationCallback() which can be used to authorize reads. This callback will get triggered *before* reading out a value to be returned to the client. You could poll your ADC in the callback.

09 Jan 2015

Rohit, Thank you for both the bonding and setReadAuthorizationCallback(). I will dig into these.

14 Apr 2016

Again I'm jumping on an old post, @Rohit, you are indeed the man!!

Do you have any examples for how to use setReadAuthorizationCallback()? How do I set the callback, and more importantly, how do I pass allow/disallow back to the stack?

For various reasons I can't use the passkey security in my device but it needs high security, so we're implementing an additional authorisation process whereby user has to write the correct key to a characteristic before they can read/write any other chars.

I want to set a flag when correct key is written, and use the read authorisation callback to check the flag before allow/disallow read to continue.

Andrew

14 Apr 2016

Does this also work for the DFU service? Can I set the readAuthorisation for DFU so unauthorised user can't bypass security by simply reflashing device?

15 Apr 2016

Ok so I've tried the following, but it doesn't seem to block read access as expected,

// inside constructor
myCharacterisitic.requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); 
myCharacterisitic.setReadAuthorizationCallback(this, &LockFunctionService::onReadRequest);

...

protected:
// testing read authorisation
    virtual void onReadRequest(GattReadAuthCallbackParams *params) {        
        params->authorizationReply = AUTH_CALLBACK_REPLY_ATTERR_READ_NOT_PERMITTED;
    }

Once the link is paired/encrypted (I'm using Nordic sniffer dongle and Master Control Panel for testing) I can read/write to the chars without restriction, including the "myCharacteristic" which should be always unauthorised.

Am I using the API correctly?

I'll also post this as separate question so as not to drag this thread out.

Regards Andrew

16 Jul 2016

Hi, sorry how I can connect to BLE board to each other and send or receive by UARTservice? I want to do the Android App job by another BLE board.