SoftwareSerial TTL MAX RS-485 Master and Multi-Slaves with SW420 and DHT11

hello!
i am looking for some help with a project,the project I'm currently working on using

  • Arduino Mega (MASTER)
  • Arduino Nano (SLAVE) with SW420 (Vibration Sensor) and DHT11 (Temperature and Humidity Sensor) (2)
  • Max485 Ttl To Rs485 Converter Module (3)

In this project I want to send temperature, humidity, and vibration data from slave to master. After studying for a few days, I tried to code like this:

MASTER NODE

//Master Node - Arduino Mega
#include <SoftwareSerial.h>
#define MAX_PIN 2 //pin enable rx tx

SoftwareSerial RS485(11,12);

String dataslave[10];
String received;
int getaran,suhu,kelembapan;
bool parsing = false;

void setup(){
  RS485.begin(9600);
  Serial.begin(9600);
  RS485.setTimeout(250);
  pinMode (MAX_PIN, OUTPUT);
}


void loop(){
  SendRequest();
}
void CekPesan(){
  while(RS485.available()>0){
    received = RS485.readString();
    Serial.println ("Data=" + received);
    parsing = true;
  }
  if (parsing){
    int x = 0;
    for (int i=0;i<10;i++){
      dataslave[i] = "";
    }
    for (int i=0; i<received.length();i++){
      if (received[i] == "#"){
        x++;
        dataslave[x] = "";
      }else {
        dataslave[x] += received[i];
      }
    }
  }
  parsing = false;
  Serial.println("Berhasil menerima data");
  CekSlave();
}

void SendRequest(){
  digitalWrite (MAX_PIN, HIGH);
  RS485.print("001");
  delay(50);
  digitalWrite (MAX_PIN, LOW);
  delay(950);
  CekPesan();

  digitalWrite (MAX_PIN, HIGH);
  RS485.print("002");
  delay(50);
  digitalWrite (MAX_PIN, LOW);
  delay(950);
  CekPesan();

}

void CekSlave(){
  getaran = dataslave[0].toInt();
  suhu = dataslave[1].toInt();
  kelembapan = dataslave[2].toInt();
  CetakData();
}

void CetakData(){
  Serial.print("Getaran:");
  Serial.println(getaran);
  Serial.print("Suhu:");
  Serial.println(suhu);
  Serial.print("Kelembapan:");
  Serial.println(kelembapan);
}

SLAVE NODE (same code, just different slave id)

#include "DHT.h"
#include <SoftwareSerial.h>
#define MAX_PIN 12
// definitions
#define DHTPIN 2          // pin of the arduino where the sensor is connected to
#define DHTTYPE DHT11     // define the type of sensor (DHT11 or DHT22)
       
SoftwareSerial RS485(10,11);
String id = "001";

String strSuhu,strLembab,strGetar;
int suhu,lembab,getar;
DHT dht(DHTPIN, DHTTYPE, 6);
int vs=9;

void setup(){
  RS485.begin(9600);
  Serial.begin(9600);
  pinMode (vs,INPUT);
  pinMode (MAX_PIN, OUTPUT);
  dht.begin();
}

void loop(){                                  
  jalankan_sensor();
}

int vibration(){
  int measurement=pulseIn (vs,HIGH);
  return measurement;
}

void jalankan_sensor(){
  suhu = dht.readHumidity();
  lembab = dht.readTemperature();
  getar = vibration();
  delay(500);
  ubah_string();
}

void ubah_string(){
  strSuhu = String (suhu);
  strLembab = String (lembab);
  strGetar = String (getar);
  cekpesan();
}

void cekpesan(){
  String DataOut = "";
  DataOut = strSuhu + "#" + strLembab + "#" + strGetar;
  Serial.println (DataOut);
  while (RS485.available()>0){
    DataIn = RS485.readString();
    Serial.println("Request:" + DataIn);
    if (DataIn == id){
      digitalWrite(MAX_PIN, HIGH);
      RS485.print(DataOut);
      delay(50);
      digitalWrite(MAX_PIN, LOW);
      Serial.println ("Mengirim:" + DataOut);
    }
  }
}

When I try to run it sensor can fetch temperature, humidity and vibration data on slave, But still can't send data to Master. Are there any suggestions for what I can do and references I should study?

I think that you will have to implement an approach where you send a request to the slave and wait for the reply. Do not send a second request over the bus before you got the reply from the slave.

Can you see that the master spoke to the slave(s) based on the serial output of the slave?

I think that you will have to implement an approach where you send a request to the slave and wait for the reply. Do not send a second request over the bus before you got the reply from the slave.

Like using While (Serial.available()) instead of IF(Serial.available() ?

Can you see that the master spoke to the slave(s) based on the serial output of the slave?

Hmm it seems that the master when sending the command "001" slave1 can receive the message but when I display the data received by the slave from the master the value is "0" instead of "001" but the slave still executes the part (if (DataIn == id) , and also on when sending data, the data received by the master is 0 and sometimes contains "?" or "▓"

Please provide a schematic and a link to the modules you are using.

It makes no sense to use swSerail on a mega, while you have still 3 out of 4 UARTs available.

void SendRequest(){
  digitalWrite (MAX_PIN, HIGH);
  RS485.print("001");
  delay(50);
  digitalWrite (MAX_PIN, LOW);
  delay(950);
  CekPesan();

  digitalWrite (MAX_PIN, HIGH);
  RS485.print("002");
  delay(50);
  digitalWrite (MAX_PIN, LOW);
  delay(950);
  CekPesan();

}

You method of sending a request is actually ok (compared to what i usually see here) and you do send a single request to a single slave, wait for a response from that slave (within a timeout) before you send a request to another slave.
Above all you must prevent more than 1 device sending data at the same time. The master should be in control of the data exchange.

First get the system working with a single slave to make sure.

You actually seem to be doing almost everything more or less correctly, but i suspect that

void jalankan_sensor(){
  suhu = dht.readHumidity();
  lembab = dht.readTemperature();
  getar = vibration();
  delay(500);
  ubah_string();
}

the delay(500); here may be causing some issues.
Your intervals should be millis() based rather than delay() based, and i think that you should actually just do the composition of the response only upon reception of the request.

does it receive the request ?
Your request reception method is rather rough, also since the requests are not separated by a separator. You may be receiving "001001001" which is not the same as the ID.

To be honest, your use of the String class with Global variables is going to use issues on an AVR. You can use String, but the way you do, chances of memory fragmentation are huge. You should switch to c-strings. It can be done with String, but it is going to be just as tricky
There is no need to concatenate the data before it is being sent, Only the ID comparison using String is actually OK the way you do it.

I second that.
As well, the suggestion about using the hardware serial ports on the Mega first is an important one. Software serial offers zero advantages on a Mega, and has significant issues, such as baud rate limitations to stone-age rates like 9600, and single-port reads(you can't have more than one SS instance reading at any one time, from what I've read). Unless you need 5 serial ports, avoid SS on the Mega.

Also, the RS485 library I am familiar with needs to be told what pin is used to "turn around" the interface in the RS485 instance where you call "begin". I.e. something like:
RS485.begin(9600,MAXPIN);
see if yours is the same. I see you are explicitly handling that turnaround pin in your code; if your RS485 library comes with examples, check to see if they're doing that, because it's usually "under the hood". Also, mine explicitly says it will not work with software serial, or something to that effect.

The OP is not using a library other than swSerial

Good point. I stand corrected.

Please provide a schematic and a link to the modules you are using.

I haven't had time to describe it but more or less for the pin configuration I use as follows:

MASTER (Arduino Mega):
RS485 Module

  • Pin A,B Master to A,B Slave
  • VCC Ground to 5V and Ground
  • Pin DE,RE to Pin 2
  • Pin RO to Pin 11
  • Pin DI to Pin 12

SLAVE 1 and 2 (Arduino Nano):
RS485 Module

  • Pin A,B Master to A,B Slave
  • VCC Ground to 5V and Ground
  • Pin DE,RE to Pin D12
  • Pin RO to Pin D10
  • Pin DI to Pin D11

DHT11

  • VCC Ground to 5V and Ground
  • Pin Data to Pin D2

SW420

  • VCC Ground to 5V and Ground
  • Pin Data to Pin
  • Pin Data to Pin D9

It makes no sense to use swSerail on a mega, while you have still 3 out of 4 UARTs available.

I plan to replace it with the ESP8266 because it requires internet in the future. So what should I do?

You method of sending a request is actually ok (compared to what i usually see here) and you do send a single request to a single slave, wait for a response from that slave (within a timeout) before you send a request to another slave.
Above all you must prevent more than 1 device sending data at the same time. The master should be in control of the data exchange.
First get the system working with a single slave to make sure.
You actually seem to be doing almost everything more or less correctly, but i suspect that.

Is there a method or something else I can use to ensure a single request is well received by the slave?

the delay(500); here may be causing some issues.
Your intervals should be millis() based rather than delay() based, and i think that you should actually just do the composition of the response only upon reception of the request.

Oh yeah, I've guessed the delay is very influential, I'll try using mills.

does it receive the request ?
Your request reception method is rather rough, also since the requests are not separated by a separator. You may be receiving "001001001" which is not the same as the ID.

That's right, when I try to use only 1 slave, the request received can be either "0010" or "$001" or something else. Do you know of a proper way or reference to submit a request?

To be honest, your use of the String class with Global variables is going to use issues on an AVR. You can use String, but the way you do, chances of memory fragmentation are huge. You should switch to c-strings. It can be done with String, but it is going to be just as tricky
There is no need to concatenate the data before it is being sent, Only the ID comparison using String is actually OK the way you do it.

Previously I tried to use the Char data type and it sent successfully but still an error like: ▓#▓#▓#▓.

Should I replace the microcontroller for Master or what should I do?

Yes well that does covers the connections, but stil the type of unit you are using is unknown.
Still let's just focus on a single slave for now.

The ESP8266 has hwSerial and an additional TX [in that can be used for debugging depending on the specific board you plan to use there are some more options, but still i am confident that your issue is with the String class.

there is Serial Input Basics which is known to work reliably if implemented correctly.
If you think that is overkill, just a simple separator in your transmission will help a lot already.

digitalWrite (MAX_PIN, HIGH);
  RS485.print("-001");
  delay(50);  
  digitalWrite (MAX_PIN, LOW);

The delay(50); is actually rather a lot at 9600bps a byte takes a bit under 1ms to transmit
and at the receiving end.

String recv = "";
uint8_t rxChars = 3;
bool start = false;
while (Serial.available() && rxChars) {
  char c = Serial.read();
  if (start) {
    recv += c;
    rxChars--;
  }
  if (c == '-') start = true;
  if (!Serial.available()) delay(1);
}

anyway something like that, you should at least test every character that comes in and see if it fits what you are expecting.

Well the main thing will be to get rid of any String variables that will not be destroyed on completion of the process. I know it's more tricky to handle, but memory fragmentation will in the end just crash the processor and when you reach that point, you will still have to do it..

Oh yeah, how far apart are you units ? and eh, if you are planning to use WiFi, why do you bother with the RS485 to begin with ? why not just read the sensor and post or make available on a local webserver ?

Alright..I'll try all the suggestions you've given. Thank you very much for that. I will provide information soon if it works or not.

I want to make a monitoring system for temperature, humidity, vibration and electric current in a server room for a college thesis. I only use wifi to connect notifications to Telegram, not to send data between microcontrollers, but using a cable. Suggestion from the supervisor that this is safer than Wifi

When sending on the Rs485, instead of delay() use RS485.flush(), flush() is a blocking function that returns when all data in the serial buffer has been sent.

Keep in mind that when using hwSerial, flush() actually blocks until all of the memory transfer has been completed, but that still leaves the byte in the FIFO and the byte ready to be transferred. (on an AVR that is, on an ESP the FIFO is huge) With swSerial this may is the way though.

And one can prevent blocking if needed by checking the result of availableForWrite() instead of using flush().

How about using ModBus RTU Library for RS485? Can?

Looks like the flush() function is actually checking for completion of the hardware transmit, instead of simply checking for an empty buffer. This is the code from the AVR board package:

void HardwareSerial::flush()
{
  // If we have never written a byte, no need to flush. This special
  // case is needed since there is no way to force the TXC (transmit
  // complete) bit to 1 during initialization
  if (!_written)
    return;

  while (bit_is_set(*_ucsrb, UDRIE0) || bit_is_clear(*_ucsra, TXC0)) {
    if (bit_is_clear(SREG, SREG_I) && bit_is_set(*_ucsrb, UDRIE0))
  // Interrupts are globally disabled, but the DR empty
  // interrupt should be enabled, so poll the DR empty flag to
  // prevent deadlock
  if (bit_is_set(*_ucsra, UDRE0))
    _tx_udr_empty_irq();
  }
  // If we get here, nothing is queued anymore (DRIE is disabled) and
  // the hardware finished transmission (TXC is set).
}

The point in this discussion is not to prevent blocking, but to know when Serial has completed the full transmission so that the RS485 can be changed from transmit to receive, and not to wait too long so that receive characters are missed.

1 Like

Hi...I'm back
I've tried all of your suggestions and now I've tried using the ModBus RTU Library from many people's suggestions to make it easier.

But, the result is still the same still can't send data. What's wrong with my code or the microcontroller or what?

Here is the code and the result

MASTER

#include <ModbusRtu.h>
#define slaveNumber 5
#define delayCom 15
#define maxQuery 2*2//slaveNumer*2
#include <SoftwareSerial.h>
SoftwareSerial mySerial(11,12);


uint8_t u8state; //!< machine state
uint8_t u8query; //!< pointer to message query

uint16_t dataBus[slaveNumber*2];
uint16_t lastPrint=100;
int slaveID[slaveNumber] = {11,12,13,14,15};
/**
 *  Modbus object declaration
 *  u8id : node id = 0 for master, = 1..247 for slave
 *  port : Serial1 port
 *  u8txenpin : 0 for RS-232 and USB-FTDI 
 *               or any pin number > 1 for RS-485
 */
Modbus master(0,mySerial,2); // ID, seriapNumber, enablePin

/**
 * This is an structe which contains a query to an slave device
 */
modbus_t telegram[slaveNumber*2];

unsigned long u32wait;

void init_modBus(){
  int num=0;
  int addr=0;
////SLAVE 1
  // Read 1 data from Slave 11
  telegram[num].u8id = slaveID[0]; // slave address
  telegram[num].u8fct = 3; // function code (this one is registers read)
  telegram[num].u16RegAdd = 0; // start address in slave
  telegram[num].u16CoilsNo = 2; // number of elements (coils or registers) to read
  telegram[num].au16reg = dataBus; // pointer to a memory array in the Arduino
  num+=1;
  addr+=2;
  
  // Write 1 data to Slave 11
  telegram[num].u8id = slaveID[0]; // slave address
  telegram[num].u8fct = 6; // function code (this one is write a multiple register)
  telegram[num].u16RegAdd = 2; // start address in slave
  telegram[num].u16CoilsNo = 1; // number of elements (coils or registers) to write
  telegram[num].au16reg = dataBus+2; // pointer to a memory array in the Arduino
  num+=1;
  addr+=1;
//
////SLAVE 2
//  // Read 1 data from Slave 2
//  telegram[num].u8id = slaveID[1]; // slave address
//  telegram[num].u8fct = 3; // function code (this one is registers read)
//  telegram[num].u16RegAdd = 0; // start address in slave
//  telegram[num].u16CoilsNo = 2; // number of elements (coils or registers) to read
//  telegram[num].au16reg = dataBus+3; // pointer to a memory array in the Arduino
//  num+=1;
//  addr+=2;
//  
//  // Write 1 data to Slave 2
//  telegram[num].u8id = slaveID[1]; // slave address
//  telegram[num].u8fct = 16; // function code (this one is write a multiple register)
//  telegram[num].u16RegAdd = 2; // start address in slave
//  telegram[num].u16CoilsNo = 1; // number of elements (coils or registers) to write
//  telegram[num].au16reg = dataBus+5; // pointer to a memory array in the Arduino
//  num+=1;
//  addr+=1;

  master.start();
  master.setTimeOut( 100 ); // if there is no answer in 100 ms, roll over
  u32wait = millis() + 40;
  u8state = u8query = 0; 
  
}

void rtuState(){
  switch( u8state ) {
  case 0: 
    if (millis() >= u32wait) u8state++; // wait state
    break;
  case 1: 
    master.query( telegram[u8query] ); // send query (only once)
    u8state++;
    u8query++;
    if (u8query >= maxQuery) 
      u8query = 0;
    break;
  case 2:
    master.poll(); // check incoming messages if communication in idle state
    if (master.getState() == COM_IDLE) {
      u8state = 0;
      u32wait = millis() + delayCom;  //delay for next state
    }
    break;
  }
}
void printData(){
  if (millis() - lastPrint>200){
    Serial.print(dataBus[0]); Serial.print(":");
    Serial.print(dataBus[1]); Serial.print(":");
    Serial.print(dataBus[2]); Serial.print("\t:\t");
    Serial.println();
  }
}

void setup() {
  Serial.begin (9600); //baud rate of Serial PC
  mySerial.begin(19200); // baud-rate of RS485
  init_modBus();
}

void loop() {
  rtuState();
  printData();
}

SLAVE

//SLAVE MENGIRIM DATA DETAK JANTUNG//
#include <ModbusRtu.h>
#include <SoftwareSerial.h>
SoftwareSerial mySerial(2, 3);
#include "DHT.h"
 
#define slaveID 11
//#define trigPin 9
//#define echoPin 10//CARI EN PIN NOMOR BERAPA
#define DHTPIN 8
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

int vs=9; //pin vibration
uint16_t suhu =0;
uint16_t lembab=0;
uint16_t getar=0;
unsigned long lastPrint=0;

// data array for modbus network sharing
uint16_t au16data[4] = {
  slaveID, 225, 8888,9999};

Modbus slave(slaveID,mySerial,4); // this is slave @1 and RS-232 or USB-FTDI

void setup() {
  Serial.begin(9600);
  mySerial.begin( 19200 ); // baud-rate at 19200
  slave.start();
  delay(10);
  dht.begin();
}

void loop() {
  slave.poll( au16data, 4 );
  if (millis() - lastPrint>200){
    Serial.print(au16data[0]); Serial.print(":");
    Serial.print(au16data[1]); Serial.print(":");
    Serial.print(au16data[2]); Serial.println();
    lastPrint = millis();
  }
  suhu = dht.readHumidity();
  au16data[1] = suhu;
}

Result:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.