Modbus communication via Max485 between ESP32 as Master and Arduino Nano as Slave

Hello everyone, I have some communication problems between ESP32 and Arduino Nano via IC MAX485 and with the use of the Modbus protocol, I would like some help from you.
I explain in detail where I have problems: I'm using a 38 PIN ESP32 WROOM 32 as Master in a project I made with Arduino Nano V3. My initial project was to use Arduino nanos as slaves that were polled by another Arduino nano as masters. Now I would like to replace the Arduino nano Master with the ESP32 .
I state that for me it is the first time that I use the ESP32, so forgive me the ignorance I show on this MCU.
Attached is what I've made at the moment on breadboard.... everything works except the communication between ESP32 and Arduino nano.


At the moment I'm testing the communication with only one slave, but actually the project has 4 slaves (all Arduino nano V3)
I am using Software Serial for serial communication. Below is the master's sketch.... in zip file..

thanks in advance to those who want to help me out.
prog_17_esp32_tft_04.zip (10,1 KB)

Sorry, not playing with the Zip file..
the level converter, why??
get a 485 board that will do 3.3 v..
some can do both 5 and 3.3..

post code using the code button, if you think it's something in there..
more than happy to take a peek..

good luck.. ~q

The level converter because the ESP32 has logic values with a 3.3V base, while the MAX485 chip is powered at 5V, therefore with 5V logic values, for the rest of your communication it is not clear to me ... master- sketch attached

/* 
Progetto 17 - MASTER - maschera con Logo per TFT ILI9341 2,8" SPI
*  MASTER
Coil              Read-write    1 bit     00001 – 09999
Discrete input    Read-only     1 bit     10001 – 19999
Input register    Read-only     16 bits   30001 – 39999
Holding register  Read-write    16 bits   40001 – 49999
*/

#include "SPI.h"
#include "TFT_eSPI.h"
#include <PNGdec.h>
#include "logo.h"

PNG png; // PNG decoder inatance
#define MAX_IMAGE_WDITH 240 // Adjust for your images


// Use hardware SPI
TFT_eSPI tft = TFT_eSPI();

#include <ModbusMaster.h>
#include <SoftwareSerial.h>

#define PinRicezione     16       // collegare l'RO della RS485
#define PinTrasmissione  17       //collegare il DI della RS485
#define PinControllo     27       //collegare il DE e l'RE della RS485
#define MODBUS_SERIAL_BAUD 9600   // Baud rate for esp32 and max485 communication

SoftwareSerial RS485Serial(PinRicezione, PinTrasmissione); // RX, TX
ModbusMaster node;              //Creo l'oggetto node

int16_t xpos = 80;
int16_t ypos = 10;

//variabili dove memorizzo le letture provenienti dagli slave
int S1_dato1 =0; int S1_dato2 =0; int S1_dato3 =0; int S1_dato4 =0; int S1_dato5 =0; int S1_dato6 =0;
int S2_dato1 =0; int S2_dato2 =0; int S2_dato3 =0; int S2_dato4 =0; int S2_dato5 =0; int S2_dato6 =0;
int S3_dato1 =0; int S3_dato2 =0; int S3_dato3 =0; int S3_dato4 =0; int S3_dato5 =0; int S3_dato6 =0; 
int S4_dato1 =0; int S4_dato2 =0; int S4_dato3 =0; int S4_dato4 =0; int S4_dato5 =0; int S4_dato6 =0;
//variabili di servizio
int result = 0;     //variabile di lettura del buffer
int p = 0;          //contatore


void preTransmission()
{
  digitalWrite(PinControllo, 1);  //abilito il DE/RE della RS485 alla trasmissione
}

void postTransmission()
{
  digitalWrite(PinControllo, 0);  //disabilito il DE/RE della RS485 alla trasmissione
}


void setup() {
  Serial.begin(9600);
  tft.begin();                  //Inizializzazione schermo
  tft.setRotation(0);           //Rotazione schermo 0=verticale 1=orizzontale
  tft.fillScreen(TFT_BLUE); //cancella lo schermo e lo colora di blu
  tft.setTextColor(TFT_YELLOW); //setta il colore del testo
  tft.setTextSize(3);               //setta la dimensione del carattere
  tft.setCursor(50, 80);            //posiziona il cursore alle coordinate indicate
  tft.println("BE MAKER");          //scrive il testo nella posizione del cursore
  tft.setTextColor(TFT_WHITE);  
  tft.setTextSize(2);
  tft.setCursor(20, 110);
  tft.println("INIZIALIZZAZIONE");
  tft.drawLine(5, 130, 235, 130, TFT_WHITE);  //disegna una linea con parametri: coordinate di inizio e di fine e colore

  pinMode(PinControllo, OUTPUT);  //settaggio pin di abilitazione della trasmissione e ricezione
  // Init in receive mode
  digitalWrite(PinControllo, 0);  //setto il pin inizialmente in ricezione 

  //Serial2.begin(baud-rate, protocol, RX pin, TX pin);.
  //Serial2.begin(MODBUS_SERIAL_BAUD, SERIAL_8E1, PinRicezione, PinTrasmissione);
  //Serial2.setTimeout(200);
  RS485Serial.begin(9600);
  
  // Modbus slave ID 1
  tft.setTextSize(2);
  tft.setCursor(5, 140);
  tft.print("Iniz.RS485...");
  tft.setCursor(5, 160);
  tft.print("Iniz.Slave 1...");
  node.setSlave(1);
  node.begin(1, RS485Serial);
  delay(100);
  // Modbus slave ID 2
  tft.setCursor(5, 180);
  tft.println("Iniz.Slave 2...");
  node.setSlave(2);
  node.begin(2, RS485Serial);
  delay(100);
  // Modbus slave ID 3
  tft.setCursor(5, 200);
  tft.println("Iniz.Slave 3...");
  node.setSlave(3);
  node.begin(3, RS485Serial);
  delay(100);
  //Modbus slave ID 4
  tft.setCursor(5, 220);
  tft.println("Iniz.Slave 4...");
  node.setSlave(4);
  node.begin(4, RS485Serial);
  delay(1000);
  // Callbacks allow us to configure the  RS485 transceiver correctly
  tft.setCursor(5, 240);
  tft.println("Iniz.Master...");  
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
  delay(2000);
  maschera();
}

void maschera() {
  tft.fillScreen(TFT_BLUE); //cancella lo schermo e lo colora di blu
  tft.setRotation(0);           //Rotazione schermo 0=verticale 1=orizzontale
  tft.setTextColor(TFT_YELLOW); //setta il colore del testo
  tft.setTextSize(3);               //setta la dimensione del carattere
  tft.setCursor(50, 80);            //posiziona il cursore alle coordinate indicate
  tft.println("BE MAKER");          //scrive il testo nella posizione del cursore
  //Inserimento maschera dati
  tft.setTextColor(TFT_WHITE);  
  tft.setTextSize(2);
  tft.setCursor(5, 110);
  tft.println("STATO SENSORI S");
  tft.drawLine(5, 130, 235, 130, TFT_WHITE);  //disegna una linea con parametri: coordinate di inizio e di fine e colore
  tft.setTextSize(2);
  tft.setCursor(5, 140);
  tft.println("TEMP. STG [C]:");
  tft.setCursor(5, 160);
  tft.println("TEMP. EXT [C]:");
  tft.setCursor(5, 180);
  tft.println("SENS. ACQUA 1:");
  tft.setCursor(5, 200);
  tft.println("SENS. ACQUA 2:");
  tft.setCursor(5, 220);
  tft.println("SENS. ACQUA 3:");
  tft.setCursor(5, 240);
  tft.println("SENS. ACQUA 4:");
  tft.drawLine(5, 260, 235, 260, TFT_WHITE);
}

int soglia_acqua_min = 470;
int soglia_acqua_max = 512;
float lungh = 1.0;

void loop() {
  //inserimento logo
  int16_t rc = png.openFLASH((uint8_t *)logo, sizeof(logo), pngDraw);
  if (rc == PNG_SUCCESS) {
    tft.startWrite();
    rc = png.decode(NULL, 0);
    tft.endWrite();
  }
  
  int t = 0; 
  t = p % 1;  //Calcola il resto della divisione per il numero di slave e sarΓ  un valore circolare 0, 1, 2, 3, 0, 1,...
  //Serial.print("Contatore ");
  //Serial.print(p);
  //Serial.print("   Resto ");
  //Serial.println(t);
  //Lettura dati dagli Slave 
  //Lettura valori sensori S1
  if(t == 0){
    Serial.println("Intterrogo Slave 1...");
    node.setSlave(1); //Seleziono Slave 1
    result = node.readInputRegisters(0x7531, 16); //0x7531 in hex significa 30001 in decimale, il 16 indica che il buffer ha 16 posizioni
    if (result == node.ku8MBSuccess) {
      S1_dato1 = node.getResponseBuffer(0);
      S1_dato2 = node.getResponseBuffer(1);
      S1_dato3 = node.getResponseBuffer(2);
      S1_dato4 = node.getResponseBuffer(3);
      S1_dato5 = node.getResponseBuffer(4);
      S1_dato6 = node.getResponseBuffer(5);
      Serial.println("Dati ricevuti...");
    }  
    pagina(1, S1_dato1, S1_dato2, S1_dato3, S1_dato4, S1_dato5, S1_dato6);
    delay(2000);
  }
  //Lettura valori sensori S2  
  if(t == 1){
    node.setSlave(2); //Seleziono Slave 2
    result = node.readInputRegisters(0x7919, 16); //0x7919 in hex significa 31001 in decimale, il 16 indica che il buffer ha 16 posizioni
    if (result == node.ku8MBSuccess) {
      S2_dato1 = node.getResponseBuffer(0);
      S2_dato2 = node.getResponseBuffer(1);
      S2_dato3 = node.getResponseBuffer(2);
      S2_dato4 = node.getResponseBuffer(3);
      S2_dato5 = node.getResponseBuffer(4);
      S2_dato6 = node.getResponseBuffer(5);
    }  
    pagina(2, S2_dato1, S2_dato2, S2_dato3, S2_dato4, S2_dato5, S2_dato6);
    delay(2000);
  }
  //Lettura valori sensori S3  
  if(t == 2){
    node.setSlave(3); //Seleziono Slave 3
    result = node.readInputRegisters(0x7D01, 16); //0x7D01 in hex significa 32001 in decimale, il 16 indica che il buffer ha 16 posizioni
    if (result == node.ku8MBSuccess) {
      S3_dato1 = node.getResponseBuffer(0);
      S3_dato2 = node.getResponseBuffer(1);
      S3_dato3 = node.getResponseBuffer(2);
      S3_dato4 = node.getResponseBuffer(3);
      S3_dato5 = node.getResponseBuffer(4);
      S3_dato6 = node.getResponseBuffer(5);
    }  
    pagina(3, S3_dato1, S3_dato2, S3_dato3, S3_dato4, S3_dato5, S3_dato6);
    delay(2000);
  }
  //Lettura valori sensori S4  
  if(t == 3){
    node.setSlave(4); //Seleziono Slave 3
    result = node.readInputRegisters(0x80E9, 16); //0x80E9 in hex significa 33001 in decimale, il 16 indica che il buffer ha 16 posizioni
    if (result == node.ku8MBSuccess) {
      S4_dato1 = node.getResponseBuffer(0);
      S4_dato2 = node.getResponseBuffer(1);
      S4_dato3 = node.getResponseBuffer(2);
      S4_dato4 = node.getResponseBuffer(3);
      S4_dato5 = node.getResponseBuffer(4);
      S4_dato6 = node.getResponseBuffer(5);
    }
    pagina(4, S4_dato1, S4_dato2, S4_dato3, S4_dato4, S4_dato5, S4_dato6);  
    delay(2000);
  }
  p++;
    tft.setTextColor(TFT_WHITE,TFT_BLUE);  
    tft.setTextSize(3);
    tft.setCursor(40, 285);
    tft.print("              ");  
}  

void pagina (int pg, int s1, int s2, int s3, int s4, int s5, int s6){
  //Serial.print("pagina ");
  //Serial.println(pg);
  tft.setTextSize(2);               //setta la dimensione del carattere
  tft.setCursor(200, 110);
  tft.setTextColor(TFT_WHITE,TFT_BLUE); //setta il colore del testo
  tft.print(pg);                  // Indica il sensore a cui la pagina fa riferimento      
    if (s1 > 5000) {
      tft.setCursor(180, 140);
      tft.setTextColor(TFT_WHITE,TFT_RED);
      tft.println("     ");
      tft.setCursor(180, 140);
      tft.println(float (s1/100.0));
      allarme(pg);
    } else {
      tft.setCursor(180, 140);
      tft.setTextColor(TFT_WHITE,TFT_BLUE);
      tft.println("     ");
      tft.setCursor(180, 140);
      tft.println(float (s1/100.0));    
    }
    if (s2 > 5000) {
      tft.setCursor(180, 160);
      tft.setTextColor(TFT_WHITE,TFT_RED);
      tft.println("     ");
      tft.setCursor(180, 160);
      tft.println(float (s2/100.0));
      allarme(pg);
    } else {
      tft.setCursor(180, 160);
      tft.setTextColor(TFT_WHITE,TFT_BLUE);
      tft.println("     ");
      tft.setCursor(180, 160);
      tft.println(float (s2/100.0));    
    }
    if (s3 > soglia_acqua_min) {
      tft.setCursor(180, 180);
      tft.setTextColor(TFT_WHITE,TFT_RED);
      tft.println("     ");
      tft.setCursor(180, 180);
      float dist_1 =  lungh - ((s3*1.0)/(soglia_acqua_max*1.0));
      tft.println(dist_1);
      allarme(pg);
    } else {
      tft.setCursor(180, 180);
      tft.setTextColor(TFT_WHITE,TFT_BLUE);
      tft.println("     ");
      tft.setCursor(180, 180);
      tft.println(s3);    
    }
    if (s4 > soglia_acqua_min) {
      tft.setCursor(180, 200);
      tft.setTextColor(TFT_WHITE,TFT_RED);
      tft.println("     ");
      tft.setCursor(180, 200);
      float dist_2 =  lungh - ((s4*1.0)/(soglia_acqua_max*1.0));
      tft.println(dist_2);
      allarme(pg);
    } else {
      tft.setCursor(180, 200);
      tft.setTextColor(TFT_WHITE,TFT_BLUE);
      tft.println("     ");
      tft.setCursor(180, 200);
      tft.println(s4);    
    }
    if (s5 > soglia_acqua_min) {
      tft.setCursor(180, 220);
      tft.setTextColor(TFT_WHITE,TFT_RED);
      tft.println("     ");
      tft.setCursor(180, 220);
      float dist_3 =  lungh - ((s5*1.0)/(soglia_acqua_max*1.0));
      tft.println(dist_3);
      allarme(pg);
    } else {
      tft.setCursor(180, 220);
      tft.setTextColor(TFT_WHITE,TFT_BLUE);
      tft.println("     ");
      tft.setCursor(180, 220);
      tft.println(s5);    
    }
    if (s6 > soglia_acqua_min) {
      tft.setCursor(180, 240);
      tft.setTextColor(TFT_WHITE,TFT_RED);
      tft.println("     ");
      tft.setCursor(180, 240);
      float dist_4 =  lungh - ((s6*1.0)/(soglia_acqua_max*1.0));
      tft.println(dist_4);
      allarme(pg);
    } else {
      tft.setCursor(180, 240);
      tft.setTextColor(TFT_WHITE,TFT_BLUE);
      tft.println("     ");
      tft.setCursor(180, 240);
      tft.println(s6);    
    }
}

int g = 0;
void allarme(int f){
  int j=0;
  j = g % 2;
  //Serial.print("Contatore allarme ");
  //Serial.print(f);
  //Serial.print("   Resto ");
  //Serial.println(j);
  if (j==0) {
    tft.setTextColor(TFT_WHITE,TFT_RED);  
    tft.setTextSize(3);
    tft.setCursor(40, 285);
    tft.println("ALLARME !");
    tft.setTextSize(2);
  } else {
    tft.setTextColor(TFT_WHITE,TFT_BLUE);  
    tft.setTextSize(3);
    tft.setCursor(40, 285);
    tft.println("ALLARME !");
    tft.setTextSize(2);      
  }
  g++;
}

//=========================================v==========================================
//                                      pngDraw
//====================================================================================
// This next function will be called during decoding of the png file to
// render each image line to the TFT.  If you use a different TFT library
// you will need to adapt this function to suit.
// Callback function to draw pixels to the display
void pngDraw(PNGDRAW *pDraw) {
  uint16_t lineBuffer[MAX_IMAGE_WDITH];
  png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
  tft.pushImage(xpos, ypos + pDraw->y, pDraw->iWidth, 1, lineBuffer);
}

Yes, I know..
I've used these, worked good..

sorry.. ~q

I'm not worried about the MAX485 IC chip, but I'm worried about the ESP32, I don't want to damage it...thanks anyway for the link, but I prefer to use the MAX485s with a 5V power supply as there is an important distance between master and slave.

The diagram shows 5v going into the Nano Vin, however the specification sheet says the allowable range is 7 to 12v.

Instead try putting 5v into the pin labeled 5v.

I changed the position of the power supply, but it doesn't work and I reset everything. I tried to insert a few lines of serial.print to get info during the process and I noticed that the Master (ESP32) seems to send the command to the slave .... and the slave certainly does not receive anything ....


In your opinion, why is it unable to print the "result" and "node.ku8MBSuccess" values on the serial monitor, surely that part of the code is not executed, do you know why?

It's probably the SoftwareSerial, ESP32 has 3 uarts..
Use HardwareSerial instead..
Search it up a bit, you'll see what I mean..

good luck.. ~q

I've already tried with HardwareSerial , it doesn't work the same, but maybe I took some incorrect examples... can you tell me some good reading about it? Thank you.

maybe this..
esp32-uart-hardware-serial-ports-help
from this forum..
~q

There appears to be a espSoftwareSerial but haven't used it and don't think you do either..
depending on your board, 16 and 17 might be Serial2 by default..
do a basic sketch trying to send "hello" to the nano..
~q


I switched to "Serial2" and got the slave initialized again in the void loop....... I feel the solution is getting closer.... thanks for your support....

1 Like

I'm stranded and I can't go on anymore, is there anyone who can kindly help me? Thank you. Is it possible that there is no one who can give me a hand?

Curious,
Maybe try 232 between esp and nano..
Would test the level shifter..
On the nano, a test sketch mirroring input recvd from esp to serial monitor..
Is the nano not receiving anything or garbage??
if modbus receives garbage you get no reply, the test sketch should show this or not..
do you have a 485 adapter for your pc??
could tap the bus and see if anything is transmitted..
got a datasheet on you esp board??
might just be as simple as using different pins, if you're not transmitting anything??

good luck.. ~q

1 Like

Attached is the sketch I'm using for the slave, at the time of the call (which does not come from the master), the slave transmits some fixed values and a potentiometer value to test the sending of variable values ... the other values of the register are set to zeros


#include <ModbusSlave.h>         //libreria da caricaricare da https://github.com/yaacov/ArduinoModbusSlave
#include <SoftwareSerial.h>      //libreria standard dell'IDE di Arduino
#include <OneWire.h>
#include <DallasTemperature.h>

#define PinRicezione  10         // collegare l'RO della RS485
#define PinTrasmissione  11      //collegare il DI della RS485
#define PinControllo  3          //collegare il DE e l'RE della RS485
#define ONE_WIRE_BUS 2

SoftwareSerial RS485Serial(PinRicezione, PinTrasmissione); // RX, TX
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

int NomeSlave = 1;  //indirizzo dello slave

Modbus slave(RS485Serial, NomeSlave, PinControllo);  //Creo l'oggetto slave

void setup() {    
    Serial.begin(9600);
    pinMode(PinControllo, OUTPUT);  // RS485 control pin must be output

    sensors.begin();

    slave.cbVector[CB_READ_INPUT_REGISTERS] = readAnalogIn;     //funzione che legge su un pin analogico dello slave comandato dal master
    
    // set Serial and slave at baud 9600.
    RS485Serial.begin( 9600 );
    slave.begin( 9600 );
}

void loop() {
    slave.poll();
}

int tempC_int;

uint16_t readAnalogIn(uint8_t fc, uint16_t address, uint16_t length) {
  Serial.println("Slave 1 chiamato da Master");
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempCByIndex(0);
  if(tempC != DEVICE_DISCONNECTED_C) 
  {
    tempC_int=tempC*100;
  } 
  else
  {
    tempC_int=0;
  }
  
  // write uint16_t value to the response buffer.

  slave.writeRegisterToBuffer(0, 500);
  slave.writeRegisterToBuffer(1, 601);
  slave.writeRegisterToBuffer(2, 702);
  slave.writeRegisterToBuffer(3, analogRead(A3));
  slave.writeRegisterToBuffer(4, 804);
  slave.writeRegisterToBuffer(5, 905);
  Serial.println("Dati inviati...");
  
/*
  slave.writeRegisterToBuffer(0, tempC_int);
  slave.writeRegisterToBuffer(1, analogRead(A2));
  slave.writeRegisterToBuffer(2, analogRead(A4));
  slave.writeRegisterToBuffer(3, analogRead(A5));
  slave.writeRegisterToBuffer(4, analogRead(A6));
  slave.writeRegisterToBuffer(5, analogRead(A7));
/*
  slave.writeRegisterToBuffer(0, analogRead(A0));
  slave.writeRegisterToBuffer(1, analogRead(A1));
  slave.writeRegisterToBuffer(2, analogRead(A2));
  slave.writeRegisterToBuffer(3, analogRead(A3));
  slave.writeRegisterToBuffer(4, analogRead(A4));
  slave.writeRegisterToBuffer(5, analogRead(A5));
  slave.writeRegisterToBuffer(6, analogRead(A6));
  slave.writeRegisterToBuffer(7, analogRead(A7));
*/
  for (int i = 6; i < length; i++) {
    // scrivo i restanti valori del buffer a zero
        slave.writeRegisterToBuffer(i, 0);
  }
  return STATUS_OK;
}

I went to sleep this morning at 5.00 am I tried different PINs suggested on the various discussion groups... I saw that the best solution (that is, the one that at least makes me flash the ESP32 request sending led on the modbus) it is Serial2 for ESP32 with Rx at PIN16 and DI at PIN17... while for enabling RE and DE I can use any PIN... I'm using 33.
Also attached is the datasheet of the ESP32 seller from whom I purchased it AZ-DELIVERY
ESP-32 Dev Kit C V2_ITA.pdf (1,2 MB)

try something like this on the nano to see if it's receiving anything at all..

#include <SoftwareSerial.h>      //libreria standard dell'IDE di Arduino

#define PinRicezione  10         // collegare l'RO della RS485
#define PinTrasmissione  11      //collegare il DI della RS485
#define PinControllo  3          //collegare il DE e l'RE della RS485


SoftwareSerial RS485Serial(PinRicezione, PinTrasmissione); // RX, TX

void setup() {    
  Serial.begin(9600);
  pinMode(PinControllo, OUTPUT);  // RS485 control pin must be output
  digitalWrite(PinControllo, LOW);//receive mode    
    // set Serial and slave at baud 9600.
  RS485Serial.begin( 9600 );
}

void loop() {

  if (RS485Serial.available())
  {
    Serial.write(RS485Serial.read());
  }
}

mode a modbus test app it's in my git..
Modbus

got the modbus specs in the docs..
might be worth a peek..
~q


yes the slave receives something incomprehensible...

ok, good..
let's change the above to..

Serial.print(RS485Serial.read(), HEX);

easier to look at maybe..

~q

1 Like

maybe some caps on the chips..

MAX485

Also, try not running through the busses on the breadboards..
I see you got A and B going to + and - on the breadboard then connecting them together..
Feel like the signal could be bouncing around in there, connect one chip to the other chip..

~q