bluetooth-lowenergyaccelerometermbednrf51

BLE Accelerometer


I want to send accelerometer values over BLE to an iOS app using a nordic nRF52. The app works perfectly with standard BLE services (HeartRate Measurement, Thermometer etc), but not when I try and define a custom BLE Accelerometer Service. Is there anything I need to do specifically when defining UUID's and things? Any help would be much appreciated, thanks.

Below is my custom Accelerometer class, and the main.cpp uploaded to the nRF52 below that.

#ifndef __BLE_ACCELEROMETER_SERVICE__
#define __BLE_ACCELEROMETER_SERVICE__

#include "ble/BLE.h"

#define UUID_ACCELEROMETER_SERVICE  "00000000-0000-1000-7450-BE2E44B06B00"

#define UUID_X_CHARACTERISTIC       "00000000-0000-1000-7450-BE2E44B06B01"
#define UUID_Y_CHARACTERISTIC       "00000000-0000-1000-7450-BE2E44B06B02"
#define UUID_Z_CHARACTERISTIC       "00000000-0000-1000-7450-BE2E44B06B03"

/**
 * @class AccelerometerService
 * @brief BLE Custom Accelerometer Service. This provides the x, y and z values of the SEEED 101020051 Grove accelerometer connected to the Nordic nRF52 DK.
 */

class AccelerometerService
{
public:

    /**
     *  @brief Add the Accelerometer Service to an existing BLE object, initialize with values for x, y and z readings, represented as doubles.
     *  @param _ble Reference to the BLE device
     *  @param _x   Initial value for the x axis
     *  @param _y   Initial value for the y axis
     *  @param _z   Initial value for the z axis
     */
    AccelerometerService(BLE &_ble, double _x = 0, double _y = 0, double _z = 0) :
    ble(_ble),
    x(_x),
    y(_y),
    z(_z),
    xAngleCharacteristic(UUID_X_CHARACTERISTIC, &x, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY),
    yAngleCharacteristic(UUID_Y_CHARACTERISTIC, &y, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY),
    zAngleCharacteristic(UUID_Z_CHARACTERISTIC, &z, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY) {

        GattCharacteristic  *readings[] = {&xAngleCharacteristic, &yAngleCharacteristic, &zAngleCharacteristic, };
        GattService         accelerometerService(UUID_ACCELEROMETER_SERVICE, readings, sizeof(readings) / sizeof(GattCharacteristic *));

        ble.addService(accelerometerService);
    }

    /**
     * @brief Update the x axis rotation with a new value.
     * @param _x - New x value from accelerometer
     */
    void update_x(uint8_t _x) {
        x = _x;
        ble.gattServer().write(xAngleCharacteristic.getValueHandle(), &x, 1);
    }

    /**
     * @brief Update the y axis rotation with a new value.
     * @param _z - New y value from accelerometer
     */
    void update_y(uint8_t _y) {
        y = _y;
        ble.gattServer().write(yAngleCharacteristic.getValueHandle(), &y, 1);
    }

    /**
     * @brief Update the z axis rotation with a new value.
     * @param _z - New z value from accelerometer
     */
    void update_z(uint8_t _z) {
        z = _z;
        ble.gattServer().write(zAngleCharacteristic.getValueHandle(), &z, 1);
    }


protected:

    /**
     * A reference to the underlying BLE instance that this object is attached to.
     * The services and characteristics will be registered in this BLE instance.
     */
    BLE &ble;

    /**
     * The current x axis rotation, represented as a double
     */
    uint8_t x;
    /**
     * The current y axis rotation, represented as a double
     */
    uint8_t y;
    /**
     * The current z axis rotation, represented as a double
     */
    uint8_t z;

    /**
     * A ReadOnlyGattCharacteristic that allows access to the peer device to the
     * x axis rotation value through BLE.
     */
    ReadOnlyGattCharacteristic<uint8_t>   xAngleCharacteristic;
    /**
     * A ReadOnlyGattCharacteristic that allows access to the peer device to the
     * y axis rotation value through BLE.
     */
    ReadOnlyGattCharacteristic<uint8_t>   yAngleCharacteristic;
    /**
     * A ReadOnlyGattCharacteristic that allows access to the peer device to the
     * z axis rotation value through BLE.
     */
    ReadOnlyGattCharacteristic<uint8_t>   zAngleCharacteristic;
};


#endif /* __BLE_ACCELEROMETER_SERVICE__ */

Below is the main.cpp file I am using via mbed.org.

#include "mbed.h"
#include "ble/BLE.h"
#include "AccelerometerService.h"

DigitalOut led1(LED1);
DigitalOut led2(LED2);

static AccelerometerService *accelerometerServicePtr;

// Function declarations
void bleInitComplete(BLE::InitializationCompleteCallbackContext *);
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *);

// Set device name and inital setup options
static const char       DEVICE_NAME[]        = "nRF52";
static const uint16_t   uuid16_list[]        = {0xFFFF};
static volatile bool    triggerSensorPolling = false;

static float           x = 10.0;    // Dummy values for accelerometer for now
static float           y = 15.0;
static float           z = 18.0;

/*
 *  Initialization callback
 */
void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
    BLE &ble = params->ble;
    ble_error_t error = params->error;

    if (error != BLE_ERROR_NONE){
        printf("*** Error occured ***\n");
        return;
    }

    /* Ensure that it is the default instance of BLE */
    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
        return;
    }

    ble.gap().onDisconnection(disconnectionCallback);

    // Setup primary service
    accelerometerServicePtr = new AccelerometerService(ble, x, y, z);

    // Setup advertising
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);

    // Advertising payload has a maximum of 31 bytes
    // BLE only, no classic BT
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED |
                                           GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    // Add name
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    // UUIDs broadcast in advertising packet
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    // Set advertising interval
    ble.gap().setAdvertisingInterval(100); //100ms

    // Start advertising
    ble.gap().startAdvertising();
}

/**
 * Restart advertising on disconnection
 */
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *)
{
    BLE::Instance(BLE::DEFAULT_INSTANCE).gap().startAdvertising();
}

/**
 * This function is called when the ble initialization process has failed
 */
void onBleInitError(BLE &ble, ble_error_t error)
{
    /* Avoid compiler warnings */
    (void) ble;
    (void) error;
    /* Initialization error handling should go here */
}

int main()
{
    // Initialize program
    printf("\n\r *** Starting Main Loop *** \r\n");

    BLE &ble = BLE::Instance(BLE::DEFAULT_INSTANCE);
    ble.init(bleInitComplete);

    while (ble.hasInitialized() == false)
    {
        while (true)
        {
            if (triggerSensorPolling && ble.gap().getState().connected) {
                triggerSensorPolling = false;

                accelerometerServicePtr->update_x(x);
                accelerometerServicePtr->update_y(y);
                accelerometerServicePtr->update_z(z);
            } 
            else {
                ble.waitForEvent();   // Infinite loop waiting for BLE interrupt events
            }
        }
    }
}

Solution

  • This is wrong, you are sending a bad advertising packet. (0xFFFF == Insert 16 bit service here)

    ...
    uuid16_list[]        = {0xFFFF};
    ...
    ...
    COMPLETE_LIST_128BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list)
    

    There are for Bluetooth reserved 16 bit identifiers, which use the reserved UUID space.

    Check This page: What range of Bluetooth UUIDs can be used for vendor defined profiles?

    What you need to do is specify the full UUID in the 128 bit list.

    I cant compile this but try something like this

    char 128bitlist[] = {,0x00,0x00,0x00,0x00 ,0x00,0x00 ,0x10,0x00 ,0x74,0x50 ,0xBE,0x2E,0x44,0xB0,0x6B,0x00};
    ...
    ...
    ble.gap().accumulateAdvertisingPayload (GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, (uint8_t *) 128bitlist, 1);
    

    An outstanding tool for checking your advertising data is Lightblue. It is free and really informative. You should check the advertising on both Android and IOS with this tool.

    Another thing to check is that you dont overfill the advertising packet. If your device name is too long, plus the 128 bit UUID, you can overfill and corrupt the packet. Try removing the name or making it really short.