Chung Phạm
Chung Phạm

Reputation: 11

Reading different data types via Modbus RTU on ESP32

I am currently running Modbus RTU via RS485 on ESP32 to read data from the Modbus Slave emulator. The code is able to read/write from the virtual PLCs using the ArduinoModbus library (I had to adjust a few parts since it doesn't support ESP32). However, the library still can't read/write other data types (float, long, double...) other than binary and int. I'm using PlatformIO in VSCode. ArduinoModbus ArduinoR485 Modbus Slave emulator

Here is the modified read() function that I'm testing in the library:

long ModbusClient::read()
{
  if (_available <= 0) {
    return -1;
  }

  long result = -1;

  switch (_type) {
    case COILS:
    case DISCRETE_INPUTS:
      result = ((uint8_t*)_values)[_read];
      if (result != -1) {
      _available--;
      _read++;
      }
      break;

    case HOLDING_REGISTERS:
    case INPUT_REGISTERS:
      if (_available >= 2) {
        result = ((uint16_t*)_values)[_read];
        result = (result << 16) | ((uint16_t*)_values)[_read + 1];
        if (result != -1) {
          _available -= 2;
          _read += 2;
        }
      }
      break;

    default:
      break; 
  }
  return result;
}

P/S: Alternatively, the comments suggest using another function in the library, but I'm still studying it:

long ModbusClient::holdingRegisterRead(int id, int address)
{
  uint16_t value;

  modbus_set_slave(_mb, id);
  
  if (modbus_read_registers(_mb, address, 1, &value) < 0) {
    return -1;
  }

  return value;
}

My main() loop:

void loop() {
  readHoldingRegisterValues();

  delay(5000);
  Serial.println();
}

void readHoldingRegisterValues() {
  Serial.print("Reading Input Register values ... ");

  // read 10 Input Register values from (slave) id 1, address 0x00
  if (!ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0x00, 10)) {
    Serial.print("failed! ");
    Serial.println(ModbusRTUClient.lastError());
  } else {
    Serial.println("success");

    while (ModbusRTUClient.available()) {
      Serial.print(ModbusRTUClient.read());
      Serial.print(' ');
    }
    Serial.println();
  }
}

Here is the output:

1111170744 0 0 0 0

From my understanding, a register can only contain 16-bit value. Float and double are 32-bit and 64-bit, so I'm trying to read each register in turn, then combine them to get the result.

Upvotes: 0

Views: 304

Answers (2)

Chung Phạm
Chung Phạm

Reputation: 11

Replace holdingRegisterRead() function with a template function:

#include "ModbusClient.h"
using namespace std;

template<typename T>
T ModbusClient::holdingRegisterRead(int id, int address, ByteOrder byteOrder){
  uint16_t values[4];
  int numRegisters = 0;

  if(is_same<T, uint16_t>::value){
    numRegisters = 1;
  } else if(is_same<T, float>::value){
    numRegisters = 2;
  } else if(is_same<T, double>::value){
    numRegisters = 4;
  } else {
    throw runtime_error("Unsupported data type");
  }
  modbus_set_slave(_mb, id);
  if(modbus_read_registers(_mb, address, numRegisters, values) < 0){
    return -1;
  }
  if(is_same<T, uint16_t>::value){
    return static_cast<uint16_t>(values[0]);
  } else if (is_same<T, float>::value) {
    uint32_t temp = ((uint32_t)values[0] << 16) | values[1];
    if (byteOrder == BIGEND){
      return *reinterpret_cast<float*>(&temp);
    } else if(byteOrder == LILEND){
      temp = (temp << 24) |
            ((temp << 8) & 0x00FF0000) |
            ((temp >> 8) & 0x0000FF00) |
            (temp >> 24);
      return *reinterpret_cast<float*>(&temp);
    } else if(byteOrder == BSWAP){
      temp = ((temp << 8) & 0xFF00FF00) |
            ((temp >> 8) & 0x00FF00FF);
      return *reinterpret_cast<float*>(&temp);
    } else if(byteOrder == LSWAP){
      temp = (temp << 16) | (temp >> 16);
      return *reinterpret_cast<float*>(&temp);
    }
  } else if(is_same<T, double>::value){
    uint64_t temp = ((uint64_t)values[0] << 48) |
                    ((uint64_t)values[1] << 32) |
                    ((uint64_t)values[2] << 16) |
                    values[3];
    if(byteOrder == BIGEND){
      return *reinterpret_cast<double*>(&temp);
    } else if(byteOrder == LILEND){
      temp = (temp << 56) |
            ((temp << 40) & 0x00FF000000000000) |
            ((temp << 24) & 0x0000FF0000000000) |
            ((temp << 8 ) & 0x000000FF00000000) |
            ((temp >> 8 ) & 0x00000000FF000000) |
            ((temp >> 24) & 0x0000000000FF0000) |
            ((temp >> 40) & 0x000000000000FF00) |
            (temp >> 56);
      return *reinterpret_cast<double*>(&temp);
    } else if(byteOrder == BSWAP){
      temp = ((temp << 8) & 0xFF00FF00FF00FF00) |
            ((temp >> 8) & 0x00FF00FF00FF00FF);
      return *reinterpret_cast<double*>(&temp);
    } else if(byteOrder == LSWAP){
      temp = (temp << 48) |
            ((temp << 16) & 0x0000FFFF00000000) |
            ((temp >> 16) & 0x00000000FFFF0000) |
            (temp >> 48);
      return *reinterpret_cast<double*>(&temp);
    }
  }
  return 0;
}

Upvotes: 0

Michael_Y
Michael_Y

Reputation: 43

I suppose,

_values

is a uint16_t array, stored as a class field or as a static variable (I'm not familiar with this library). Since modbus doesn't transfer data types, they're only defined in your program and in a client, you can re-write this _values variable and it's usage in library as it suits you. For example, you can change it for a union or a struct:

struct {
   uint16_t uintValues[REG_NUM];
   int16_t  intValues[REG_NUM];
   float    floatValues[REG_NUM];
   etc...
} sValues;

Then you can pass uintValues or floatValues member of sValues (_values) variable in read/write functions depending on which registers with which data types is requested by a client.

Upvotes: 0

Related Questions