1
\$\begingroup\$

I'm trying to communicate between 2 boards (NodeMCU, arduino mega) over an RS485 hardware setup. This is just for learning to later only the NodeMCU as IoT solution and the mega is simulating any rs485 device. issue: the data communication is not working at all!! These are the converters used.

circuit schematic *Arduino mega is connected to the laptop via USB cable. *breadboard's power line is receiving 5 volts & ground from a power supply unit.

this is how the system should work: the NodeMCU reads the input voltage from the Potentiometer, maps that (0 to 255) and changes the led brightness (via pwm) and also sends one of five characters (a,b,c,d,e) based on the led brightness calculated.

code excerpt from the NodeMCU Code:

#include <ESP8266WiFi.h>; //to recognize nodemcu board and use wifi later
#include <WiFiClient.h>; //to recognize nodemcu board and use wifi later
int potval = 0; //to store voltage input from potentiometer
int ledrate = 0; //to calculate led pwm rate
void setup() {
pinMode(16, OUTPUT); //labeled D0 on board,the led controlled by Potentiometer
pinMode(12, OUTPUT); //labeled D6 on board, controlling converter's enable
Serial.begin(9600); 
}
void loop() {
potval = analogRead(A0); //labeled A0 on board
  ledrate = abs(map(potval, 10, 1024, 0, 255));
  if (ledrate > 255) {
    ledrate = 254;
  } //the above 4 lines calculated pwm rate for led light
analogWrite(16, ledrate); //16 is actually labeled D0 on the board
if (ledrate >= 0 && ledrate < 50) { //compare led rate
    digitalWrite(12, HIGH); //turn converter into send mode
    Serial.println("a"); //print "a"
    delayMicroseconds(500); //wait
    digitalWrite(12, LOW);//turn converter into receive mode

    delayMicroseconds(1500);
}
//lines that print "b" or "c" etc based on ledval NOTincluded here for //simplicity
delay(3000);
}

the Arduino Mega is expected to read the characters delivered to (( Serial3 )) and print them via (( Serial )) to the arduino IDE's Serial monitor on the laptop. code excerpt from the Arduino mega:

#include <string.h>     // for strcmp()
// max length of a the data or command
#define   MAX_DATA_LEN    (15)
#define   TERMINATOR_CHAR ('\r')
// the buffer for the received chars
// 1 extra char for the terminating character "\0"
char g_buffer[MAX_DATA_LEN + 1];
// function prototype
void processSerialData(void);
String Serialinput;
void setup() {
  // put your setup code here, to run once:
  pinMode(13, OUTPUT);
  Serial.begin(9600);
  Serial.println("Mega reading a,b,c,d,e from NodeMCU. setup done");
  Serial3.begin(9600);
  pinMode(8, OUTPUT); //control RS 485 comm adapter
}
void loop() {
  // Here we read the sensors, calculate the output etc.
  delay(100);
  // Here we process data from serial line
  processSerialData();
}
void processSerialData(void) {
  digitalWrite(8, LOW); //activate the rs485 adapter into receive mode
  int data;
  bool dataReady;
  while ( Serial3.available() > 0 ) {
    data = Serial3.read();
dataReady = addData((char)data);
    if ( dataReady )
      //if dataready==true then the func addData has reutrned true meaning it has read all that was received,
      //nothing more coming, then the code goes to decide what to do with it
      processData();
    }
}
bool addData(char nextChar)
{
  // This is position in the buffer where we put next char.
  // Static var will remember its value across function calls.
  static uint8_t currentIndex = 0;

  // Ignore some characters - new line, space and tabs
  if ((nextChar == '\n') || (nextChar == ' ') || (nextChar == '\t'))
    return false;

  // If we receive Enter character...
  if (nextChar == TERMINATOR_CHAR) {
    // ...terminate the string by NULL character "\0" and return true
    g_buffer[currentIndex] = '\0';
    currentIndex = 0;
    return true;
  }

  // For normal character just store it in the buffer and move
  // position to next
  g_buffer[currentIndex] = nextChar;
  currentIndex++;

  // Check for too many chars
  if (currentIndex >= MAX_DATA_LEN) {
    // The data too long so reset our position and return true
    // so that the data received so far can be processed - the caller should
    // see if it is valid command or not...
    g_buffer[MAX_DATA_LEN] = '\0';
    currentIndex = 0;
    return true;
  }

  return false;
}

// process the data - command
// strcmp compares two strings and returns 0 if they are the same.
void processData(void) { //this void compares the strings read and decides what to do
  //it's NOT the one that reads what's arriving at the port, it's rather what gets them and
  //decides what to do based on them

  if (strcmp(g_buffer, "a") == 0 ) { //strcmp == string compare. compares two strngs
    //g_buffer is a var where in input was stored, so strcmp sees if it equals "1"
    digitalWrite(31, HIGH);
    Serial.println("received a");
    Serial.println("LED1 is on");
  }

  else if (strcmp(g_buffer, "b") == 0 ) {
    digitalWrite(31, LOW);
    Serial.println("LED1 is off");
    Serial.println("received b");
  }

  else if (strcmp(g_buffer, "c") == 0 ) {
    Serial.println("received c");
    Serial.println("Version 1.0");
  }

  else if (strcmp(g_buffer, "e") == 0 ) {
    Serial.println("received e");
    Serial.println("just an e");
  }

  else if ( strcmp(g_buffer, "d") == 0 ) {
    Serial.println("received letter d ...  will blink");
    for (int cntr = 0; cntr < 11; cntr++) {
      digitalWrite(31, HIGH);
      delay(200);
      digitalWrite(31, LOW);
      delay(75);
    }
  }

  else {
    Serial.print("Unknown command ");
    Serial.println(g_buffer);
  }
} //ends the void

here is where a problem happened:when i ran the system , BUT only the sentence from the setup was printed on the serial monitor, but nothing else at all appeared on the serial monitor afterwards.

Troubleshooting:

  1. i checked all the physical connections, an everything is connected well.
  2. i connected the NodeMcU to the laptop separately, and opened the serial monitor, it printed characters , such as a or b , etc, as expected. so the NodeMCU is working correctly.
  3. i connected the Arduino mega separately to the laptop, and changed the code to make it receive from (Serial) instead of (Serial3), and typed a into the serial monitor input in Arduino IDE, and the board received it and worked as expected.
  4. i bypassed the (TTL to RS485) converters, and directly took the the Tx and Rx from the NodeMCU and connected them to the Arduino Mega's Rx and Tx. the system actually worked and the Arduino mega received the characters from Serial3 and printed them to the serial monitor of the laptop.
  5. in an attempt to try figuring out what's happening to the board, i re-connected the circuit as in the schematic above using the converters, and modified the code to see what the port is receiving:
#include <string.h>     // for strcmp()
// max length of a the data or command
#define   MAX_DATA_LEN    (15)
#define   TERMINATOR_CHAR ('\r')
// the buffer for the received chars
// 1 extra char for the terminating character "\0"
char g_buffer[MAX_DATA_LEN + 1];
// function prototype
void processSerialData(void);
String Serialinput;
void setup() {
  // put your setup code here, to run once:
  pinMode(13, OUTPUT);
  Serial.begin(9600);
  Serial.println("Mega reading a,b,c,d,e from NodeMCU. setup done");
  Serial3.begin(9600);
  pinMode(8, OUTPUT); //control RS 485 comm adapter
}
void loop() {
  // Here we read the sensors, calculate the output etc.
  delay(100);
  // Here we process data from serial line
  processSerialData();
}
void processSerialData(void) {
  digitalWrite(8, LOW); //activate the rs485 adapter into receive mode
  int data;
  bool dataReady;
  while ( Serial3.available() > 0 ) {
    data = Serial3.read();
//following are the new lines added for troubleshooting:
String serial3input;
    Serial.println("----------------------------");
    Serial.println("serial 3 received"); //this worked so serial 3 is hardware active and receiving sth
    serial3input = Serial3.readString();
    Serial.print("Serial.readString into variable gives:   ");
    Serial.println(serial3input);
    Serial.print("Serial.readString directly gives:   ");
    Serial.println(Serial3.readString());
    Serial.print("Serial.read directly gives:   ");
    Serial.println(Serial3.read());
    Serial.print("Serial.read into a variable gives:   ");
    Serial.println(data);
    Serial.print("char(serial.read) gives:   ");
    Serial.println(char(Serial3.read()));
//end of lines added for troubleshooting

dataReady = addData((char)data);
    if ( dataReady )
      //if dataready==true then the func addData has reutrned true meaning it has read all that was received,
      //nothing more coming, then the code goes to decide what to do with it
      processData();
    }
}
bool addData(char nextChar)
{
  // This is position in the buffer where we put next char.
  // Static var will remember its value across function calls.
  static uint8_t currentIndex = 0;

  // Ignore some characters - new line, space and tabs
  if ((nextChar == '\n') || (nextChar == ' ') || (nextChar == '\t'))
    return false;

  // If we receive Enter character...
  if (nextChar == TERMINATOR_CHAR) {
    // ...terminate the string by NULL character "\0" and return true
    g_buffer[currentIndex] = '\0';
    currentIndex = 0;
    return true;
  }

  // For normal character just store it in the buffer and move
  // position to next
  g_buffer[currentIndex] = nextChar;
  currentIndex++;

  // Check for too many chars
  if (currentIndex >= MAX_DATA_LEN) {
    // The data too long so reset our position and return true
    // so that the data received so far can be processed - the caller should
    // see if it is valid command or not...
    g_buffer[MAX_DATA_LEN] = '\0';
    currentIndex = 0;
    return true;
  }

  return false;
}

// process the data - command
// strcmp compares two strings and returns 0 if they are the same.
void processData(void) { //this void compares the strings read and decides what to do
  //it's NOT the one that reads what's arriving at the port, it's rather what gets them and
  //decides what to do based on them

  if (strcmp(g_buffer, "a") == 0 ) { //strcmp == string compare. compares two strngs
    //g_buffer is a var where in input was stored, so strcmp sees if it equals "1"
    digitalWrite(31, HIGH);
    Serial.println("received a");
    Serial.println("LED1 is on");
  }

  else if (strcmp(g_buffer, "b") == 0 ) {
    digitalWrite(31, LOW);
    Serial.println("LED1 is off");
    Serial.println("received b");
  }

  else if (strcmp(g_buffer, "c") == 0 ) {
    Serial.println("received c");
    Serial.println("Version 1.0");
  }

  else if (strcmp(g_buffer, "e") == 0 ) {
    Serial.println("received e");
    Serial.println("just an e");
  }

  else if ( strcmp(g_buffer, "d") == 0 ) {
    Serial.println("received letter d ...  will blink");
    for (int cntr = 0; cntr < 11; cntr++) {
      digitalWrite(31, HIGH);
      delay(200);
      digitalWrite(31, LOW);
      delay(75);
    }
  }

  else {
    Serial.print("Unknown command ");
    Serial.println(g_buffer);
  }
} //ends the void

when running the new code, the Arduino IDE serial monitor, receiving from the Arduino Mega, would show something like: >

Mega reading a,b,c,d,e from NodeMCU. setup done

serial 3 received Serial.readString into variable gives:
Serial.readString directly gives:
Serial.read directly gives: -1 Serial.read into a variable gives: 244

char(serial.read) gives: ⸮

<

I thought I've misunderstood the converters, so I took one to a separate breadboard and manually added logic high/low to its inputs and tried combinations for hours. here's the results: mapping the RS485 adapter's outputs

Observation: the converter is outputting logic high (5V) ALL THE TIME at DI & RO , regardless of send/receive mode, signal input or none, and this is going to the Arduino mega tx/rx.

  • So, why is the communication not working when done over the RS485 converters??!!
\$\endgroup\$
  • \$\begingroup\$ We don't expect every post to be perfect, but posts with correct spelling, punctuation, and grammar are easier to read. They also tend to get read and upvoted more frequently. Remember, you can always go back at any time and edit your post to improve it. See Write to the best of your ability on the site's help pages. If English is not your first language then add this to your user profile so we can make allowances. \$\endgroup\$ – Transistor Jun 29 at 9:17
  • \$\begingroup\$ @Transistor, i've changed it a bit now! thank you for the tip ! \$\endgroup\$ – Martin Krause Jun 29 at 9:26
  • 1
    \$\begingroup\$ Your diagram has a common/ground wire missing between the pair of RS485 converters. Please clarify whether on not this is present in your setup, and also how these devices are powered. \$\endgroup\$ – brhans Jun 29 at 13:49
  • \$\begingroup\$ The A and B wires are crossed on the diagram. A goes to A and B to B. Also the converter in the link does not match the diagram, in diagram you have RE and DE pins, but the link has device with RX, TX, VCC and GND, no RE or DE. Any explanations? \$\endgroup\$ – Justme Jun 29 at 15:54
  • \$\begingroup\$ @Justme , i've corrected the schematic. my actual wiring had A to A and B to B. i've also corrected the link to the converter. silly mistake on my side, sorry. i just had many tabs open trying to figure out what i'm doing wrong \$\endgroup\$ – Martin Krause Jun 29 at 17:08
1
\$\begingroup\$

To be completely sure you should see what's really happening on your bus with a scope.

But reading your code I strongly suspect you have at least one problem here:

digitalWrite(12, HIGH); //turn converter into send mode
Serial.println("a"); //print "a"
delayMicroseconds(500); //wait
digitalWrite(12, LOW);//turn converter into receive mode

What you do there is take hold of the bus before writing, pass the data to the buffer and wait for 500 μseconds. Then you immediately release the bus. This seems to be the way to do it, but there is an important flaw; well, actually a couple: first off, at 9600 baud it takes 1041 μseconds to transmit a byte, so you need to wait longer. Second: even when this is a microcontroller I'd assume there might be some tiny overhead or clock mismatch that might give you trouble.

I think only increasing this delay would make it work, maybe with a safety margin, say 1200 μseconds. One way to improve on this is to add Serial.flush() after writing to the port, that blocks your code until the byte is sent to the UART. Since you're sending just one byte I'm not sure flushing only (with no additional delay) works for your situation, so you might need to play a bit to find the right solution (if you have a scope it would make it easier, you can monitor the DE/~RE pin together with the bus and see if you're trimming the tail of the byte or you are sending it through). This problem is very well documented: see, for instance here, or here. If you cannot manage with the trial and error because you don't have a scope or the patience, another thing you can do is to take a look at Nick Gammon's libraries for RS-485.

Since this is SE-EE I will end with a hardware side note: you can avoid most issues related to switching the direction control signal on a half-duplex RS485 link if your devices support hardware direction control. The ones you have don't, but you can find others that do. You can also use the good old 555 timer to switch the DE/~RE line as I explained here.

\$\endgroup\$
  • \$\begingroup\$ first of all , thank you very, VERY much for the well written explanation. i tried gradually increasing the delay up to 1500 microseconds but it didn't help! so, i added a serial.flush() line and it solved the problem!! the Mega board is now receiving the characters and acting accordingly! i'm now taking my time reading the tutorials you've linked , so that i become better informed on the topic . thank you very much! highly appreciated \$\endgroup\$ – Martin Krause Jul 11 at 10:28
  • \$\begingroup\$ You're welcome Martin. I'm glad my answer was helpful. There might be a couple more useful Q&As on StackOverflow about this topic, mostly related to Python and C/C++ stuff but you might find them informative. Happy coding and networking! \$\endgroup\$ – Marcos G. Jul 11 at 10:38
1
\$\begingroup\$

The adapter uses MAX485 which needs a 5V +/- 10% supply to operate, so powering it from 3.3V supply is out of specifications for the chip.

\$\endgroup\$
  • \$\begingroup\$ thank you for noting that. i did re-wire the circuit but it sadly didn't cause a difference! I've just downloaded fritzing , i'll upload a proper schematic and a code excerpt to make this easier to solve ! @Justme \$\endgroup\$ – Martin Krause Jun 30 at 19:14
  • \$\begingroup\$ This is absolutely right but if you use a 5V power supply for the transceivers you can make them work with 3.3V logic on the TX and DE/~RE signals. If you look at the datasheet you can see that the VIH is 2 V and the VOH is 3.5V. I've used these same transceivers with 3.3V logic successfully. \$\endgroup\$ – Marcos G. Jul 3 at 6:25
  • \$\begingroup\$ This is true; MAX485 needs 5V supply and it has TTL level logic inputs so it works with 3.3V level signal. But for completeness sake it must also be mentioned that MAX485 logic level output is 5V so it can directly be connected to other 5V devices or devices with 5V tolerant inputs. Connecting it to input supporting only 3.3V levels can do damage. So be careful to first check if 5V and 3.3V devices can be mixed freely or not. \$\endgroup\$ – Justme Jul 3 at 9:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.