Modbus RTU Protocol float help

Apologies if posted in the wrong place, first time.

I'm trying to read a voltage value in RapidScada from Arduino via Modbus RTU but I only get integers on the SCADA side when the Arduino is outputting floats.

I'm very new to Modbus, any help would be greatly appreciated

Link to Modbus Library

Rapidscada Documentation

Uno R3 board as slave/server, Rapidscada as master/client


#include <modbus.h>         // MODBUS protocol
#include <modbusDevice.h>   // devices
#include <modbusRegBank.h>  // register bank
#include <modbusSlave.h>    // slave addresses

modbusDevice regBank;  // object - stores data
modbusSlave slave;     // modbus slave protocol handler

// variables defined area

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);  // begin serial monitor at 9600 baudrate
  regBank.setId(5);    // set slave device id (can be any number) for arduino to talk to modbus master device

  regBank.add(30001);  // analog input device 1 (30000 modbus register/address)

  regBank.add(2);  // digital output for led

  regBank.add(10003);  // digital input for pushbutton (10000 modbus register/address)

  slave._device = &regBank;  // assign modbus slave device to the protocol handler

  slave.setBaud(9600);  // initialise serial port for comms at 9600 baudrate

  pinMode(3, INPUT);  // set pin 3 to input - pushbutton
}

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

  float sensor1 = analogRead(0);  // variable to store analogIn pin value - sensor 1 (pot)
  delay(20);                      // 20ms delay between sensor readings

  boolean digitalInput3 = digitalRead(3);  // variable to store if digital pin input is true/false - pushbutton

  int digitalOutput2 = regBank.get(2);  // variable to store digital pin 2 data/state given from scada - led

  float sensor1read = (sensor1 * 5.0) / 1024.0;  // analogIn pin value (0-1023) multiplied by 5V and divide by 1024 to convert value into readable voltage (0-5V) for 10bit resolution ADC

  // test to serial monitor
  //Serial.println(sensor1read);
  //Serial.println(digitalInput3);


  if (digitalOutput2 >= 1 && digitalRead(2) == LOW)  // if scada button pressed & led is off - turn on
    digitalWrite(2, HIGH);
  if (digitalOutput2 <= 0 && digitalRead(2) == HIGH)  // if scada button pressed & led is on - turn off
    digitalWrite(2, LOW);


  // analog values
  regBank.set(30001, sensor1read);  // regBank function to write data on rtu protocol addresses - analog input values

  // digital inputs
  regBank.set(10003, digitalInput3);  // regBank function to write data on rtu protocol addresses - digital input values

  delay(100);   // delay to ensure enough time for values to be sent
  slave.run();  // function to ensure running slave device
}`

The Modbus protocol can only transport 1bit coil and 16bit register values. Some manufacturers use two holding registers (2 x 16bit = 32 bit) to encode a single precision float value but that has to be documented because it isn't covered by the standard.

You should post a link to the library you're using.

What Arduino board are you using?

Most PLCs use such high numbers to distinguish between the register types but actually use lower numbered registers. It depends on the manufacturer which method it implements but the manual should tell you that.

Hi pylon, thanks for taking the time to respond.

The board is an UNO R3 as a slave, the Rapidscada is the master.

Link to Modbus Library

Extra info on the library

Where I came across the library, someone had success with floating points with it

Any help or insights is very appreciated. Modbus is new territory but I'm trying to learn.

Trying to read analogue inputs from a power factor correction circuit I'm designing for a project, so I need to read floating points for voltage and current values. The code posted is just a mockup for the Modbus testing, trying to get a crude framework I can build on.

Thanks

Don't use that library on an UNO, it's using rather much RAM, you'll probably run into problems because of that.

I recommend this library for an UNO.

As I wrote, that's not a question of the Arduino library but of the other side (the Rapidscada in your case). Consult the manual of that system about how it expects a float to be transferred by 16 bit integers. You might also post a link to that manual so we may help you find that out.

Often these values are transferred by integers as you can define the unit, it doesn't have to be V, it can be mV.

Thankyou for the link to a more suitable library. I will have a look at that in more detail. The advanced slave example looks more promising.

I have edited the post to include the Rapidscada documentation, I've posted it in the reply too just incase.

Rapidscada documentation

I will try to find the information on the Rapidscada side for the correct method for receiving floats.

I understand what you mean about transferring integers and using mV, unfortunately my range exceeds this in certain circumstances and I'm looking for a high degree of accuracy but I will take the advice into consideration. It may become useful.

Do you know any good sources for teaching myself Modbus protocol? I have found various sources but I thought I would ask incase you know of more robust sources of information.

Thanks again for taking the time to reply. It has been very helpful

It seems your PLC supports any variant, even some which are contrary to the standard. So you may transfer a float this way:

float fval = 1.5;
uint32_t dval = *((uint32_t *) &fval);
uint16_t reg1 = dval >> 16;
uint16_t reg2 = dval & 0xFFFF;

But you know that you loose accuracy if you change to floats from integers, don't you?

Thankyou for that. That gives me some solid information to work with. I see what you mean about two holding registers to transfer the information.

I wasn't aware that accuracy would be lost during the change from integer to float but that's great you mentioned that. Information like that is very valuable at this stage.

I apologise for the lack of technical knowledge on my part. My skillset is very much electrical/electronic. I'm trying to expand it into software through the project.

Thanks for taking the time to help, I appreciate it.

Could you explain the 'dval' part?

I think i understand the rest of it.

I type-cast a float pointer into a pointer to a 32 bit integer and de-reference it to get an exact copy of the float bits in an integer format. That works because a float is also represented by 32 bits (the bits just have another meaning). Once I have the bits of the float in an integer it's much easier to split that up into two 16 bit values.

That is an excellent explanation. Thankyou for all the help. I will run some tests and see how I get on.

This worked perfectly. Thankyou pylon. Really helped. I would have replied sooner but I've been studying C++ alot to get better. Thanks again.

// store voltage value in input register and read
//------------------------------------------------------------------------------------------//

// float voltage value cast to 32bit int and split between 2x 16bit ints
uint32_t dualValue1 = *((uint32_t *)&voltage);
uint16_t register1 = dualValue1 >> 16;  // right shift >> by 16bits
uint16_t register2 = dualValue1 & register1;

// write values to input registers for voltage value
modbusTCPServer.inputRegisterWrite(0x00, register1);
modbusTCPServer.inputRegisterWrite(0x01, register2);

// read values from input registers for voltage value
modbusTCPServer.inputRegisterRead(0x00);
modbusTCPServer.inputRegisterRead(0x01);

//------------------------------------------------------------------------------------------//

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.