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
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
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!
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
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.
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).