0

I am trying to read a SDM220 meter on my solar setup. I've been reading it using Modbus SCADA, so know it all works ok. But I want to read it with my Arduino Mega 256. My problem is that when I run the code below the final value MtrKWh , is not float value. it's actual value is printed. Here is a copy of the output.

17241 MtrKWh1 1129906176 MtrKWh1<<16 57737 MtrKWh2 1129963913 MtrKWh3 11299639.00 MtrKWh 2

the value '1129963913' MtrKWh3,

if I turn it into Binary is 01000011010110011110000110001001,

if I convert this to binary using https://www.h-schmidt.net/FloatConverter/ it gives me the value

217.881,

the very value I expect. Am I missing some brackets somewhere? So how do I get MtrKWh to print its float value? I have left a lot of commented out bits to show the several things I have tried. Sorry if it appears messy, I will tidy it up once I solve this problem. I'm sure it is something simple, but, I haven't been able to see it so far. Any sugestions would be appreciated.

    // MtrV, MtrC, MtrW, MtrKWh
    //void AddressRegistry_0()
    result = sdm220_modbus.readInputRegisters(0x48, 2);
    //while (!result == 0) {
    // delay(10);
    // result = sdm220_modbus.readInputRegisters(0x156,4);
    //}
   if (result == sdm220_modbus.ku8MBSuccess)
   {
    // 1 Serial.println((node.getResponseBuffer(0x0D) + node.getResponseBuffer(0x0E) << 16)/100.0f);
    // 2 pvpower = ((long)node.getResponseBuffer(0x03) << 16 | node.getResponseBuffer(0x02)) / 100.0f;
    // 3 long pressure = ((pressure_data_high << 16) | pressure_data_low) / 4;
    // 1 working ?? MtrKWh1 = (((long)sdm220_modbus.getResponseBuffer(0x0) + sdm220_modbus.getResponseBuffer(0x1) << 16)/6.f);
    // 2 working ?? MtrKWh1 = ((long)sdm220_modbus.getResponseBuffer(0x0) << 16 | sdm220_modbus.getResponseBuffer(0x1)) / 100.3f;
    // 3 working MtrKWh1 = (( sdm220_modbus.getResponseBuffer(0x0)  << 16) |  sdm220_modbus.getResponseBuffer(0x1))  / 16.3f;
    //
   // float MtrKWh0 =((float)(sdm220_modbus.getResponseBuffer( 0) << 16 | sdm220_modbus.getResponseBuffer( 1)));
    //      Serial.print(MtrKWh0);
    //    Serial.print(" MtrKWh0     ");

signed long MtrKWh1 = sdm220_modbus.getResponseBuffer(0x0);
Serial.print(" ");
Serial.print(MtrKWh1);
Serial.print(" MtrKWh1");

MtrKWh1 = MtrKWh1 << 16;
Serial.print(" ");
Serial.print(MtrKWh1);
Serial.print(" MtrKWh1<<16");

word MtrKWh2 = sdm220_modbus.getResponseBuffer(0x1);
Serial.print(" ");
Serial.print(MtrKWh2);
Serial.print(" MtrKWh2");

signed long MtrKWh3 = MtrKWh1 | MtrKWh2;
Serial.print(" ");
Serial.print(MtrKWh3);
Serial.print(" MtrKWh3");

float (MtrKWh) = float (MtrKWh3) / 10000.0;
Serial.print(" ");
Serial.print(MtrKWh);
Serial.print(" MtrKWh     ");

//uint32_t x = 0x3f322e3f;
//float y = *(float*)&x;
//Serial.print (y, 6);
// Serial.print(" MtrKWhxxx ");

   }
   else
   {
    rs485DataReceived = false;
    Serial.println("Fail sdm220_modbus.readInputRegisters(0x0, 1) ");
   }
5
  • You have your answer commented out in the source code. Conversion of type using pointers, not casting.
    – Majenko
    Commented May 15, 2017 at 22:43
  • Or you could use reinterpret_cast if the compiler supports it - I've never tried.
    – Mark Smith
    Commented May 15, 2017 at 22:48
  • 1
    Casting will convert an integer into a float with the same numeric value (1000 => 1000.000). Instead what is wanted is the overlaying of a float's structure on the memory of the integer value, which is what the nasty type-punning in the commented section is doing. Personally I'd use a union to overlay a 4-byte array over a float.
    – Majenko
    Commented May 15, 2017 at 22:54
  • @majenko reinterpret_cast does exactly that. I think it's a C++11 thing though, and I don't know whether the Arduino compiler supports it.
    – Mark Smith
    Commented May 16, 2017 at 6:11
  • I put this reinterpret_cast<float>(MtrKWh) in the code, i get error during complie invalid cast from type 'long int' to type 'float' I thought that was he point of reinterpret_cast, to change type? Have I got the syntax right? signed long (MtrKWh) = float (MtrKWh3) / 10000.0; Serial.print(" "); Serial.print(MtrKWh); reinterpret_cast<float>(MtrKWh); Serial.print(" MtrKWh ");
    – bres55
    Commented May 16, 2017 at 9:18

2 Answers 2

1

OK. So I never got the reinterpret_cast, thing to work. Went with Majenko's solution. This code goes above Setup

// UNION, used to re-jig float
union ifloat {
uint8_t bytes[4];
float val;
};
// END UNION

And this is the modified code, with all the comments and bug print statements removed.

  // MtrV, MtrC, MtrW, MtrKWh
 //void AddressRegistry_0()
 result = sdm220_modbus.readInputRegisters(0x48, 2);
 if (result == sdm220_modbus.ku8MBSuccess)
 {
signed long MtrKWh1 = sdm220_modbus.getResponseBuffer(0x0);
word MtrKWh2 = sdm220_modbus.getResponseBuffer(0x1);
byte msb3 = MtrKWh1;
byte msb4 = MtrKWh1 >> 8;
byte msb1 = MtrKWh2;
byte msb2 = MtrKWh2 >> 8;
ifloat f;
f.bytes[3] = msb4;
f.bytes[2] = msb3;
f.bytes[1] = msb2;
f.bytes[0] = msb1;
Serial.println(f.val,3 );
  }
 else
 {
rs485DataReceived = false;
Serial.println("Fail sdm220_modbus.readInputRegisters(0x0, 1) ");
 }

And it is giving me the right numbers! Thanks for input to all who did, it helps some times to be show which door to walk through. Majenko, if you have time, and inclination, it would really help my understanding if you could describe your code. I am using it, but not sure how it works,or what most of it is doing. If you have the time.

1
  • Description added to my answer.
    – Majenko
    Commented May 16, 2017 at 15:30
1

You need to convert the format by overlaying one type on top of the other, rather than casting from one type to another.

1000 as an integer cast to a floating point value is still 1000.

You have an example of how it can be done in comments:

//float y = *(float*)&x;

However this method is called type punning and is frowned upon (there are compiler options that can prevent it being used). Instead using a union is a better option:

union ifloat {
    uint8_t bytes[4];
    float val;
};

void setup() {
    ifloat f;
    Serial.begin(115200);
    f.bytes[3] = 0x43;
    f.bytes[2] = 0x59;
    f.bytes[1] = 0xE1;
    f.bytes[0] = 0x89;
    Serial.println(f.val, 4);

}

void loop() {
}

So you can get the byte order right your current numeric value of 1129963913 is 0x4359E189 in hexadecimal. Byte 3 is therefore the most significant byte and byte 0 the least significant. Note that it can change from system to system depending on the endianness of the chip.

How it works is quite simple. You have 4 bytes in memory, and the union just gives you different views of those 4 bytes.

You can either see them as four individual bytes (f.bytes[0-3]) or as a floating point value (f.val).

You can understand more about how the four bytes describe the floating point number by understanding more about the IEEE 754 Single Precision Floating Point Format.


You can achieve the same thing using reinterpret_cast, but that has to be done using pointers, not the values, which makes it a little more complex to understand:

// Your incoming 32-bit value
uint32_t l = 1129963913; 

// A pointer to the same memory with a different type
float *f = reinterpret_cast<float *>(&l);

// Print the *content* of the pointer interpreted as a float.
Serial.println(*f, 3); 
1
  • Thanks for response. I think I saw a similar solution on my searches earlier. I found it difficult to understand/impliment. I have 2 words, MtrWhr1 and MtrWh2, do I need to convert these two words to bytes, then to hex, then use the code above to get the float conversion?
    – bres55
    Commented May 15, 2017 at 23:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.