Putting sensor data floats in a Union to read with BLE

Hello everyone,

I'm working on moving an old hobbyproject to the new Arduino Giga. I had a working bluetooth function with a MIT app inventor app. The Giga has BLE insted of bluetooth so I'm trying to convert everything. Packing, sending the data and reading the data are all so different from Bluetooth classic that I find it quite the challenge. I've gotten as far as reading 1 sensor value but I would like at least 3 and if that works step it up to all 12.

I found this thread by @Klaus_K that gave me the idea to put multiple values in a union and send it as 1 characteristic. I have little to no experience coding myself and reading up on the method used still leaves me with a lot of questions. I tried making seperate smaller sketches with these examples , here ... and here

The truncated sketch looks like this:


//credit to Klaus_K @ https://forum.arduino.cc/t/ble-very-weak-signal/631751/33

#include <ArduinoBLE.h>

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

#define BLE_UUID_SENSOR_DATA_SERVICE              "ECBCAD86-1010-441B-BE5D-C11AB3415169"
#define BLE_UUID_MULTI_SENSOR_DATA                "ECBCAD86-2010-441B-BE5D-C11AB3415169"

#define NUMBER_OF_SENSORS 3

union multi_sensor_data
{
  struct __attribute__( ( packed ) )
  {
    float values[NUMBER_OF_SENSORS];
  };
  uint8_t bytes[ NUMBER_OF_SENSORS * sizeof( float ) ];
};

union multi_sensor_data multiSensorData;

float airTempAverage = 25,6
float humidityAverage = 67,5
float pressureAverage = 1012

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

BLEService sensorDataService( BLE_UUID_SENSOR_DATA_SERVICE );
BLECharacteristic multiSensorDataCharacteristic( BLE_UUID_MULTI_SENSOR_DATA, BLERead | BLENotify, sizeof multiSensorData.bytes );

#define BLE_LED_PIN = LEDB;

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

  pinMode( BLE_LED_PIN, OUTPUT );

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

  for ( int i = 0; i < NUMBER_OF_SENSORS; i++ ) 
  {
    multiSensorData.values[i] = i;
  }
}

void loop()
{
  
  static long previousMillis = 0;

  if (millis() - previousMillis >= 2000) 
  {
    updateStatistics();
    updateAverageReadings = true;
  }

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

  if ( central )
  {
    Serial.print( "Connected to central: " );
    Serial.println( central.address() );
    
    multiSensorData.values[1] = airTempAverage;
    multiSensorData.values[2] = humidityAverage;
    multiSensorData.values[3] = pressureAverage;

    while ( central.connected() )
    {
      unsigned long currentMillis = millis();
      if ( currentMillis - previousMillis >= 2000 )
      {
        previousMillis = currentMillis;

        if ( central.rssi() != 0 )
        {
          for ( int i = 0; i < NUMBER_OF_SENSORS; i++ )
          {
            multiSensorData.values[i] = multiSensorData.values[i] + 0.1;
          }
          multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );
        }
      }
    }

    Serial.print( F( "Disconnected from central: " ) );
    Serial.println( central.address() );
  }
}

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

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

  // BLE add characteristics
  sensorDataService.addCharacteristic( multiSensorDataCharacteristic );

  // add service
  BLE.addService( sensorDataService );

  // set the initial value for the characeristic:
  multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );

  // start advertising
  BLE.advertise();

  return true;
}

My main uncertainty is if my declaration of
multiSensorData.values[1] = airTempAverage;
multiSensorData.values[2] = humidityAverage;
multiSensorData.values[3] = pressureAverage;

is correct,

and what is the 0.1 doing in multiSensorData.values[i] = multiSensorData.values[i] + 0.1;

It is transmitting, and my app is reading the bytes (when BluetoothLE1.BytesReceived set TextLabel.Text to get byteValues). Or at least there is a rapid stream of numbers. I'm searching topics and reading to understand a way to seperate them and put them on the correct (seperate) tekst fields.

This method is not allowed. Use memcpy or a cast to a byte type instead, as described here:
Don't use unions or pointer casts for type punning.


As a side note, array indices start at zero, not one. multiSensorData.values[3] is out of bounds.

That would be 1 of possible explanations why the Arduino keep rebooting every minute :sweat_smile:

I've never heard of memcpy and std::bit_cast is full of code I've never ssen before and looks complicated :smiling_face_with_tear:

This bit looks fine

float x = 25.64f;  //the airTempAverage variable
uint8_t bytes[sizeof(x)];
std::memcpy(bytes, &x, sizeof(x));

But what does the f (behind the number in float) do here?

And would I be able to combine floats in 1 memcpy?

Another way is to put the data into a struct to send. See post #7 of this thread for example code >> Serial Communication Help - #7 by groundFungus

Unlikely, there's probably another issue somewhere.

25.64 would be of type double, the f suffix makes it of type float instead.
See Floating-point literal - cppreference.com.

Yes, you could make x an array of floats. You could also use a struct, but that could introduce padding issues, so make sure it's packed.

23.45 is a literal floating point number.

C++ will assume it is of type double, shorthand for double precision floating point.

Adding the 'f' suffix makes it a single precision floating point number.

There are time when that would make a difference. I am not sure it matters in these circumstances.

HTH

a7

If I used an array, could I do it like this?

float sensorData[] { airTempAverage, humidityAverage, pressureAverage};

float x = sensorData[NUMBER_OF_SENSORS]; 
uint8_t bytes[sizeof(x)];
std::memcpy(bytes, &x, sizeof(x));

I don't think I have a good grasp on the struct way yet,
but if I replace x with struct sensorData... (and help of ChatGPT)

struct sensorData((packed))
{
  float temp = airTempAverage;
  float humi = humidityAverage;
  float pres = pressureAverage;
};

sensorData sensorData;  // was missing this
size_t dataSize = sizeof(sensorData); //and this

uint8_t dataBuffer[dataSize]; //and I didnt have this
memcpy(dataBuffer, &sensorData, dataSize);

is declaring a single precision floating number important here or not even relevant since it's combining multiple floats?

edit: Alright, I'm going to use chatGPT to help me with writing it correctly. Updating the code blocks above

No, sensorData[NUMBER_OF_SENSORS] is out of bounds again.

float x[] {airTempAverage, humidityAverage, pressureAverage};
uint8_t bytes[sizeof(x)];
std::memcpy(bytes, &x, sizeof(x));

You cannot just invent syntax like that, I'd recommend looking at chapter 10 of https://www.learncpp.com, it will prove much more efficient than the trial-and-error approach.

I'm not sure what you mean?

in the original example where float x = 25.64f; f was used in combination with a number. After replacing the number with a variable I assumed I could no longer use f there. I was wondering if that was important.

The variable already has a type. The f suffix only applies to literals.

I see, thank you.

I think I will try an array instead of struct. Currently reading up and trying to compile some things. Will post what I have if it starts to work.

Note that since you're converting to a byte type, you don't have to use memcpy in this case:

float sensorData[] {airTempAverage, humidityAverage, pressureAverage};
multiSensorDataCharacteristic.writeValue(
    reinterpret_cast<const unsigned char *>(sensorData), sizeof sensorData);

You cannot do this in general, it only works for character/byte types like (unsigned) char and std::byte.

I am testing

 multiSensorData.values[0] = airTempAverage;
          multiSensorData.values[1] = humidityAverage;
          multiSensorData.values[2] = pressureAverage;

          size_t dataSize = sizeof(multiSensorData);

          uint8_t dataBuffer[dataSize];

          memcpy(dataBuffer, sensorData, dataSize);

          multiSensorDataCharacteristic.writeValue(dataBuffer, sizeof dataSize);

If I got that right, am I understanding correctly that memcpy(dataBuffer, sensorData, dataSize); is the same as reinterpret_cast<const unsigned char *>(sensorData), sizeof sensorData);
UNder the conditions you mentioned.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.