Combining and Converting to HEX to Floating point

I was able to get the data from Schneider PM5560 power meter via modbus RS485. but having difficulty in interpreting that data.
i wanted to read register 3035 which has a Data type of float 32. this is the code i am using

#include<ModbusMaster.h>


#define MAX485_DE 3
#define MAX485_RE_NEG 2


ModbusMaster node;

void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
}

void setup() {
  
  Serial.begin(9600);
 // Serial2.begin(9600);

  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);

  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  //My slave uses 9600 baud
  delay(1000);
  Serial.println("starting arduino: ");
  Serial.println("setting up Serial ");
  Serial.println("setting up RS485 port ");
//  slave id
 node.begin(1, Serial);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
}

void loop() {
 uint16_t result;
  
//slave address and length
  result = node.readHoldingRegisters(3035 ,2);

  if (result == node.ku8MBSuccess)
  {
      Serial.print("V avg : ");
      Serial.println(node.getResponseBuffer(0),HEX);
      Serial.println(node.getResponseBuffer(1), HEX);
  }

  Serial.print("\n");
  
  delay(500);
}

this is the output i am getting

image

Now if we combine hex values 437A and DD60 we get 437ADD60 and converting it to floating point we get 250.860, which is required as meter is giving this value on its screen.

Please suggest changes in code to get the required data.

void
setup (void)
{
    Serial.begin (9600);

    float f;
    uint16_t *p = (uint16_t*) & f;

    *p++ = 0xDD60;
    *p++ = 0x4374;

    Serial.println (f);
}

void
loop (void)
{
}

2 Likes

Thanks the code works now. just one more thing how can I remove the box values(garbage) from the output

#include<ModbusMaster.h>


#define MAX485_DE 3
#define MAX485_RE_NEG 2


ModbusMaster node;

void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
}

void setup() {
  
  Serial.begin(9600);
 // Serial2.begin(9600);

  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);

  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  //My slave uses 9600 baud
  delay(1000);
  Serial.println("starting arduino: ");
  Serial.println("setting up Serial ");
  Serial.println("setting up RS485 port ");
//  slave id
 node.begin(1, Serial);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);

   
}

void loop() {



 uint16_t result;
  
//slave address and length
  result = node.readHoldingRegisters(3035 ,2);

  if (result == node.ku8MBSuccess)
  {

    float f;
    uint16_t *p = (uint16_t*) & f;

    *p++ = node.getResponseBuffer(1);
    *p++ = node.getResponseBuffer(0);

    Serial.println (f);

  }

  Serial.print("\n");
  
  delay(500);
}

this is the output

image

You need to use a different serial connection for the Serial Monitor and the Modbus.

Ok thanks let me do that.

Thanks @bobcousins, i used seoftwareserial and designate other pins to Rx, Tx, and it started working fine.
@gcjr
I want to read multiple registers but as soon as I try reading other register all the register values comes out to be 0. (Note: only at the start after 10,15 values of 0 actual values do comes up once only).

this is my code

#include<ModbusMaster.h>
#include <SoftwareSerial.h>

#define MAX485_DE 3
#define MAX485_RE_NEG 2

SoftwareSerial mySerial(9,8);

ModbusMaster node;

void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
}

void setup() {
  
  mySerial.begin(9600);
  Serial.begin(9600);
 // Serial2.begin(9600);

  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);

  // Init in receive mode
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  //My slave uses 9600 baud
  delay(1000);
  Serial.println("starting arduino: ");
  Serial.println("setting up Serial ");
  Serial.println("setting up RS485 port ");
//  slave id
 node.begin(1, mySerial);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);

   
}

void loop() {



 uint16_t result;
  uint16_t res;

  
//slave address and length
  result = node.readHoldingRegisters(3035 ,2);
  res = node.readHoldingRegisters(3009 ,2);


  if (result == node.ku8MBSuccess)
  {

    float f;
    uint16_t *p = (uint16_t*) & f;
   Serial.println("Voltage avg : ");
    *p++ = node.getResponseBuffer(1);
    *p++ = node.getResponseBuffer(0);

    Serial.println (f);

  }
  delay(500);

  if (res == node.ku8MBSuccess)
  {

    float f1;
    uint16_t *p = (uint16_t*) & f1;
        Serial.println("Current avg : ");
    *p++ = node.getResponseBuffer(1);
    *p++ = node.getResponseBuffer(0);

    Serial.println (f1);

  }

  Serial.print("\n");
  
  delay(500);
}

output
image

Sigh ..... Don't use unions or pointer casts for type punning

would you post code that does it the proper way?

Per the above link (and given it unlikely that any Arduino core supports C++20):

void setup (void) {
  Serial.begin (115200);
  delay(2000);

  float f;
  uint16_t i[] = {0xDD60, 0x4374};
  memcpy(&f, i, sizeof(i));
  Serial.println (f, 6);
}

void loop (void) {
}

Result:

244.864746

Confirmed here, after swapping bytes for Little Endian representation.

thanks

can you explain how memcpy() might it be different on different machine/architectures?

void
memcpy (void *dst, void *src, size_t nByte)
{
    for (int n = 0; n < nByte; n++)
        *dst++ = *src++;
}

Does that code compile? I'm pretty sure you can't dereference a 'void *'.

i wish you could have noted this issue but tried answering the question

i may find my answer here, but i think it's attempting to be efficient by copying words and needs to deal with the stray bytes at the ends of the copy regions

seems like a lot of code just to copy 4 bytes

I can’t speak to the platform-specific implementations of memcpy(). The point of my Post #7 was that casting a ‘float *’ to ‘uint16_t *’ and then dereferencing the latter is not legal C++ code. As pointed out in the post I linked, the rules of reinterpret_cast on pointers are complex, especially if the result of the cast is dereferenced.

There are a few cases where it’s legal and of those the only ones I use are:

  1. Cast a pointer to a ‘void *’ and then back to the original pointer type. You may then dereference it once it’s cast back. This might be necessary when using ‘C-type’ interfaces such as the API calls in ESP32’s FreeRTOS. Also, some platforms and libraries support defining a ‘void *’ parameter that’s passed to an ISR or Callback Function. It can be cast back to a pointer to the original type in the ISR / Callback and then deferenced. Obviously, the ISR / Callback must know what that original type was.

  2. Cast a pointer to a ‘uint8_t *’ (aka a Byte pointer). You may then deference that to access the byte representation of the original object (both examine and modify). This is why memcpy() is legal. Note that I don’t think it’s legal to go the other way … you can’t cast a byte array to a ‘float *’ and then dereference it as a float.

@gfvalvo @gcjr, thanks for your input can you plz look into my code at post#6 and guide how can I update my code to read multiple registers.

if you don't mind, i'd like to understand this better. i'm not trying to pick a fight

especially after looking at torvald's code, it looks like the issue is alignment, that if not careful, a pointer might be improperly aligned for some specific variable type. for example, a pointer to a float is set to a pointer to a char where the char pointer is not word (4 byte) aligned. is this correct?

and if doing things like this is illegal, why does the compiler accept without warning the use of casts such as

char c;
float *f = (float*) &c;

does it really come down to some knowledgeable review saying "you shouldn't do that"?

I would think the result of doing that is platform-specific. It might range for inefficient access of the float variable all the way to the hardware throwing and exception.

I know there are dozens of compiler flags that can be set / unset. Perhaps there's on (or a combination of ones) that will kick out a warning for that. Try a web search for the warning "dereferencing type-punned pointer will break strict-aliasing rules"

Good question, perhaps somewhat.

It's not illegal, but undefined behaviour. There are many undefined behaviours. I think a conforming C++ compiler is not required to generate warnings for undefined behaviour.

There are many 3rd party C++ analysis tools which do a much better job flagging potentially risky code, but even those may not cover everything.

In a recent project high integrity project I worked on, you can imagine how much fun it was checking every line of code for undefined and implementation defined behaviour, analysing the results, modifying the code or writing a justification of why the code was acceptable.

it's hard for me to imagine it being hardware/platform specific.

isn't it more related to doing something like

char   arr [10];                     // assume work aligned
float *f = (float*) & arr [1];

and in the above case, i don't believe it works even when using memcpy()

Why not? A 32-bit processor accesses memory 4 bytes at a time on 32-bit address boundaries. So, accessing a misaligned float will require two memory accesses instead of one ---> less efficient.

Also, don’t you think that at 32-bit (or 64-bit) processor with a hardware Floating Point peripheral could possibly get upset (even to to the point of throwing an exception) accessing a misaligned float?

Did you try it? It works as expected for me using memcpy():

void setup() {
  Serial.begin(115200);
  delay(2000);

  char   arr[] = {0x00, 0x60, 0xDD, 0x74, 0x43, 0x00, 0x00};
  float f;
  memcpy(&f, arr + 1, sizeof(float));
  Serial.println(f, 4);
}

void loop() {
}

Output:

244.8647

yes, absolutely. but isn't that an alignment problem not a hardware/platform problem?

yes of course
but you're copying the data to a float, not (misusing) an unaligned pointer to a float as a demonstration of the problem i believe is why doing something like this is "illegal" or "undefined"


is it really just about alignment?