How to 'Extract' FLOAT data from longer Array

Hello,
I’m working on a Battery Management System project, and I’ve come across an issue I can’t seem to solve on my own. I’m using serial communication between an ESP32 (Node32s) and my Battery Management System. The data from the BMS comes in as ‘packets’ of 8 bytes, with the 4 bytes in the middle representing a floating point number. I’m using the following code to ‘compile’ those 4 bites in the correct order (it’s little endian, I think…). I’ve tried a number of different variations on this (eg. ‘long’ vs ‘unsigned long’), with no success.

  readData(VpackRESP);
  float Vpack;
  unsigned long VpackL;
  VpackL = (unsigned)VpackRESP[2];
  VpackL += (unsigned)VpackRESP[3] << 8;
  VpackL += (unsigned)VpackRESP[4] << 16;
  VpackL += (unsigned)VpackRESP[5] << 24;
  Vpack = (float)Vpackl;

As an example, for array values of 0x5A 0x64 0x53 0x42, I expect to get a value of 52.848; however, the value I get is: 1112761434. Based on an online converter I found, this seems to be the result of using UINT32 format, rather than float (to be perfectly honest, I don’t really know what that means…).

Anyway, I’m trying to get this to output 52.828. Any help would be appreciated.

Use memcpy:

  readData(VpackRESP);
  float Vpack;
  uint32_t VpackL = VpackRESP[5];        // MSB
  VpackL = (VpackL << 8) | VpackRESP[4];
  VpackL = (VpackL << 8) | VpackRESP[3];
  VpackL = (VpackL << 8) | VpackRESP[2]; // LSB
  static_assert(sizeof(Vpack) == sizeof(VpackL), "Error: size mismatch");
  memcpy(&Vpack, &VpackL, sizeof(float));

Complete working example: Compiler Explorer

Pieter

BINGO!!!

Thanks so much for the speedy (and completely effective) response PieterP! Now I just need to spend the next 5 hours trying to figure out how it works!

Although frowned-upon by some, I find unions handy for tasks like these:

union uVpackBF
{
    float   fVPack;
    byte    grbVpack[sizeof(float)];
    
};

void setup() 
{
    uVpackBF 
        unV;
        
    Serial.begin(115200);
    //
    unV.grbVpack[0] = 0x5a;
    unV.grbVpack[1] = 0x64;
    unV.grbVpack[2] = 0x53;
    unV.grbVpack[3] = 0x42;    
    Serial.print( "Vpack is " ); Serial.println( unV.fVPack, 3 );

    unV.fVPack = 52.828;
    Serial.print( "Bytes are " ); 
    Serial.print( unV.grbVpack[0], HEX );
    Serial.print( unV.grbVpack[1], HEX );
    Serial.print( unV.grbVpack[2], HEX );
    Serial.println( unV.grbVpack[3], HEX );

    while(1);

}//setup

void loop() 
{

}//loop

Output:

Vpack is 52.848
Bytes are DF4F5342

You receive the data as 4 8-bit integers (bytes), then you combine them into a 32-bit integer using bit shifts (<<). Finally, you want to interpret these 32 bits as a IEEE 754 floating point number. When you just cast it to a float using (float) VpackL, you convert the integer value of the 32-bit integer to a floating point number with (approximately) the same value (e.g. the integer 3 becomes the floating point number 3.0). That’s not what you want, you want the bits of the of the integer to become the bits of the floating point number. That’s what memcpy does, it copies the bit representation of one variable to another, it doesn’t copy the numerical value.

Blackfin:
Although frowned-upon by some, I find unions handy for tasks like these:

Not just frowned-upon, simply not allowed by the C++ standard. **Don't do it. **

I see no reason why using a union would be clearer or easier than using memcpy. Memcpy is easier to read, clearly shows your intent, and is the only valid way to do this conversion (at least until we get std::bit_cast in C++20).

Another, simpler way to do that : ficzTL - Online C++ Compiler & Debugging Tool - Ideone.com

guix:
Another, simpler way to do that : ficzTL - Online C++ Compiler & Debugging Tool - Ideone.com

This invokes undefined behavior as well. Like I said earlier, memcpy is the only valid way to do the conversion.

Can you give one case where this will fail to give the correct result ? I understand that the behavior hasn't been defined by standards, but it works and it's widely used!

Undefined behavior doesn't just mean that is not defined by the standard, it means that your entire program becomes ill-formed and the compiler isn't expected to do anything useful: Undefined behavior - cppreference.com

Undefined behavior

Renders the entire program meaningless if certain rules of the language are violated.

There are no restrictions on the behavior of the program. Examples of undefined behavior are memory accesses outside of array bounds, signed integer overflow, null pointer dereference, more than one modifications of the same scalar in an expression without any intermediate sequence point (until C++11)that are unsequenced (since C++11), access to an object through a pointer of a different type, etc. Compilers are not required to diagnose undefined behavior (although many simple situations are diagnosed), and the compiled program is not required to do anything meaningful.

guix:
I understand that the behavior hasn't been defined by standards, but it works and it's widely used!

IMO, it depends on the context. Working on a dopey little Arduino project? Then, that philosophy is probably fine.

How would you feel about riding in an airliner, in poor weather, where the person who wrote the code for the autopilot had the same attitude?

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