Need help with communicating between 2 RS-485 Energy Meters

Hi everyone,
I had recently made a project which involves an Arduino Opta communicating with 2 energy meters namely the Selec EM2M and EM4M energy meters over RS-485 protocol and the values recieved are visualized on Arduino IoT Cloud Dashboard.

The project otherwise works fine but it has one issue, when reading the first Input Register from any of the 2 meters i.e. the 0x14 for the EM2M & 0x00 for the EM4M (according to my code) i get the error "Failed to read register at address XX from slave Y-Connection Timed out" (X being the address of the input register and Y being the slave id). This happens atleast 5-6 times in a minute. But any register following the first are read fine and i've never got an error for those.

Below is the code i use-

/* 
  Sketch generated by the Arduino IoT Cloud Thing "XY" 

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  float active_power;
  float active_power_kW1;
  float active_power_kW2;
  float active_power_kW3;
  float average_current;
  float average_power_factor;
  float current;
  float current_L1;
  float current_L2;
  float current_L3;
  float frequency1;
  float frequency2;
  float import_energy1;
  float import_energy2;
  float import_energy3;
  float max_dmd_active_power1;
  float max_dmd_active_power2;
  float power_factor;
  float power_factor1;
  float power_factor2;
  float power_factor3;
  float total_import_energy1;
  float total_import_energy2;
  float total_kVA1;
  float total_kVA2;
  float total_kW;
  float voltage_average_LL;
  float voltage_average_LN;
  float voltageLN1;
  float voltage_V12;
  float voltageV1N;
  float voltage_V23;
  float voltageV2N;
  float voltage_V31;
  float voltageV3N;

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/



#include "thingProperties.h"
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#undef ON
#undef OFF

unsigned long rate = 600; // refresh rate in ms
unsigned long lastMillis = 0;

constexpr auto baudrate { 9600 };

constexpr auto bitduration { 1.f / baudrate };
constexpr auto preDelayBR { bitduration * 9.6f * 3.5f * 1e6 };
constexpr auto postDelayBR { bitduration * 9.6f * 3.5f * 1e6 };

void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);

  Serial.println("Modbus RTU Client");

  RS485.setDelays(preDelayBR, postDelayBR);

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  // Set debug message level
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();

  // Start Modbus RTU client
  if (!ModbusRTUClient.begin(9600, SERIAL_8E2)) {
    Serial.println("- Failed to start Modbus RTU Client!");
    while (1);
  } 

}

void loop() {
  ArduinoCloud.update();

  // Update energy meter data and show it via the Serial Monitor
  if (millis() - lastMillis > rate) {
    lastMillis = millis();
    
    // Read from first energy meter (ID 1/EM2M)
    delay(35);
    voltageLN1 = readModbusRegister(0x01, 0x14);
    delay(30);
    current = readModbusRegister(0x01, 0x16);
    delay(30);
    active_power = readModbusRegister(0x01, 0x0E);
    delay(30);
    power_factor= readModbusRegister(0x01, 0x18);
    delay(30);
    frequency1= readModbusRegister(0x01,0x1A);
    delay(30);
    max_dmd_active_power1 = readModbusRegister(0x01, 0x1C);
    delay(30);
    total_import_energy1 = readModbusRegister(0x01, 0x02);
    
    // Read from second energy meter (ID 2/EM4M)
    delay(35);
    voltageV1N = readModbusRegister(0x02, 0x00);
    voltageV2N = readModbusRegister(0x02, 0x02);
    voltageV3N = readModbusRegister(0x02, 0x04);
    voltage_average_LN = readModbusRegister(0x02, 0x06);
    voltage_V12 = readModbusRegister(0x02, 0x08);
    voltage_V23 = readModbusRegister(0x02, 0x0A);
    voltage_V31 = readModbusRegister(0x02, 0x0C);
    voltage_average_LL = readModbusRegister(0x02, 0x0E);

    current_L1 = readModbusRegister(0x02, 0x10);
    current_L2 = readModbusRegister(0x02, 0x12);
    current_L3 = readModbusRegister(0x02, 0x14);
    average_current = readModbusRegister(0x02, 0x16);

    active_power_kW1 = readModbusRegister(0x02, 0x18);
    active_power_kW2 = readModbusRegister(0x02, 0x1A);
    active_power_kW3 = readModbusRegister(0x02, 0x1C);
    total_kW = readModbusRegister(0x02, 0x2A);

    max_dmd_active_power2 = readModbusRegister(0x02, 0x46);

    power_factor1 = readModbusRegister(0x02, 0x30);
    power_factor2 = readModbusRegister(0x02, 0x32);
    power_factor3 = readModbusRegister(0x02, 0x34);
    average_power_factor = readModbusRegister(0x02, 0x36);

    frequency2 = readModbusRegister(0x02, 0x38);

    import_energy1 = readModbusRegister(0x02, 0x4C);
    import_energy2 = readModbusRegister(0x02, 0x4E);
    import_energy3 = readModbusRegister(0x02, 0x50);
    total_import_energy2 = readModbusRegister(0x02, 0x58);
  
  /*  // Print results from first energy meter
    Serial.print("Meter 1 - ");
    Serial.print(voltageLN1, 3);
    Serial.print("V ");
    Serial.print(current, 3);
    Serial.print("A ");
    Serial.print(active_power, 3);
    Serial.print("kW ");
    Serial.print(max_dmd_active_power1, 3);
    Serial.print("kW ");
    Serial.print(power_factor, 3);
    Serial.print("PF ");
    Serial.print(frequency1, 3);
    Serial.print("Hz ");
    Serial.print(total_import_energy1, 3);
    Serial.println("kWh"); 

    // Print results from second energy meter
    Serial.print("Meter 2 - ");
    Serial.print(voltageV1N, 3);
    Serial.print("V ");
    Serial.print(voltageV2N, 3);
    Serial.print("V ");
    Serial.print(voltageV3N, 3);
    Serial.print("V ");
    Serial.print(voltage_average_LN, 3);
    Serial.print("V ");
    Serial.print(voltage_V12, 3);
    Serial.print("V ");
    Serial.print(voltage_V23, 3);
    Serial.print("V ");
    Serial.print(voltage_V31, 3);
    Serial.print("V ");
    Serial.print(voltage_average_LL, 3);
    Serial.print("V ");

    Serial.print(current_L1, 3);
    Serial.print("A ");
    Serial.print(current_L2, 3);
    Serial.print("A ");
    Serial.print(current_L3, 3);
    Serial.print("A ");
    Serial.print(average_current, 3);
    Serial.print("A ");

    Serial.print(active_power_kW1, 3);
    Serial.print("kW ");
    Serial.print(active_power_kW2, 3);
    Serial.print("kW ");
    Serial.print(active_power_kW3, 3);
    Serial.print("kW ");
    Serial.print(total_kW, 3);
    Serial.print("kW ");

    Serial.print(max_dmd_active_power2, 3);
    Serial.print("kW ");

    Serial.print(power_factor1, 3);
    Serial.print("PF ");
    Serial.print(power_factor2, 3);
    Serial.print("PF ");
    Serial.print(power_factor3, 3);
    Serial.print("PF ");
    Serial.print(average_power_factor, 3);
    Serial.print("PF ");

    Serial.print(frequency2, 3);
    Serial.print("Hz ");


    Serial.print(import_energy1, 3);
    Serial.println("kWh");
    Serial.print(import_energy2, 3);
    Serial.println("kWh");
    Serial.print(import_energy3, 3);
    Serial.println("kWh");
    Serial.print(total_import_energy2, 3);
    Serial.println("kWh"); */
  }
}

/* Function to read a float value from the Selec energy meter input registers */
float readModbusRegister(uint8_t slaveID, uint16_t startAddress) {
  float value = 0.0;

  // Send reading request over RS485
  if (!ModbusRTUClient.requestFrom(slaveID, INPUT_REGISTERS, startAddress, 2)) {
    // Error handling
    Serial.print("- Failed to read register at address ");
    Serial.print(startAddress, HEX);
    Serial.print(" for slave ID ");
    Serial.print(slaveID, HEX);
    Serial.print(": ");
    Serial.println(ModbusRTUClient.lastError());
  } else {
    // Response handler
    uint16_t word1 = ModbusRTUClient.read();  // Read word1 from buffer
    uint16_t word2 = ModbusRTUClient.read();  // Read word2 from buffer

    // Combine words in correct order for float (swapped)
    uint32_t combined = (word2 << 16) | word1;

    // Interpret the combined value as a float
    memcpy(&value, &combined, sizeof(value));
  }

  return value;
}

The below are the things I've tried to fix the issue but didn't fix the issue

  • I've tried increasing the delay before reading the first register from any meter from 30ms to 1500ms.
  • tried increasing the overall delay between reading registers to upto 500ms
  • Tried adding a timeout for the modbus to 5sec. (Didnt improve anything so i removed that staement.)
  • added a retry logic to my code but that only increased the errors.
  • tried increasing the refresh rate of my code to from 1 sec to 1.5 secs to 5 sec, which is now set to 600ms
  • tried with the bare bones version of my code by only reading 6 registers from the EM2M
  • tried with 9600&19200 baudrates, tried all combinations of parity and stop bits.

As for more insight i am using a 120Ω termination resistor as recommened by the power meter manufacturer and the possibility of bad connections can be ruled out as rest of the registers are read with any issue.
The Opta has 3 Terminals for RS-485 A(-), GND, B(+) but the power meters have only 2 terminals + & - and the GND of the opta isn't connected to anything but I don't think/know if that could cause the issue as the other registers are read fine.

One crude way that i've done to stop my Arduino Cloud Dashboard from showing a 0 because it couldn't read the first registers is that i've added a statement in my code just before the first ones that will read another register that i dont need to monitor.
For example if the first register to be read for EM2M was 0x14, I made the first register to be 0x12. So now atleast my dashboard doesnt show a 0 randomly but the 0x14 register gives the connection timed out error as its now the first one to be read. But it is sub-optimal as it introduces delays. I have removed it for now so it didnt cause any confusion to what is the first statement but this is what I've done which is explanied above

// Read from first energy meter (ID 1/EM2M)
    delay(35);
    total_kVA1= readModbusRegister(0x01, 0x12); // statement added as buffer
    delay(30);
    voltageLN1 = readModbusRegister(0x01, 0x14);

I hope I will get good advice here and find a solution to my problem.
I apologize for such a long post as i wanted to provide details of everything I could and hopefully haven't missed anything.

Thank You
EM2M_Instruction-Manual.pdf (151.8 KB)

EM4M_Instruction-Manual.pdf (2.9 MB)

Well, you have the advantage of seeing the documentation for your meters, we cannot!

My apologies, I should've added the links to the documentation.

Below are the files-

EM2M_Instruction-Manual.pdf (151.8 KB)
EM4M_Instruction-Manual.pdf (2.9 MB)

I don't see any hints in the documentation.

I suggest you program a second Arduino board to receive your messages and serial.Print() them in HEX to examine that first message.

You can connect both boards using TTL, TX on one end to the other boards RX and same for the other direction. Grounds connected.

Write a simple program to just receive and print the messages.

hello, I have been working on this project since 1 month

and I am using the above converter for communication between arduino or esp8266 with the selec em2m meter

please provide me some leads waiting for the help>>>

IF that solves the problem, then it is the actual answer to the problem. That suggests the meter internal software has a problem that the maker decided to not fix.

Hello,
I have exhausted all methods within my knowledge to fix the "connection timed out" issue. As Paul_KD7HB suggested, it might indeed be a software issue that the manufacturer has not addressed and continues to sell the product as is.

Following Paul_KD7HB's earlier advice to print the RS-485 messages in HEX to discern what the meter is transmitting was unfruitful; instead of a HEX message, I received a 'connection timed out' error.

The only measure that seemed to reduce the errors (from 5-6/min to 3-5/min, although this could be an illusion as my frustration prevented me from investigating further) was:

  1. (This actually ensures error-free reading of all desired registers, unlike other solutions or my previous mention of an illusion. Consider this an exception.) Insert a dummy INPUT_REGISTER that is not needed for monitoring just before the first desired INPUT_REGISTER. This serves as a wakeup call, allowing all subsequent registers to be read without errors.
    Example:
delay(100);
total_kVA1 = readModbusRegister(0x01, 0x12); // Added as a dummy to serve as a wakeup call for the meter, monitoring not required
delay(30);
voltageLN1 = readModbusRegister(0x01, 0x14);
delay(30);
current = readModbusRegister(0x01, 0x16);
  1. Introduce a 100ms delay before reading any registers, as demonstrated above for total_kVA1. Subsequent delays can be reduced as needed (in my case, 30ms between registers).
  2. Employ an Odd Parity Bit with Stop Bit 2. (!ModbusRTUClient.begin(baudrate, SERIAL_8O2)

If you need further assistance feel free to reply and ask any of you queries


Atleast doing what I did in Point 1 stops both my Voltage readings from going to 0 for a few seconds.