Modbus RTU master-slave communication

I'm using ESP32 with the RS485 converter with DE/RE pins. For the slave I'm using Wemos D1 Mini ESP8266 with the auto-flow version of RS485 converter. I have the following connections:

MASTER:
ESP32 PIN 16 -> RS485 RO
ESP32 PIN 17 -> RS485 DI
ESP32 PIN 32 -> RS485 DE and also RE

SLAVE
WEMOS D1 MINI PIN 4 -> RS485 RX
WEMOS D1 MINI PIN 5 -> RS485 TX

and naturally 3.3V to VCC and GND to GND for both boards.

I'm having hard time reading from the slave and I can't figure out why. I tried multiple different libraries but none of them worked. I also tried to setup just a simple serial communication and send a message to the slave and that was working. But I can't read the register with the following code.

MASTER

#include <ModbusRTUMaster.h>

const byte rxPin = 16; // RX pin for RS-485
const byte txPin = 17; // TX pin for RS-485
const byte dePin = 32; // Driver Enable pin (set to LOW for auto flow control)

ModbusRTUMaster modbus(Serial, dePin); // Create Modbus master object

void setup() {
  Serial.begin(115200); // Serial monitor for debugging

  // Initialize Modbus communication
  modbus.begin(38400); // Baud rate for RS-485

  // Initialize the RS-485 driver enable pin
  pinMode(dePin, OUTPUT);
  digitalWrite(dePin, LOW); // Set DE pin low for auto flow control
}

void loop() {
  uint16_t holdingRegisterValue = 0; // Variable to store the register value

  // Request the value of holding register 0 from slave with ID 1
  bool success = modbus.readHoldingRegisters(1, 0, &holdingRegisterValue, 1); // slave ID, register address, pointer to store value, number of registers

  // Check if the request was successful
  if (success) {
    Serial.print("Holding Register Value: ");
    Serial.println(holdingRegisterValue);
  } else {
    Serial.println("Failed to read holding register");
    if (modbus.getTimeoutFlag()) {
      Serial.println("Error: Timeout");
      modbus.clearTimeoutFlag();
    } else if (modbus.getExceptionResponse() != 0) {
      Serial.print("Error: Exception Response ");
      Serial.print(modbus.getExceptionResponse());
      Serial.println();
      modbus.clearExceptionResponse();
    } else {
      Serial.println("Error: Unknown");
    }
  }

  delay(1000); // Wait before making the next request
}

and the SLAVE:

#if __AVR__
  // Uncomment the following line if you want to use SoftwareSerial on pins 10 and 11; note this only works on AVR boards.
  //# define USE_SOFTWARE_SERIAL
#endif

#if defined USE_SOFTWARE_SERIAL
  #include <SoftwareSerial.h>
#endif

#include <ModbusRTUSlave.h>

#if defined USE_SOFTWARE_SERIAL
  const byte rxPin = 4; // RX pin for SoftwareSerial
  const byte txPin = 5; // TX pin for SoftwareSerial
  SoftwareSerial mySerial(rxPin, txPin);
  ModbusRTUSlave modbus(mySerial);  // Use SoftwareSerial without DE pin
#else
  #if defined ESP32
    ModbusRTUSlave modbus(Serial2); // Use Serial2 without DE pin for ESP32
  #else
    ModbusRTUSlave modbus(Serial); // Use Serial without DE pin for ESP8266
  #endif
#endif

const byte pinToRead = 14; // The pin whose state you want to read
uint16_t holdingRegister = 0; // Single holding register

void setup() {
  pinMode(pinToRead, INPUT); // Set the pin as input

  // Configure Modbus registers
  modbus.configureHoldingRegisters(&holdingRegister, 1); // Single holding register
  modbus.begin(1, 38400); // Initialize Modbus with slave ID 1 and baud rate 38400
}

void loop() {
  holdingRegister = digitalRead(pinToRead); // Read the state of the pin and store in holding register
  
  modbus.poll(); // Handle Modbus communication

  // You can add additional code here if needed
}

on the master serial monitor I'm seeing this output. And also on the slave's converter I don't see any action on the TX or RX leds indicators.

�
Failed to read holding register
Error: Timeout

I'm not sure if my code is right since I got it using chatgpt. My goal for now is just to be able to read one register from the slave.

using the wrong serial object for the esp32..
pins 16,17 are Serial1..
So just use Serial1 for modbus..

good luck.. ~q

@qubits-us You mean just to edit this line from this ModbusRTUMaster modbus(Serial, dePin) to this ModbusRTUMaster modbus(Serial1, dePin)?
If so I tried it but I'm getting this error on the serial monitor

rst:0x8 (TG1WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1344
load:0x40078000,len:13964

try Serial2 instead..
the esp32 has 3 hardware serials..
they can be re-assigned..

~q

@qubits-us if I use Serial2, I can see the RX led blinking on the slave transmitter but it is not responding back and I have this in my master's serial log:

Failed to read holding register
Error: Timeout

the esp32 sounds correct now..
the slave wemos d1..
i see you are using pins 4 and 5..
but without the software serial, pretty sure it's using the Serial object..
don't know too much about the 8266, maybe someone else will chime in for you..

good luck.. ~q

@qubits-us yes the master is correct, I tried to flash the slave with ESPHome firmware and I was able to read the register. But I cannot get it to working with any of the libraries I've found for Arduino IDE. I'm trying it with this one, but I'm still not getting any response..This is the code that I'm using which I found in the examples on the github:

#include <ModbusRTU.h>

#define REGN 10
#define SLAVE_ID 1

ModbusRTU mb;

void setup() {
  Serial.begin(38400, SERIAL_8N1);
#if defined(ESP32) || defined(ESP8266)
  mb.begin(&Serial);
#else
  mb.begin(&Serial);
  //mb.begin(&Serial, RXTX_PIN);  //or use RX/TX direction control pin (if required)
  mb.setBaudrate(38400);
#endif
  mb.slave(SLAVE_ID);
  mb.addHreg(REGN);
  mb.Hreg(REGN, 100);
}

void loop() {
  mb.task();
  yield();
}

but there is again no activity on the slave's transmitter TX/RX indicator LEDs.

I finally got it working with the following codes, @qubits-us thanks for your help! :slight_smile:
MASTER

#include <ModbusRTUMaster.h>


const byte dePin = 32; // Driver Enable pin (set to LOW for auto flow control)

ModbusRTUMaster modbus(Serial2, dePin); // Create Modbus master object

void setup() {
  Serial.begin(115200); // Serial monitor for debugging

  // Initialize Modbus communication
  modbus.begin(38400); // Baud rate for RS-485

  // Initialize the RS-485 driver enable pin
  pinMode(dePin, OUTPUT);
  digitalWrite(dePin, LOW); // Set DE pin low for auto flow control
}

void loop() {
  uint16_t holdingRegisterValue = 0; // Variable to store the register value

  // Request the value of holding register 0 from slave with ID 1
  bool success = modbus.readHoldingRegisters(1, 0, &holdingRegisterValue, 1); // slave ID, register address, pointer to store value, number of registers

  // Check if the request was successful
  if (success) {
    Serial.print("Holding Register Value: ");
    Serial.println(holdingRegisterValue);
  } else {
    Serial.println("Failed to read holding register");
    if (modbus.getTimeoutFlag()) {
      Serial.println("Error: Timeout");
      modbus.clearTimeoutFlag();
    } else if (modbus.getExceptionResponse() != 0) {
      Serial.print("Error: Exception Response ");
      Serial.print(modbus.getExceptionResponse());
      Serial.println();
      modbus.clearExceptionResponse();
    } else {
      Serial.println("Error: Unknown");
    }
  }

  delay(1000); // Wait before making the next request
}

SLAVE:

#include <SoftwareSerial.h>
#include <ModbusRTU.h>

#define REGN 0
#define SLAVE_ID 1
#define RS485_TX_PIN D1  // GPIO5
#define RS485_RX_PIN D2  // GPIO4

SoftwareSerial swSerial(RS485_RX_PIN, RS485_TX_PIN);  // RX, TX
ModbusRTU mb;

void setup() {
  swSerial.begin(38400, SWSERIAL_8N1);
  mb.begin(&swSerial);
  mb.slave(SLAVE_ID);

  mb.addHreg(REGN);
  mb.Hreg(REGN, 100);
}

void loop() {
  mb.task();
  yield();
}
1 Like