Convert binary float string to IEEE 754

Hi,

I'm using an Arduino to grab some measurements from our electric meter via Modbus.

Measurements are stored as 32 bit IEEE 754 floats and split up into two registers (16 bits each). If I read two consecutive registers, I obtain the values, e.g.:

#1: 100001001001000
#2: 1010001111011

If I concatenate these and want to print it, the result is wrong. I figured out that I have to add leading zeroes to them until they are 16 bits long each:

without zeroes: 100001001001000 1010001111011 = 1.9341583E-36
with zeroes : 0100001001001000 0001010001111011 = 50,02 (Hz)

I wrote a function that adds the missing zeroes, but I don't know how to convert this "binary float string" into a readable number that I can save in a database.

Kind regards,
Heavyweight

Post the code you've written -- with proper Code Tags.

And, post a reference for how the data is transferred from the meter. What's the byte order? Big or Little Endian?

I can't give useful code examples. I only fetch two registers via Modbus (Library: ArduinoModbus.h) and prepend missing zeros. Nothing else.

The manual is not available online and the only specification given is that it uses "Floating point as per IEEE Standard".

But if I plug in the 32 bit long binary number into this converter (IEEE-754 Floating Point Converter) the correct result appears (I can read on the power meter, that the frequency is ~50 Hz and that's the measurement I'm currently trying to retrieve).

I tried some casting, but this results in "2.07"...

unsigned long i = 01000010010010000001010001111011;
float f = *(float*)&i;

Still not enough info. The solution will likely involve memcpy(). But, to get it right, you need to know how it comes in and how it's stored in your variables. Understand that a uint32_t will be stored in Little Endian format in the Arduino.

Have you tried with the proper 0b prefix to define binary numbers?

unsigned long i = 0b01000010010010000001010001111011;
float f = *(float*)&i;

I tried some casting, but this results in "2.07"...

Your endianess is wrong.

void setup() {
  Serial.begin(115200);
  int val1 = 0b100001001001000;
  int val2 = 0b1010001111011;
  uint32_t data = (uint32_t)val1 << 16 | val2;
  float f = *(float*)&data;
  Serial.println(f);
}

void loop() {
  // put your main code here, to run repeatedly:

}
    union {
        uint32_t B32;
        float Float;
    } floatb32;

    uint16_t a = 16968, b = 5243;
    
    floatb32.B32 = (a << 16) | b;
    
    std::cout << floatb32.Float << std::endl;

Ok, here's the full code. It's more of a doodle, than actual code.

I was reading the same measurement from two different locations, because one is given as an Integer (start: 0x0040, length: 1) and the other in IEEE (start: 0x1038, length: 2). I did it because I wanted to know, how to read a value from two registers, because the output was not what I was expecting it to be. Therefore I did this comparison.

This was the output:

16:07:15.677 -> 50050
16:07:15.677 -> ----
16:07:16.684 -> 100001001001000
16:07:16.719 -> 11001100110011
16:07:16.719 -> ----

Then I tried dealing with the binary numbers, added leading zeroes and tried to convert it to a readable number again. That's why I'm playing with variables reg1 and reg2 instead of using real-world values directly from ModbusRTUClient.read().

#include <ArduinoRS485.h> 
#include <ArduinoModbus.h>

String addLeadZeroes(String binInput) {
  int len = binInput.length();
  int neededZeroes = 16 - len;
  String leadZeroes = "";

  for(int i = 0; i < neededZeroes; i++) {
    leadZeroes += "0";
  }

  leadZeroes += binInput;

  return leadZeroes;
}


void setup() {

  Serial.begin(19200);
  while (!Serial);
 
  String reg1 = "100001001001000";
  String reg2 = "1010001111011";

  String zReg1 = addLeadZeroes(reg1);
  String zReg2 = addLeadZeroes(reg2);

  String final = zReg1 + zReg2;

  Serial.println(final);


  Serial.println("Modbus");
  // start the Modbus RTU client
  if (!ModbusRTUClient.begin(19200)) {
    Serial.println("Failed to start Modbus RTU Client!");
    while (1);
  } else {
    Serial.println("Success");
  }
  
}

void loop() {
  // Read from Registers with type "Integer" (given from manual)
  if (!ModbusRTUClient.requestFrom(1, INPUT_REGISTERS, 0x0040, 1)) {
    Serial.print("failed to read registers! ");
    Serial.println(ModbusRTUClient.lastError());
  } else {    
    while(ModbusRTUClient.available()) {
           Serial.println(ModbusRTUClient.read(), DEC); 
    }
    Serial.println("----");

  }

  // Read from Registers with type "IEEE" (given from manual)
    if (!ModbusRTUClient.requestFrom(1, INPUT_REGISTERS, 0x1038, 2)) {
    Serial.print("failed to read registers! ");
    Serial.println(ModbusRTUClient.lastError());
  } else {    
    while(ModbusRTUClient.available()) {
           Serial.println(ModbusRTUClient.read(), BIN); 
    }
    Serial.println("----");

  }

  delay(5000);
  
}

Both of your solutions are working! Thanks alot! Now I just have to rewrite it, so that it's converting the data from ModbustRTUClient.read().

BTW, is it gcc float lib for AVRs fully compatible with IEEE 754 latest standard? l.e. subnormal numbers, infinite, NaN...

If it is not, direct conversion may not be reliable. I doubt OP will fall into these cases, but anyway, just a though.

Hi
Would it not be easier to just declare a 2 byte array. Get your data from modbus RTU in the byte array and then store the byte array in your high level environment? There you can have much easier options to convert the byte array to float values you need. If you are not consuming floats on Arduino then why bother converting it on Arduino?

Even without having known the full codes of your sketch, it is possible to know (from the following information of IEEE-754 Structure) the clues of inserting leading zeros with the register values.

1. IEEE-754 Format for 32-bit Floating Point Number
IEEE754Float.png
Figure-1:

2. Figure-1 demands that the input binary data should be a 32-bit number and must align with it.

3. Your numbers are:
(1) Higher 16-bit:
100001001001000
==>0100001001001000

(2) Lower 16-bit
1010001111011
==> 0001010001111011

4. Now, the composite IEEE-754/binray32 formatted (from Step-2) 32-bit number that conforms with Fig-1 is:
01000010010010000001010001111011
==>0100 0010 0100 1000 0001 0100 0111 1011
==> 0x4248147B

5. Codes to extract float number from the data of Step-4.

union
{
  float x;
  unsigned long y;
}data;

void setup() 
{
  Serial.begin(9600);
  data.y = 0x4248147B;
  Serial.println(data.x, 2);  //shows: 50.02
  
}

void loop() 
{

}

IEEE754Float.png