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)