Reform float32 from two unsigned int16 [SOLVED]

Hi

I'm stuck trying to reform a 32 bit float from 2 unsigned 16 bit ints. I'm using an arduino as a modbus master to read a flow meter. the registers in the meter are all 16 bit but the device uses 2 registers to represent a 32 bit float. I can read the 16 bit ints but I' having difficulty reforming them to a 32 bit float (using big endian)

I came across this as a suggestion but I cant make it work:

#include

uint16_t regs[2]; //reg[0] & reg[1] contain the 16 bit ints

long int num = ((reg[0] & 0xFFFF) << 16) | (reg[1] & 0xFFFF);
float numf;
memcpy(&numf, &num, 4);

numf should now contain the reformed float but it remains 0

any ideas?

Richard

Despite the fact that you are very close to getting that code to work I would still recode it using a union.

This part should have a cast to unsigned long so you aren't shifting your data off the end of your int:
((reg[0] & 0xFFFF) << 16)

What are the values of reg[0] and reg[1]?
What value do you expect in numf? (approximately if you don't know the actual number)

Does the flow meter use IEEE754 floating-point values? Are the bytes in each 16-bit integer in the right order?

This flowmeter with MODBUS protocol stores all 8, 16 and 32-bit values in 8-bit registers ... are you sure your flow meter doesn't do the same? AquaMaster 3 MODBUS

Do you have a datasheet for the flow sensor. I find it hard to believe that it uses floating point values, natively.

Thanks for the replies - still working on it!

The registers on the flow meter store data as 16 bit unsigned
They use two registers to make a 32 bit float

so if reg[0] = 0x41D1 = 16849 (not relevant)
& reg[1] = 0x94BD = 38077

The Float32 = 0x41D194BD = 26.1976

The float is in standard IEEE 754 format

The device uses big endian as above
The int values of reg[0] & reg[1] are not relevant as reg[0] contains 1 sign bit, 8 exponent bits and the first 7 bits of the fraction of the 32 bit float. The other 16 bits of the fraction are in reg[1]

I thought it would be straight forward to combine 2 16 bit words to make a 32 bit float but so far no luck!

I have looked at union but was unable to see how to make it do the correct reforming

The initial code I posted was said to do the job but I cant make it work!

http://aravaniskostas.com/2012/04/24/two-16bit-integers-that-represent-one-32bit-float/

Richard

Have you fixed the problem of shifting data off the end of your integers?

uint16_t regs[2] = {0xAAAA, 0xBBBB};    //reg[0] & reg[1] contain the 16 bit ints

// I would expect ((reg[0] & 0xFFFF) << 16) to result in 0x0000 so this expression:
long int num = ((reg[0] & 0xFFFF) << 16) | (reg[1] & 0xFFFF);
// will result in 'num' being 0x0000BBBB

// You can fix this with judicious choice of data types:
long int num = ((reg[0] & 0x0000FFFFUL) << 16) | (reg[1] & 0xFFFF);

// Or with type casting:
long int num = (((unsigned long)reg[0] & 0xFFFF) << 16) | (reg[1] & 0xFFFF);

// Either should produce the pattern you want: 0xAAAABBBB
// You can even use them both:
long int num = (((unsigned long)reg[0] & 0x0000FFFFUL) << 16) | (reg[1] & 0xFFFF);

// Note:  0xFFFFUL and 0x0000FFFFUL mean the same thing

// Actually, since 16-bit integers are already limited to 16 bits, you don't need to mask them.
long int num = (unsigned long)reg[0] << 16) | reg[1];
// This works on UNSIGNED integers.  With signed integers you get issues with sign extension 
// when they get promoted to a larger type.

The code from this thread works, however it doesn't differentiate ±inf or ±0:
(number of decimal places for print was set to 13)

[b] Special#     HEX        Serial Monitor[/b]
 NaN          7FFFFFFF   nan
 Infinity     7F800000   inf
-Infinity     FF800000   inf
 0.0          00000000   0.0000000000000
-0.0          80000000   0.0000000000000
 Your Number  41D194BD   26.1976261138916

Other reference: IEEE 754 Converter

Many thanks! Problem solved!

I took John Wasser's suggestion
The ints in my registers were unsigned so the & 0xFFFF was unnecessary

The problem was solved by making regs[0] an unsigned long int

so I replaced

long int num = ((regs[0] & 0xFFFF) << 16) | (regs[1] & 0xFFFF);

with

long int num = (((unsigned long)regs[0] << 16) | regs[1]);

and everything worked fine!

I also tried Delta_G's suggesstion with

union {
float asFloat;
int asInt[2];
}
temp; // or some other name that doesn't clash in your program...

temp.asInt[0] = regs[0];
temp.asInt[1] = regs[1];

float numf = temp.asFloat;

but this returned numf = 0 not sure why but as I have one method working I think I will stick to it!

Many thanks for your help

Richard

Just to point out that both the union method and casting through memcpy method ignore the sign when dealing with the special numbers. May not be an issue, unless special handling is required in your MODBUS specification, or if it's important to know direction of flow when special numbers occur.

memcpy method:

uint16_t regs[12] = {0x41D1, 0x94BD,  // 26.1976261138916
                     0x7FFF, 0xFFFF,  // nan
                     0x7F80, 0x0000,  // inf
                     0xFF80, 0x0000,  // -inf
                     0x0000, 0x0000,  // +0
                     0x8000, 0x0000   // -0
                    };

float f32(uint16_t u1, uint16_t u2)
{
  long int num = u1 << 16 | u2;
  float numf;
  memcpy(&numf, &num, 4);
  return numf;
}

void setup() {
  Serial.begin(115200);
  float numf = f32(regs[0], regs[1]);
  Serial.println(numf, 13);
  numf = f32(regs[2], regs[3]);
  Serial.println(numf, 13);
  numf = f32(regs[4], regs[5]);
  Serial.println(numf, 13);
  numf = f32(regs[6], regs[7]);
  Serial.println(numf, 13);
  numf = f32(regs[8], regs[9]);
  Serial.println(numf, 13);
  numf = f32(regs[10], regs[11]);
  Serial.println(numf, 13);
}

void loop() {
}

thanks for that

It shouldn't be a problem for me

The registers I am interrogating all contain straight forward numbers for flow rate, energy rate, temperature etc

So far they seem to be coming across fine using modbus!

Richard

float f32(uint16_t u1, uint16_t u2)
{
  long int num = u1 << 16 | u2;
  float numf;
  memcpy(&numf, &num, 4);
  return numf;
}

dlloyds code above does not work. As John Wasser has stated several times you are shifting u1 into oblivion.

float f32(uint32_t u1, uint16_t u2)
{
  long int num = u1 << 16 | u2;
  float numf;
  memcpy(&numf, &num, 4);
  return numf;
}

Does not work. As John Wasser has stated several times you are shifting u1 into oblivion.

... unless you're using the SAM (Due) where oblivion starts at bit32. Oh well, I'll seriously have to get an AVR one day and practice coding that's more portable (thanks).