Sending float over BLE

Hey all,

I’m trying to send sensor readings over BLE to an app I created using MIT app inventor and something is not working, my guess is I’m missing something in my sketch or misunderstanding on how BLE works.

This is the sketch I use:

#include <ArduinoBLE.h>

#define BLE_BUFFER_SIZES             20
#define BLE_DEVICE_NAME                "DTD"
#define BLE_LOCAL_NAME                "DTD_1"

int red = 11;
int green = 10;
int blue = 9;
float sensor = A0;
float cf = 19.5; // caliberation factor
float pressureReading;

BLEService BLESensors("00001101-0000-1000-8000-00805F9B34FB");
BLECharacteristic pressureBLE("2A19", BLERead | BLENotify | BLEBroadcast, BLE_BUFFER_SIZES);

void setup()
{
  LedColor (0,255,0);
  Serial.begin(9600);
  while (!Serial);

  // begin initialization
  if (!BLE.begin())
  {
    Serial.println("starting BLE failed!");

    while (1);
  }

  // set the local name peripheral advertises
        BLE.setDeviceName(BLE_DEVICE_NAME);
        BLE.setLocalName(BLE_LOCAL_NAME);
        BLE.setAdvertisedService(BLESensors);
        BLESensors.addCharacteristic(pressureBLE);

        BLE.addService(BLESensors);
  // start advertising
  BLE.advertise();

  Serial.println(("Bluetooth device active, waiting for connections..."));
}

void loop() 
{

  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  // if a central is connected to peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());


    // while the central is still connected to peripheral:
    while (central.connected()) 
    {
      LedColor (0,0,255);
      
      for (int i=0;i<10;i++)
          {
            float Reading = analogRead(sensor); 
            pressureReading = pressureReading + Reading;
            delay (50);
          }
          
          pressureReading = pressureReading / 10;
          pressureReading = (pressureReading * 5.0) / 1023.0;
          pressureReading = pressureReading * cf;

          Serial.println(pressureReading);

          pressureReading = 0;

  }
  // when the central disconnects, print it out:
      Serial.print(F("Disconnected from central: "));
      Serial.println(central.address());
      LedColor (255,0,0);
}
}

void LedColor(int redL, int greenL, int blueL)
 {
  analogWrite(red, redL);
  analogWrite(green, greenL);
  analogWrite(blue, blueL);
}

and this is a small app I created to check if I recive the data:

for some reason I see the sensor reading in the monitor but not in the app.

Can you please modify your original post to ensure the source code is in a single box. It should look like this.

// Your example source code

You need three at the beginning and end in the edit window? When you click on this icon </> you get.

```
type or paste code here
```

This ensures we can get all the source code. Right now if you copy the code there are invisible characters that prevent successful compilation and parts might be missing.

From what I see, you do not update the characteristic with the data. See write().

https://www.arduino.cc/en/Reference/ArduinoBLEBLECharacteristicwriteValue

Also check out the different native data type characteristics supported by the ArduinoBLE library.

https://www.arduino.cc/en/Reference/ArduinoBLEBLECharacteristicBLECharacteristic

Hint: Before you test any other BLE software, test your peripheral with a generic BLE app on an smartphone. I use BLE Scanner on iPhone but there are many other on iOS and Android. When that works you can look for issues in your code in MIT app inventor.

Thank you for the answer, I’ll check it.
also I edited the post so the code is in a single box.

Hey, after doing some reading and few changes I still cant seem to make it work and I realy dont know why.

I’m able to find the device using BLE Scanner but it cannot show the values that are being sent.
Do you have any idea what could the problem be?

#include <ArduinoBLE.h>

#define BLE_BUFFER_SIZES             20
#define BLE_DEVICE_NAME                "DTD"
#define BLE_LOCAL_NAME                "DTD_1"

int red = 11;
int green = 10;
int blue = 9;
float sensor = A0;
float cf = 19.5; // caliberation factor
float pressureReading;

BLEService BLESensors("00001101-0000-1000-8000-00805F9B34FB");
BLECharacteristic pressureBLE ("2A19", BLERead | BLENotify | BLEBroadcast, BLE_BUFFER_SIZES);

void setup()
{
  LedColor (0,255,0);
  Serial.begin(9600);
  while (!Serial);

  // begin initialization
  if (!BLE.begin())
  {
    Serial.println("starting BLE failed!");

    while (1);
  }

  // set the local name peripheral advertises
        BLE.setDeviceName(BLE_DEVICE_NAME);
        BLE.setLocalName(BLE_LOCAL_NAME);
        BLE.setAdvertisedService(BLESensors);
        BLESensors.addCharacteristic(pressureBLE);

        BLE.addService(BLESensors);
  // start advertising
  BLE.advertise();

  Serial.println(("Bluetooth device active, waiting for connections..."));
}

void loop() 
{

  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  // if a central is connected to peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());


    // while the central is still connected to peripheral:
    while (central.connected()) 
    {
      LedColor (0,0,255);
      
      //for (int i=0;i<10;i++)
         // {
            byte pressureReading = map(analogRead(sensor),0,1023,0,255); 
          //  pressureReading = pressureReading + Reading;
          //  delay (50);
         // }
          
         // pressureReading = pressureReading / 10;
         //  pressureReading = (pressureReading * 5.0) / 1023.0;
         // pressureReading = pressureReading * cf;

          Serial.println(pressureReading);
          pressureBLE.writeValue((byte)pressureReading);

          pressureReading = 0;
          delay (500);
  }
  // when the central disconnects, print it out:
      Serial.print(F("Disconnected from central: "));
      Serial.println(central.address());
      LedColor (255,0,0);
}
}

void LedColor(int redL, int greenL, int blueL)
 {
  analogWrite(red, redL);
  analogWrite(green, greenL);
  analogWrite(blue, blueL);
}

The issue is your characteristic definition. Change it to

BLEByteCharacteristic pressureBLE ("2A19", BLERead | BLENotify );

When you create a generic BLECharacteristic the last parameter is the valueSize not a buffer size. You have to write the correct amount of bytes to update the characteristic. For your case it seems better to use a characteristic of the correct type.

Here are a few other things I noted about your code:

  • the UUID for the characteristic is 16-bit, you should use a 128-bit random UUID
  • the UUID for the service is not random, you can use a few bits to group a few UUIDs but most bits should be random
  • you use while in loop and delay(), while this is keeps simple examples short it will prevent you from extending your sketch later without breaking what you already have. Try to ensure your loop() function is running as often as possible.

Have a look at the following example to learn to time control parts of your code

File -> Examples -> 02.Digital -> BlinkWithoutDelay

  • your variable naming could be improved avoid short variable names if they become cryptic or could mean different things e.g. you called a pin for a LED "red" and then you had to rename the function parameters to "readL" to avoid a naming collision. You could have use redLedPin for instance.
  • the following code is wrong (the compiler fixes the code in the background)
float sensor = A0; // wrong data type for  pin
... analogRead(sensor) ...

I have written a little example that includes a few other techniques that might be useful.

BLE Sensor example (click to view)
/*
  This example creates a BLE peripheral with a service and a characteristic.

  The circuit:
  - Arduino Nano 33 IoT.

  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_SENSOR_SERVICE          "82D20000-87F8-C79D-9E63-A091F4171879"
#define BLE_UUID_PRESSURE                "82D20001-87F8-C79D-9E63-A091F4171879"

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

#define BLE_DEVICE_NAME                 "Arduino Nano 33 IoT"
#define BLE_LOCAL_NAME                  "Arduino Nano 33 IoT"

BLEService sensorService( BLE_UUID_SENSOR_SERVICE );
BLEShortCharacteristic pressureCharacteristic( BLE_UUID_PRESSURE, BLERead | BLENotify );

//----------------------------------------------------------------------------------------------------------------------
// I/O and App
//----------------------------------------------------------------------------------------------------------------------

#define BLE_LED_PIN                     LED_BUILTIN
#define PRESSURE_SENSOR_PIN             A0

uint16_t averagePressure = 0;
bool updatePressureCharacteristic = false;

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

  pinMode( BLE_LED_PIN, OUTPUT );
  pinMode( PRESSURE_SENSOR_PIN, INPUT );
  analogReadResolution( 10 );

  if ( !setupBleMode() )
  {
    Serial.println( "Failed to initialize BLE!" );
    while ( 1 );
  }
  else
  {
    Serial.println( "BLE initialized. Waiting for clients to connect." );
  }
}

void loop()
{
  bleTask();
  sensorTask();
}


void sensorTask()
{
  #define SENSOR_SAMPLING_INTERVAL  50
  #define SAMPLE_BUFFER_SIZE        10

  static uint16_t sampleBuffer[SAMPLE_BUFFER_SIZE] = { 0 };
  static uint32_t previousMillis = 0;
  static uint32_t sampleIndex = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis >= SENSOR_SAMPLING_INTERVAL )
  {
    previousMillis = currentMillis;
    uint16_t currentPressure = analogRead( PRESSURE_SENSOR_PIN );
    sampleBuffer[sampleIndex] = currentPressure;
    sampleIndex = ( sampleIndex + 1 ) % SAMPLE_BUFFER_SIZE;

    if ( sampleIndex == 0 )
    {
      uint32_t bufferTotal = 0;
      for ( uint32_t i = 0; i < SAMPLE_BUFFER_SIZE; i++ )
      {
        bufferTotal += sampleBuffer[i];
      }
      averagePressure = (uint16_t) round( bufferTotal / SAMPLE_BUFFER_SIZE );
      updatePressureCharacteristic = true;
    }
  }
}


bool setupBleMode()
{
  if ( !BLE.begin() )
  {
    return false;
  }

  // set advertised local name and service UUID
  BLE.setDeviceName( BLE_DEVICE_NAME );
  BLE.setLocalName( BLE_LOCAL_NAME );
  BLE.setAdvertisedService( sensorService );

  // add characteristics
  sensorService.addCharacteristic( pressureCharacteristic );

  // add service
  BLE.addService( sensorService );

  // set the initial value for the characeristics
  pressureCharacteristic.writeValue( averagePressure );

  // set BLE event handlers
  BLE.setEventHandler( BLEConnected, blePeripheralConnectHandler );
  BLE.setEventHandler( BLEDisconnected, blePeripheralDisconnectHandler );

  // start advertising
  BLE.advertise();

  return true;
}


void bleTask()
{
  #define BLE_UPDATE_INTERVAL 10
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis >= BLE_UPDATE_INTERVAL )
  {
    previousMillis = currentMillis;
    BLE.poll();
  }

  if ( updatePressureCharacteristic )
  {
    updatePressureCharacteristic = false;
    pressureCharacteristic.writeValue( averagePressure );
  }
}


void blePeripheralConnectHandler( BLEDevice central )
{
  digitalWrite( BLE_LED_PIN, HIGH );
  Serial.print( "Connected to central: " );
  Serial.println( central.address() );
}


void blePeripheralDisconnectHandler( BLEDevice central )
{
  digitalWrite( BLE_LED_PIN, LOW );
  Serial.print( "Disconnected from central: " );
  Serial.println( central.address() );
}

Thank you,
it seems to work now (on BLE scanner) now I need to check where is the problem with my code

Dear @Klaus_K , if the value that is being read by the pressure sensor is a float type, why should we use BLEByteCharacteristic instead of BLEFloatCharacteristic?

I´m facing the same problem of @darkpr0phet trying to send data from a load cell to mobile using a MIT app inventor developed app, and it´s not showing any data.

Checking the trasmitted data at NRFConnect, the values appear as Hexadecimal (?)

I´m still having the doubts that I posted in https://forum.arduino.cc/t/reading-ble-data-from-nano-iot-33/872854

In the example from darkpr0phet he converted the pressure data to a byte. Maybe that was all that he required. Therefore, the right answer was to use a BLEByteCharacteristic.

Sensors usually do not create float data. The library may do that but that is often a compromise. Many microcontrollers must handle float in software. This costs a lot of memory and processor cycles compared to integer math. It makes writing software for beginner easier, and the drawback can be ignored because most beginners have simple sketches with limited processing requirements.

In most cases floating point data should be avoided for the BLE interface for the following reason, floating point data types can be different in one platform to another. Some implementations use IEEE-754 but others use IEEE-11073 (e.g., medical devices). Floating point HEX and Binary notation is non-intuitive for most users. Even beginners can learn to convert an integer HEX value by hand.

And the Bluetooth SIG has made use of fixed-point values for some characteristics to show good design practice e.g., temperature is defined as sint16 with a resolution of 0.01 degrees. So, for 21.5 C you would have a value of 2150.

Generic BLE apps just show you the bytes they find in a characteristic. They know how many bytes are in the characteristic, but they do not know what the format is (in most cases).

You can add descriptors to a characteristic to describe the data type, but this is most often not done and therefore BLE apps ignore that anyway.

On which platform are you using MIT app inventor. In played a bit with it but my phone uses iOS and BLE does not seem to be supported by the MIT app. I used many other BLE apps an they work (mostly :slight_smile: ).

1 Like

@Klaus_K , first of all, thanks for answering my both posts!

Ok, you got a point here. It´s all a matter of voltage variation and thus, I can set the number of significant digits by myself. The load cell signal is too low to be read directly, thus it passes through an HX711 module (amplifier and A/D converter). The sketch that I followed plugs the A/D converter output into a digital port of Arduino. Maybe if I plug it into an analogic imput (even receiving a digital signal) and map it to 0-1023 I can get a better precision and work with an integer value.

Ok, I´ll take a look on how to add descriptors to the characteristics.

Well, that´s another issue. My wife´s phone also uses IOS. I´m doing this project for her and I don´t make any idea of how to develop an app to Apple devices. This is why I considered the possibility to use a laptop. My phone uses Android and MIT Inventor can handle BLE if you download the appropriate add-on.

Take a look at http://iot.appinventor.mit.edu/assets/tutorials/MIT_App_Inventor_Basic_Connection.pdf

This is where I got this :point_down:t2:

Thanks again for the tips!

Thanks for the info, I will have a look.

As I said, BLE apps ignore the presentation format descriptor so far. The User Description is shown by some apps making identifying the characteristics easier when use a generic BLE app.
I have written an example on how to implement the two most useful descriptors.

BLE Descriptor example (Click to open)
/*
  This example shows how to use BLE descriptors.

  The circuit:
  - Arduino Nano 33 BLE and BLE Sense
  - Arduino Nano RP2040 Connect
  - Arduino Nano 33 IoT

  You can use a generic BLE central app, like BLE Scanner (iOS and Android) or
  nRF Connect (iOS and 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_SENSOR_SERVICE                   "65C30000-8987-3D40-DF89-978B41A839C0"
#define BLE_UUID_SENSOR                           "65C30001-8987-3D40-DF89-978B41A839C0"

#define BLE_UUID_CHARACTERISTIC_USER_DESCRIPTION  "2901"
#define BLE_UUID_PRESENTATION_FORMAT              "2904"

#define BLE_UUID_GATT_UNIT_UNITLESS               0x2700
#define BLE_UUID_GATT_UNIT_VOLT                   0x2728


//----------------------------------------------------------------------------------------------------------------------
// BLE Characteristic Format Descriptor
//----------------------------------------------------------------------------------------------------------------------
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Descriptors/org.bluetooth.descriptor.gatt.characteristic_presentation_format.xml
// Unfortunately, this descriptor is ignored by BLE apps so far.

typedef struct __attribute__( ( packed ) )
{
  uint8_t format;
  int8_t exponent;
  uint16_t unit;
  int8_t name_space;
  int16_t description;
} presentation_format_t;


union presentation_format_u
{
  struct __attribute__( ( packed ) )
  {
    presentation_format_t values;
  };
  uint8_t bytes[ sizeof( presentation_format_t ) ];
};

union presentation_format_u sensorPresentationFormat = { .values = { .format = 6, .exponent = -3, .unit = BLE_UUID_GATT_UNIT_VOLT, .name_space = 1, .description = 0 } };


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

#define BLE_DEVICE_NAME                           "Arduino RP2040"
#define BLE_LOCAL_NAME                            "Arduino RP2040"

BLEService sensorService( BLE_UUID_SENSOR_SERVICE );
BLEUnsignedIntCharacteristic sensorCharacteristic( BLE_UUID_SENSOR, BLERead | BLENotify );

BLEDescriptor sensorPresentationFormatDescriptor( BLE_UUID_PRESENTATION_FORMAT, sensorPresentationFormat.bytes, sizeof sensorPresentationFormat.bytes );
BLEDescriptor sensorUserDescriptionDescriptor( BLE_UUID_CHARACTERISTIC_USER_DESCRIPTION, "Voltage with resolution 0.001 V" );


//----------------------------------------------------------------------------------------------------------------------
// APP & I/O
//----------------------------------------------------------------------------------------------------------------------

#define BLE_LED_PIN                               LED_BUILTIN
#define SENSOR_PIN                                A0

typedef struct __attribute__( ( packed ) )
{
  uint16_t value;
  bool updated;
} sensor_data_t;

sensor_data_t sensorData = { .value = 0, .updated = false };

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

  Serial.println( "ArduinoBLE Example - Descriptors" );

  pinMode( BLE_LED_PIN, OUTPUT );
  digitalWrite( BLE_LED_PIN, LOW );
  pinMode( SENSOR_PIN, INPUT );
  
  analogReadResolution( 12 );

  if ( !setupBleMode() )
  {
    Serial.println( "Failed to initialize BLE!" );
    while ( 1 );
  }
  else
  {
    Serial.println( "BLE initialized. Waiting for clients to connect." );
  }
}


void loop()
{
  bleTask();
  sensorTask();
}


void sensorTask()
{
  const uint32_t SENSOR_UPDATE_INTERVAL = 100;
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis >= SENSOR_UPDATE_INTERVAL )
  {
    previousMillis = currentMillis;
    uint16_t adcValue = analogRead( SENSOR_PIN );
    sensorData.value = map( adcValue, 0, 4095, 0, 3300 );
    sensorData.updated = true;
  }
}


bool setupBleMode()
{
  if ( !BLE.begin() )
  {
    return false;
  }

  // set advertised local name and service UUID
  BLE.setDeviceName( BLE_DEVICE_NAME );
  BLE.setLocalName( BLE_LOCAL_NAME );
  BLE.setAdvertisedService( sensorService );

  // BLE add characteristics
  sensorService.addCharacteristic( sensorCharacteristic );

  // BLE add descriptors
  sensorCharacteristic.addDescriptor( sensorPresentationFormatDescriptor );
  sensorCharacteristic.addDescriptor( sensorUserDescriptionDescriptor );

  // add service
  BLE.addService( sensorService );

  // set the initial value for the Characteristic
  sensorCharacteristic.writeValue( sensorData.value );

  // set BLE event handlers
  BLE.setEventHandler( BLEConnected, blePeripheralConnectHandler );
  BLE.setEventHandler( BLEDisconnected, blePeripheralDisconnectHandler );

  // start advertising
  BLE.advertise();

  return true;
}

void bleTask()
{
  const uint32_t BLE_UPDATE_INTERVAL = 10;
  static uint32_t previousMillis = 0;

  uint32_t currentMillis = millis();
  if ( currentMillis - previousMillis >= BLE_UPDATE_INTERVAL )
  {
    previousMillis = currentMillis;
    BLE.poll();
  }

  if ( sensorData.updated )
  {
    sensorData.updated = false;
    sensorCharacteristic.writeValue( sensorData.value );
  }
}

void blePeripheralConnectHandler( BLEDevice central )
{
  digitalWrite( BLE_LED_PIN, HIGH );
  Serial.print( F( "Connected to central: " ) );
  Serial.println( central.address() );
}


void blePeripheralDisconnectHandler( BLEDevice central )
{
  digitalWrite( BLE_LED_PIN, LOW );
  Serial.print( F( "Disconnected from central: " ) );
  Serial.println( central.address() );
}

Thanks for sharing your example!

I see that I´m a little bit far from writing a good code. My programming level is a little below the necessary to understand all the statements of your code. Need to go back to the books... :blush:

Two questions that I believe are simple for now:

What does the "F" before the string to be printed?

Serial.print( F( "Connected to central: " ) );

What this expression does?

 BLE.poll();

That is a macro that tells the compiler to leave the String in Flash memory. It is important on old Arduinos because they have small RAMs. It is not needed in most new Arduinos because they have a lot more RAM. But when RAM gets filled up you can get some back.

The BLE stack needs to be called from time to time to see what is going on. Without it all the callback do not work. You might need to adjust the timing depending your application.

https://www.arduino.cc/en/Reference/ArduinoBLEBLEpoll

1 Like

@darkpr0phet , we seem to be at the same point. I can find my device on Android NRFConnect and read data sent to it (in HEX format). But my developed app can´t get the data. If you solved the problem with your Android app would you mind sharing the blocks that read the data? I´m having doubts whether to use the block "callBLE.RegisterForxyz", the block "callBLE.Readxyz" or both of them...