c++embeddedesp32modbusrs485

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.


Solution

  • 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;
    }