Modbus on RS485

Hi guys!
I am trying to read modbus registers through RS485 adapter.
At the beginning it has been used cheap RS485 adapter (Vcc, RX, TX, GND, no DI/DE terminals), it was correctly working in C:

USB-TTL <=> TTL-RS485 <=> device

so all boards were designed without DI/DE terminals.

Now I can’t get code working to correctly read modbus register.
If I manually write tx buffer (reading from Slave #1, FunctionID 04, reading 1 register from address 0x331A):

0x01 0x04 0x33 0x1A 00 01 1F 49

I can see echo received on RX line during transmission request, then (about 6ms later) I can see data from modbus device.

#include <ModbusMaster.h>

#define RS485_SERIAL Serial2
#define RX_PIN 16
#define TX_PIN 17
#define DE_PIN 4
#define SLAVE_ID 1
#define REG_ADDR 13082
#define NUM_REG 1

ModbusMaster node;



void postTransmission() 
{
  delay(20);  // 20 ms di attesa dopo l'invio del frame
}

void setup() 
{
  Serial.begin(115200);
  delay(1000);
  Serial.println("Modbus test...Output:");

  RS485_SERIAL.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
}

void loop() 
{

// Creiamo il frame manualmente per il log
  uint8_t frame[16];
  int i, n;



  i = 0;
  frame[i++] = SLAVE_ID;              // Slave ID
  frame[i++] = 0x04;                  // Funzione 03 = read holding registers
  frame[i++] = (REG_ADDR >> 8) & 0xFF;// High byte indirizzo
  frame[i++] = REG_ADDR & 0xFF;       // Low byte indirizzo
  frame[i++] = 0x00;                  // High byte numero registri
  frame[i++] = NUM_REG;                // Low byte numero registri
  uint16_t crc = calcCRC(frame, 6);
  frame[i++] = crc & 0xFF;            // CRC Low
  frame[i++] = (crc >> 8) & 0xFF;     // CRC High

// Invia tutto il frame
  RS485_SERIAL.write(frame, i);  // i contiene il numero di byte scritti
  RS485_SERIAL.flush();           // aspetta che tutti i byte siano stati trasmessi

// svuota buffer RX e ignora l'eco ricevuta da trasmettitore RS-485 senza gestione 

while (RS485_SERIAL.available()) 
{
  RS485_SERIAL.read();
}
delay(20); 


n = RS485_SERIAL.available();
Serial.print("ricevuti ");
Serial.print(n);
Serial.println(" bytes...");


while(n>0) 
{
  uint8_t b = RS485_SERIAL.read();

 if (b < 0x10) 
    Serial.print("0");
  Serial.print(b, HEX);
  Serial.print(" ");


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

Output:
ricevuti 7 bytes...
01 04 02 04 E9 7A 7E
ricevuti 7 bytes...
01 04 02 04 E9 7A 7E

I installed library:
ModbusMaster v2.0.1 (Doc Walker)

How can I manage echo?
I don’t understand if I have to manage it using DI/DE pins, or if I can manage it via sofrware.

#include <ModbusMaster.h>


#define RS485_SERIAL Serial2
#define RX_PIN 16
#define TX_PIN 17
#define DE_PIN 4
#define SLAVE_ID 1
#define REG_ADDR 13082
#define NUM_REG 1

ModbusMaster node;

void postTransmission() {
  delay(20);  // 20 ms di attesa dopo l'invio del frame
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("ESP32 Modbus RTU Debug completo frame");

  // NO DE_PIN AVAILABLE!!!!!!
  //pinMode(DE_PIN, OUTPUT);
  //digitalWrite(DE_PIN, LOW);

  RS485_SERIAL.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);

  node.begin(SLAVE_ID, RS485_SERIAL);

  //node.postTransmission(postTransmission); // assegna la funzione, questo serve per impostare un ritardo di 20ms dalla trasmissione della richiesta alla lettura del buffer seriale per cercare di evitare l'eco


  // ERROR: not available!!! 
  //node.noReceive(20);  // <- questa funzione è disponibile solo in alcuni fork

  //node.preTransmission(preTransmission);
  //node.postTransmission(postTransmission);
}





void loop() 
{

  // esempio di trasmissione Modbus RTU

  // trasmetto richiesta lettura n° 1 registro

  // 0x01 0x04 0x33 0x1A 0x00 0x01 0x1F 0x49

  //

  // risposta:

  // 0x01 0x04 0x02 0x04 0xEC 0xBA 0x7D







// lettura Modbus register
uint8_t result = node.readInputRegisters(REG_ADDR, NUM_REG);

// provo a gestire l'eco

while (RS485_SERIAL.available()) RS485_SERIAL.read();

// aspetta che arrivi la vera risposta
// same result using several delay values: always 0.00 V
//delay(20);
//delay(10);
delay(2);

// ora leggi il risultato
result = node.getResponseBuffer(0);

  if (result == node.ku8MBSuccess) 
  {
    // Lettura riuscita, estraggo il valore dal buffer
    uint16_t value = node.getResponseBuffer(0);  // 0 = primo registro letto
    Serial.print("Registro ");
    Serial.print(REG_ADDR);
    Serial.print(" = ");
    Serial.println(value);
    // conversione in Volt
    Serial.print("Tensione = ");
    Serial.print(value * 0.01, 2);  // due decimali
    Serial.println(" V");
  } 
  else 
  {
    Serial.print("Errore Modbus: ");
    Serial.println(result);
  }
  delay(5000);

}

How echo is correctly managed?
Thanks.

And at the moment what you use?
Library example code doesn't work for you?

Sorry guys, on my code there were many stupid “copy&paste” of code I don’t completely understand.
Here my current code:

#include <ModbusMaster.h>

#define RS485_SERIAL Serial2
#define RX_PIN 16
#define TX_PIN 17
#define DE_PIN 4
#define SLAVE_ID 1
#define REG_ADDR 13082
#define NUM_REG 1

#define TRIGGER_PIN 23

int postDelayMs;   // ritardo iniziale, lo puoi cambiare al volo
int h;
int CurrentReg;
ModbusMaster node;

// --- DE/RE manuale ---
//void preTransmission() { digitalWrite(DE_PIN, HIGH); }
//void postTransmission() { digitalWrite(DE_PIN, LOW); }

// --- Funzione per stampare frame completo ---
void printFrame(const uint8_t* frame, uint8_t len, const char* label) {
Serial.print(label);
Serial.print(" (");
Serial.print(len);
Serial.print(" byte): ");
for (uint8_t i = 0; i < len; i++) {
if (frame[i] < 0x10) Serial.print("0");
Serial.print(frame[i], HEX);
Serial.print(" ");
}
Serial.println();
}

// --- Stampa in esadecimale ---
void printHex(const uint8_t* buf, size_t len, const char* label) {
Serial.print(label);
Serial.print(" (");
Serial.print(len);
Serial.print(" bytes): ");
for (size_t i = 0; i < len; i++) {
if (buf[i] < 0x10) Serial.print('0');
Serial.print(buf[i], HEX);
Serial.print(' ');
}
Serial.println();
}

// --- Funzione per stampare CRC ---
uint16_t calcCRC(const uint8_t* data, uint8_t len) {
uint16_t crc = 0xFFFF;
for (uint8_t pos = 0; pos < len; pos++) {
crc ^= data[pos];
for (uint8_t i = 0; i < 8; i++) {
if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001;
else crc = crc >> 1;
}
}
return crc;
}

void preTransmission() {
digitalWrite(TRIGGER_PIN, 0);
}

void dumpRxBuffer()
{
int n = RS485_SERIAL.available();
Serial.printf("RX buffer: %d byte: ", n);

for (int i = 0; i < n; i++) 
{
    int b = RS485_SERIAL.read();
    Serial.printf("%02X ", b);
}
Serial.println();

}

void postTransmission()
{
RS485_SERIAL.flush();  // assicurati che la TX sia davvero finita
//digitalWrite(TRIGGER_PIN, 1);
//delay(2);
delayMicroseconds(100); // piccolo margine
//digitalWrite(TRIGGER_PIN, 1);

// Pulisci il buffer RX dall’eco

// dump per debug prima della pulizia
//dumpRxBuffer();

// pulizia dell'eco
while (RS485_SERIAL.available())
{
RS485_SERIAL.read();
}
delay(postDelayMs);
digitalWrite(TRIGGER_PIN, 1);
}

void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("ESP32 Modbus RTU Debug completo frame");

Serial.write(12);//ASCII for a Form feed

pinMode(TRIGGER_PIN, OUTPUT);
digitalWrite(TRIGGER_PIN, 1);
//pinMode(DE_PIN, OUTPUT);
//digitalWrite(DE_PIN, LOW);

RS485_SERIAL.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);

node.begin(SLAVE_ID, RS485_SERIAL);
//node.postTransmission(postTransmission); // assegna la funzione, questo serve per impostare un ritardo di 20ms dalla trasmissione della richiesta alla lettura del buffer seriale per cercare di evitare l'eco

// ERROR: not available!!!
//node.noReceive(20);  // <- questa funzione è disponibile solo in alcuni fork

node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
//node.setTimeout(200); // 200 ms, giusto per debug
postDelayMs = 1;  // inizio con 1ms
h = 0;
}

//#define ModbusManual 1

void loop()
{

// esempio di trasmissione Modbus RTU
// trasmetto richiesta lettura n° 1 registro
// 0x01 0x04 0x33 0x1A 0x00 0x01 0x1F 0x49
//
// risposta:
// 0x01 0x04 0x02 0x04 0xEC 0xBA 0x7D

Serial.printf("postDelayMs %02d [ms]: ", postDelayMs);

//uint8_t result = node.readHoldingRegisters(REG_ADDR, NUM_REG);
uint8_t result;

// provo a gestire l'eco
// svuota eventuale echo
//while (RS485_SERIAL.available()) RS485_SERIAL.read();
// aspetta che arrivi la vera risposta
//delay(20);
//delay(10);
//delay(2);
// ora leggi il risultato
//result = node.getResponseBuffer(0);

if (h)
{
//result  = node.readInputRegisters(REG_ADDR, NUM_REG);
CurrentReg = REG_ADDR;

}
else
{
CurrentReg = 12561; // temperatura
}

result  = node.readInputRegisters(CurrentReg, NUM_REG);

if (result == node.ku8MBSuccess)
{
// Lettura riuscita, estraggo il valore dal buffer
uint16_t value = node.getResponseBuffer(0);  // 0 = primo registro letto
//    Serial.print("Registro ");
//    Serial.print(CurrentReg);
//    Serial.print(" = ");
Serial.println(value);

/*// Se vuoi convertirlo in Volt (esempio EPEVER)
Serial.print("Tensione = ");
Serial.print(value * 0.01, 2);  // due decimali
Serial.println(" V");
*/

}
else
{
Serial.print("Errore Modbus: ");
Serial.println(result);

}

// Pulizia buffer RX, eventuali byte residui
while (RS485_SERIAL.available())
{
RS485_SERIAL.read();
}

postDelayMs++;

h ^= 1;
delay(2000);
}

I am using ModbusMaster v2.0.1 (Doc Walker)

Now it works but I don’t understand why.

Inside postTransmission:
RS485_SERIAL.flush();

  • I wait Modbus message has moved to serial buffer, but serial line still sending data…

delayMicroseconds(100);

  • after this serial line should be IDLE

while (RS485_SERIAL.available())
{
RS485_SERIAL.read();
}

  • removing received echo from serial buffer

delay(postDelayMs);

  • for debug purposes: postDelayMs increases after every request

digitalWrite(TRIGGER_PIN, 1);

  • for debug purposes: showing postTransmission ends

Now I expect serial line reading (for Modbus answer) starts afer TRIGGER_PIN toggles to 1.
On my oscilloscope I see:

  • 0 ms: start modbus request & echo receiving
  • 8 ms: modbus request ends, serial line in IDLE
  • 12 ms: modbus device sending me data

so I should correctly receive/read serial data only within postDelayMs values 0 to 4, but at 17 I am still reading (correctly, I hope) data from slave device.
I introduced h to toggle between two different registrs, I supposed something wrong with rx buffer clearing.
So I don’t understand WHEN serial line starts to be read…

Hello,
When you call:

node.readInputRegisters(...)

the library performs these steps internally:

  1. It calls your preTransmission() callback
  2. It sends the Modbus request frame
  3. It calls your postTransmission() callback
  4. Immediately after postTransmission() returns, the library starts waiting for the response

That's means:

  • Your delay inside postTransmission() does not delay the Modbus read
  • The library always begins receiving right after postTransmission() ends
  • That’s why you still get the response even when your delay is longer than the slave’s response time

if i understand what you mean :) :slightly_smiling_face: