Passing float values with Bluetooth

Gentlemen:

I'm having a terrible time getting two Nano boards to pass float values via Bluetooth. One is a Nano BLE 33 and the other is a Nano BLE 33 Sense. I'm trying to pass a floating-point number from one board to the other. Eventually, I will want to send six float numbers...I'm just testing one for now.

I have based my code on the LED/LedControl Arduino example, but the example is confusing. The peripheral example program declares the UUID for the service as

BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214");

Then it declares the characteristic as

BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);

For some reason the example service and characteristic have the same UUID. I declared mine with separate UUID's since I eventually want to send six values (they can't all be the same as the service). Mine are

BLEService DisplayData("cba174a9-aed5-4c2f-8ea9-98de50b2bb38");
BLEFloatCharacteristic RADIO_epitch("4007f5d2-6d38-4249-b4b2-c48062eaf30a", BLERead | BLEWrite);

My peripheral board seems to function OK. I can connect to it with my phone using "nRF Connect", which detects the board, is able to connect, and shows both the service UUID and the characteristic UUID correctly. Opening the characteristic, I see that it has a value (in hex). I am also able to disconnect with the phone.

Now the central. Just like the example, I have

BLE.begin();
BLE.scanForUuid("cba174a9-aed5-4c2f-8ea9-98de50b2bb38"); //(the service UUID)

in setup(). Then in the loop() I have

BLEDevice peripheral = BLE.available();

I also have all the serial debug from the example to trace it. After stopping the scan

BLE.stopScan();

I call a function that tries to get the number. There I have

BLECharacteristic RADIO_epitch = peripheral.characteristic("4007f5d2-6d38-4249-b4b2-c48062eaf30a");

Where RADIO_epitch is the name of the characteristic (I used the same name as in the peripheral) along with its UUID. The program is able to connect, able to discover attributes, but then fails to detect a characteristic, which I test for with

if(!RADIO_epitch){...}

Just like in the example program. At that point the program just keeps trying and failing. So apparently the characteristic has a value of zero.

I have a feeling that it has something to do with the BLECharacteristic declaration. There doesn't appear to be anything that let's the central know that the value is a float. I have tried using BLEFloatCharacteristic, both in the above delaration and at the top of the program in the form

BLEFloatCharacteristic RADIO_epitch("4007f5d2-6d38-4249-b4b2-c48062eaf30a", BLERead | BLEWrite);

in various combinations, but I get a nightmare of type mismatch errors. The example program doesn't even have the above line at all. I don't know how it determines the data type.

I'm hoping I have given you enough information and that it is something simple and stupid that I'm doing wrong. I would appreciate any help that you guys can offer.

Don

Your issue comes from the fact that the BLECharacteristic class does not have a readValue and writeValue function for float (see BLECharacteristic.h in the BLE library). You can get around that by using both of these functions with the address and size of a float variable.

Here is an example for the peripheral. I run this from a USB battery pack.

/*
  This example creates a BLE peripheral with a service that contains two float characteristics.

  The circuit:
  - Arduino Nano 33 BLE Sense board.

  You can use a generic BLE central app, like LightBlue (iOS and Android) or
  nRF Connect (Android), to interact with the services and characteristics
  created in this sketch.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------

#define BLE_UUID_TEST_SERVICE               "9A48ECBA-2E92-082F-C079-9E75AAE428B1"
#define BLE_UUID_FLOAT_VALUE                "C8F88594-2217-0CA6-8F06-A4270B675D69"
#define BLE_UUID_AMPLITUDE                  "E3ADBF53-950E-DC1D-9B44-076BE52760D6"

//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------

BLEService testService( BLE_UUID_TEST_SERVICE );
BLEFloatCharacteristic floatValueCharacteristic( BLE_UUID_FLOAT_VALUE, BLERead | BLENotify );
BLEFloatCharacteristic amplitudeCharacteristic( BLE_UUID_AMPLITUDE, BLERead | BLEWrite );

float floatValue = 0.0;
float amplitude = 0.1;

void setup()
{
  BLE.begin();

  // set advertised local name and service
  BLE.setDeviceName( "Arduino Nano 33 BLE" );
  BLE.setLocalName( "Arduino Nano 33 BLE" );
  BLE.setAdvertisedService( testService );

  // BLE add characteristics
  testService.addCharacteristic( floatValueCharacteristic );
  testService.addCharacteristic( amplitudeCharacteristic );
  

  // add service
  BLE.addService( testService );

  // set the initial value for characeristics
  floatValueCharacteristic.writeValue( floatValue );
  amplitudeCharacteristic.writeValue( amplitude );
  

  BLE.advertise();
}

void loop()
{
  static long previousMillis = 0;

  BLEDevice central = BLE.central();

  if ( central )
  {
    while ( central.connected() )
    {
      if( amplitudeCharacteristic.written() )
      {
        amplitude = amplitudeCharacteristic.value();
      }
      
      long interval = 50;
      unsigned long currentMillis = millis();
      if ( currentMillis - previousMillis > interval )
      {
        previousMillis = currentMillis;
        static float t = 0.0;
        t += 2 * PI / interval;
        floatValue = amplitude * sin( t );
        if ( central.rssi() != 0 )
        {
          floatValueCharacteristic.writeValue( floatValue );
        }
      }
    }
  }
}

And here is the code for the central. Connect the Serial Plotter and you should see a sin wave with slowly increasing peak amplitude. The wave is created by writing the peak amplitude as float characteristic and reading the signal as float characteristic with notification.

/*
  This example creates a BLE central that scans for a peripheral with a Test Service
  If that contains floatValue characteristics the value can be seen in the Serial Monitor or Plotter.

  The circuit:
  - Arduino Nano 33 BLE or Arduino Nano 33 IoT board.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------

#define BLE_UUID_TEST_SERVICE               "9A48ECBA-2E92-082F-C079-9E75AAE428B1"
#define BLE_UUID_FLOAT_VALUE                "C8F88594-2217-0CA6-8F06-A4270B675D69"
#define BLE_UUID_AMPLITUDE                  "E3ADBF53-950E-DC1D-9B44-076BE52760D6"

void setup()
{
  Serial.begin( 9600 );
  while ( !Serial );

  BLE.begin();

  BLE.scanForUuid( BLE_UUID_TEST_SERVICE );
}


void loop()
{
  BLEDevice peripheral = BLE.available();

  if ( peripheral )
  {
    if ( peripheral.localName() != "Arduino Nano 33 BLE" )
    {
      return;
    }

    BLE.stopScan();

    connectPeripheral( peripheral );

    BLE.scanForUuid( BLE_UUID_TEST_SERVICE );
  }
}


bool connectPeripheral( BLEDevice peripheral )
{
  if ( !peripheral.connect() )
  {
    return false;
  }

  if ( !peripheral.discoverAttributes() )
  {
    peripheral.disconnect();
    return false;
  }

  BLECharacteristic floatValueCharacteristic = peripheral.characteristic( BLE_UUID_FLOAT_VALUE );
  BLECharacteristic amplitudeCharacteristic = peripheral.characteristic( BLE_UUID_AMPLITUDE );
  if ( !floatValueCharacteristic || !amplitudeCharacteristic )
  {
    peripheral.disconnect();
    return false;
  }

  if ( !floatValueCharacteristic.canSubscribe() )
  {
    peripheral.disconnect();
    return false;
  }

  if ( !floatValueCharacteristic.subscribe() )
  {
    peripheral.disconnect();
    return false;
  }

  while ( peripheral.connected() )
  {
    if ( floatValueCharacteristic.valueUpdated() )
    {
      float floatValue;
      floatValueCharacteristic.readValue( &floatValue, 4 );
      Serial.println( floatValue );
    }

    static long previousMillis = 0;
    long interval = 1000;
    unsigned long currentMillis = millis();
    if ( currentMillis - previousMillis > interval )
    {
      previousMillis = currentMillis;
      static float amplitude = 0.5;
      amplitude += 0.1;
      if ( amplitude > 8.0 )
      {
        amplitude = 0.5;
      }
      amplitudeCharacteristic.writeValue( &amplitude, 4 );
    }
  }

  peripheral.disconnect();
  return true;
}

Let me know if you have any questions or anything is unclear.

1 Like

Well, I tried your suggestions and now I'm able to pass float values. But I still have issues with getting it to communicate reliably. You have to reset the two boards and open the serial window in just the right order or they won't connect.

Thanks for your help!

Don

eromlignod:
But I still have issues with getting it to communicate reliably. You have to reset the two boards and open the serial window in just the right order or they won't connect.

There are two issues:

First the example code is reduced to show the float functionality. I usually prefer to write the sketch in a way that loop() keeps running as fast as possible and can recover from failures. But that makes the code more complicated and needs to be tailored to the use case.

Second there is an issue with the BLE implementation that stops the BLE stack from working properly when the connection is lost e.g. you walk away with the central while connected or in your case you reset/reprogram the central device while you are connected to the peripheral.
Because the central example sketch stays connected forever this will cause the crash every time you modify your central code. You will need to reset your peripheral after uploading a new central sketch.

The only way you can send multiple values at a time is through a byte array.

This is handled by:

BLECharacteristic myCharacteristic(“MY-128BIT-UUID”, myBLEproperties, ARRAY_SIZE, IS_SIZE_FIXED_T_OR_F);

So how does this handle floats, you may wonder.

Well, fortunately there is a method, which has been used by others for transmitting BLE health thermometer measurement values.

It is based on a conversion method defined by IEEE11073 which converts a float into a 4 byte value using exponent and mantissa etc.

The Arduino code for this conversion method can be found in the Adafruit Bluefruit nRF51 library within the “healththermometer” example.

To convert these 4 byte values back to float again you can use this code:

float IEEE11073_2float(uint8_t *dat)
{
  int32_t Mantissa = (dat[2] << 16 | dat[1] << 8 | dat[0]);
  uint8_t Neg = bitRead(dat[2],7);
  int8_t fExp = dat[3];
  if (Neg) Mantissa |= 255 << 24;
  return (float(Mantissa) * pow(10, fExp));
}

I was able to transmit the 6DOF (x-y-z accel and gyro) float values using this method without problem.

gerrikoio:
The only way you can send multiple values at a time is through a byte array.

You could also simply have multiple characteristics. This would also make your solution more flexible in case only some of those values are required. The central/client can then decide which characteristics it wants to read.

Some standard characteristics convert float values simply into integer values with a fixed point. e.g. 10.35 is stored as 1035. (See temperature characteristic description link below)

Bluetooth Temperature characteristic XML description

You could also simply have multiple characteristics.

Yes you could send your data as a text string (characters) but would you really. Still, from what I know this is still sent as a byte array. For clarity, please refer to data types link below. Here it tells us that text strings sent via Bluetooth are encoded as UTF-8 string or UTF-16 string formats.

Some standard characteristics convert float values simply into integer values with a fixed point. e.g. 10.35 is stored as 1035. (See temperature characteristic description link below)

Yes, this is another work around if you want the float value to only have two decimal places and you know in advance what the max value will be as this determines what data type to use. In your example, the 1035 is greater than 255 and thus requires two bytes to store that data or you need to use a specific data typed characteristic (as defined in the Arduino libary), such as a short, but this means you cannot send multiple values at a time.

If you look at the Bluetooth spec for data types (https://www.bluetooth.com/specifications/assigned-numbers/format-types/) you will see that float values are defined as IEEE-11073 16-bit FLOAT (i.e. value is stored in 2 bytes) or IEEE-11073 32-bit FLOAT (i.e. value is stored in 4 bytes)

gerrikoio:
Yes you could send your data as a text string (characters) but would you really.

We do not want to use text strings. You can add multiple characteristics to a service. Each characteristic can have its own data type. The advantage is that the client can choose which characteristic it is interested in and how often it would like to read it. You can have a sensor with multiple different values e.g. temp, humidity, pressure, wind speed ... but a display client that can only display temperature only reads that one characteristic and does not even need to know how to decode the others.

gerrikoio:
Yes you could send ...
but this means you cannot send multiple values at a time.

As described above, in most cases you might not want to "send" multiple values at the same time. There are cases where the data has been specified as structures with multiple values (e.g. CSC measurement). They are a pain for development because you need to know what the structure is and handle it. With simple data types you can at least guess.

What do you mean by "you can/cannot send"? From an application point of view in BLE you do not actively send data. Your application layer just stores the data locally. The BLE stack allows clients to read and write the data using the GATT procedures according the access rules you have set.

gerrikoio:
If you look at the Bluetooth spec for data types (https://www.bluetooth.com/specifications/assigned-numbers/format-types/) you will see that float values are defined as IEEE-11073 16-bit FLOAT (i.e. value is stored in 2 bytes) or IEEE-11073 32-bit FLOAT (i.e. value is stored in 4 bytes)

The BLE library has the BLEFloatCharacteristic which uses 4 bytes and is compatible with the float data type used inside the sketch. Which is likely IEEE-754 because that is what the ARM Cortex-M4 FPU unit uses. The conversion to another Float type is only necessary when you provide a predefined service. The IEEE-11073 data types seem only necessary for medical devices.

You can add multiple characteristics to a service. Each characteristic can have its own data type. The advantage is that the client can choose which characteristic it is interested in and how often it would like to read it. You can have a sensor with multiple different values e.g. temp, humidity, pressure, wind speed ... but a display client that can only display temperature only reads that one characteristic and does not even need to know how to decode the others.

You are absolutely correct. This is what the Environmental Sensing Service does, by way of example.

But, the issue for consideration, from a firmware development perspective, is that creating multiple services and/or characteristics does use up valuable resource. So, it is up to the developer to decide which method suits as there are multiple criteria to consider. However, this is outside the scope of this post.

That is, the title of this post is "passing float values with Bluetooth". I would interpret this as wanting to send (or transmit) more than one float value at a time. IMHO, the information I provided answered that question.