Electronic – TTL to RS485 converter faulty outputs

rs485

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??!!

Best Answer

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.