RS485 communication problem

I am attempting to communicate using MAX485 TTL to RS-485 Modules I purchased with an Arduino MEGA and NANO.

I wrote the sketches for the Mega to poll the Nano and the Nano to reply if the data poll was for it.

I am sending 2 numbers with start and end markers ie, <11,1>, the first number is the number associated with the Nano, an ID tag(trgetNumer), the second is a Query tag, If the Nano ID matches the TargetNumber value Defined in the Sketch, then it replies with the data determined by the Query The data will be scores of 4 teams, 1 current scores, 2 high scores, eventually this data will be retrieved from an array.

The issue I am having is that it is not working.

When I connect tx1,rx1 of the Mega to pins 10,11 of the Nano it works fine

When I try to use the rs485 boards I get nothing.

can someone please explain why this is not working? I have a connected to A and B to B, 5 v and ground, common grounds on boards

I have Rx going to R0 and TX going to DI on both boards

Mega code

/*

    arduino Mega
    sends poll to Nanos and waits for response
    Reads a serial input string with start end Markers.
  Parses the string to get the data, verifies that the data is correct


  created 22 Jun 2018
  by Mark Noll



*/


#define SerialTxControl 8   //RS485 Direction control

#define RS485Transmit    HIGH
#define RS485Receive     LOW

#define Pin13LED         13

const byte numChars = 250;


/*-----( Declare Variables )-----*/
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
boolean newData = false;
int Team1Score;
int Team2Score;
int Team3Score;
int Team4Score;
int TargetNumber;
int nValidate;
  int j = 1;
//============

void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';

  char rc;

  while (!Serial1) {
    // wait for serial port to connect. Needed for native USB port only
  }
  
  while ((Serial1.available() > 0) && (newData == false)) {

    rc = Serial1.read();
   
    if (recvInProgress == true) {

      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      if (rc == endMarker)  {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        Serial1.flush(); 
        newData = true;
      }
    }

    else if (rc == startMarker) {
      recvInProgress = true;

    }
  }
}


void parseData() {      

  char * strtokIndx; 
  strtokIndx = strtok(tempChars, ",");    
  nValidate = atoi(strtokIndx);     

 strtokIndx = strtok(NULL, ","); 
  TargetNumber = atoi(strtokIndx);     

  strtokIndx = strtok(NULL, ","); 
  Team1Score = atoi(strtokIndx); 

  strtokIndx = strtok(NULL, ","); 
  Team2Score = atoi(strtokIndx);     

  strtokIndx = strtok(NULL, ","); 
  Team3Score = atoi(strtokIndx);     

  strtokIndx = strtok(NULL, ","); 
  Team4Score = atoi(strtokIndx);    




}
void setup()   
{
  // Start the built-in serial port, probably to Serial Monitor
  Serial.begin(9600);  
  Serial1.begin(9600);


  Serial.println("Arduino Mega RS485 Serial Communication");

  pinMode(Pin13LED, OUTPUT);
  pinMode(SerialTxControl, OUTPUT);

  digitalWrite(SerialTxControl, RS485Receive);  // Init Transceiver

}

void loop()   
{

  digitalWrite(Pin13LED, HIGH);  
  digitalWrite(SerialTxControl, RS485Transmit);  

  if(j==1){
         Serial1.println("<11,1>"); 
       
           }
 if(j==-1){
         Serial1.println("<11,2>"); 
        
          }

  j=j*-1;
  
      
  digitalWrite(Pin13LED, LOW);  

  digitalWrite(SerialTxControl, RS485Receive);  

  delay(20);//  pause to allow Nano to reply

  // get the data
  recvWithStartEndMarkers();


  if (newData == true) {
    strcpy(tempChars, receivedChars);



    parseData(); 
     newData = false;

     
    //Display for now and later VALIDATE and add to array
    Serial.print("Validation: ");
    Serial.println(nValidate);
    Serial.print("Team1: ");
    Serial.println(Team1Score);
    Serial.print("Team2: ");
    Serial.println(Team2Score);
    Serial.print("Team3: ");
    Serial.println(Team3Score);
    Serial.print("Team4: ");
    Serial.println(Team4Score);



    //delay(60);
  }

}

nano code

/*

  Reads a serial input string with start end Markers.
  Parses the string to get the data, verifies that it is for this Module, 
  if it is, it  Sends the Data requested by the poll
  created 22 Jun 2018
  by Mark Noll

*/

//Arduino Nano 


#include <SoftwareSerial.h>

#define SSerialRX        10  //Serial Receive pin  
#define SSerialTX        11  //Serial Transmit pin

#define TargetNumber    11  //  number of this target, each target has unique ID 11-27

#define SSerialTxControl  8  //RS485 Direction control 
#define RS485Transmit    HIGH
#define RS485Receive     LOW

#define LEDPin         13

int nTarget;  // target number (this trget)
int nQuery;   //  Query  1 = team scores, 2= possibly use for highest scores saved to array

const byte numChars = 250;

char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing


// variables to hold the parsed data
boolean newData = false;

SoftwareSerial RS485Serial(SSerialRX, SSerialTX); // RX, TX


void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  
  char rc;

  while ((RS485Serial.available() > 0) && (newData == false)) {
   
      rc = RS485Serial.read();
    
      if (recvInProgress == true) {
      
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      if (rc == endMarker)  {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }

    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}


void parseData() {      // split the data into its parts

  char * strtokIndx; // this is used by strtok() as an index
 
  strtokIndx = strtok(tempChars, ",");     // get the first part - the string
 
  nTarget = atoi(strtokIndx);     // convert this part to an integer

  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  ;
  nQuery = atoi(strtokIndx);     // convert this part to an integer

}


void setup() {
 
  pinMode(SSerialTxControl, OUTPUT);//Control pin for RS485 
  pinMode(LEDPin, OUTPUT);// LED  

  // Open serial communications and wait for port to open:
  RS485Serial.begin(9600);
  Serial.begin(9600);
  digitalWrite(SSerialTxControl, RS485Receive);  // Init Transceiver

  while (!RS485Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("RS485 serial established");

}

void loop() {
        
    // get the data

    digitalWrite(LEDPin,LOW);
    recvWithStartEndMarkers();

    if (newData == true) {
      strcpy(tempChars, receivedChars);
      // this temporary copy is necessary to protect the original data
      //   because strtok() used in parseData() replaces the commas with \0
      
       //test data
       Serial.print("received :");
      Serial.println(tempChars);
      
      parseData();
       newData = false;
       
        if (nTarget == TargetNumber){

          if (nQuery == 1){// polled for team scores
            
                  digitalWrite(LEDPin,HIGH);
                       // debugging making sure we hve serial
                
                   digitalWrite(SSerialTxControl, RS485Transmit);  
                    RS485Serial.println("<99,11,11,22,33,44>"); // Send the byte back  test data  
                    
                    Serial.println("sent: <99,11,11,22,33,44>" );
                 
                  delay(10);   
                  digitalWrite(SSerialTxControl, RS485Receive);  
          }
                if (nQuery == 2){// polled for team scores
            
                  digitalWrite(LEDPin,HIGH); // flash  when transmit
                       // debugging making sure we hve serial
                   digitalWrite(SSerialTxControl, RS485Transmit);   
                    RS485Serial.println("<99,11,1,2,3,4>"); // Send the byte back  test data  99 validation, 11, 22,33,44 test data for teams
                    
                    Serial.println("sent: <99,11,1,2,3,4>" );
                 
                  delay(10);   
                  digitalWrite(SSerialTxControl, RS485Receive);   
          }
      
              }
     
      newData = false;

    }
 
  

}

A needs to be pulled high and B low, try a 680 ohm for each.

DKWatson:
A needs to be pulled high and B low, try a 680 ohm for each.

if I understand a 680 ohm resistor from A to 5v and B to ground?

Thanks

Yep.

Purely for information, at the physical layer of any communications protocol stack, there exists an idle state. With asynchronous transmission the start of transmission is indicated and detected by a level shift away from the idle state. You cannot leave these signals floating, everybody gets confused. In some instances and with some MCUs you can use the internal pull-up/down resistors but with the 328P I've always found the externals to make for a much cleaner signal.

DKWatson:
Yep.

Still no Joy

A to 5v B to gnd with 680Ω resistors

Ok, try this.

On the Nano you have Rx/Tx connected ti pins 0/1 and TE connected to 3. In your code you declare 10/11 and 8.

DKWatson:
Ok, try this.

On the Nano you have Rx/Tx connected ti pins 0/1 and TE connected to 3. In your code you declare 10/11 and 8.

Ah yes, the image was the initial connection.

I switched to 10,11 with software serial to debug using the Serial Monitor.

I will switch back to 0,1 and try it since I know the logic works

  if(j==-1){

Serial1.println("<11,2>");       
          }
  j=j*-1;
  digitalWrite(SerialTxControl, RS485Receive);

At the point in time when you switch the control line from transmit to receive, how many characters have been sent out? Probably none. It may have sent the first BIT of '<' but you haven't waited enough time for the transmission to finish even one single character.

The simple solution is to add Serial1.flush(); before changing the control line.

A more complex solution is to monitor the number of characters in the outgoing buffer (via Serial1.availableForWrite()) and then change the control line a number of milliseconds after the buffer is emptied.

Morgan's correct. Another way to attack it is to frame your transmission with STX/ETX and then you know for sure when the packet is complete.

DKWatson:
Morgan's correct. Another way to attack it is to frame your transmission with STX/ETX and then you know for sure when the packet is complete.

I just ran across this library GitHub - Protoneer/RS485-Arduino-Library

It seems they have included an STX ETX protocol in the RS485_SendMessage() function

i will incorporate this and try again

thanks for all your help

MorganS:
At the point in time when you switch the control line from transmit to receive, how many characters have been sent out?

I found GitHub - Protoneer/RS485-Arduino-Library which has STX ETX protocols in a library

Thanks for your assistance

Rewriting code now

The other problem is with SoftwareSerial itself, it doesn't play well with others. It blocks, which is enough of a problem, but it seizes all pin change interrupts while its doing it. Some may feel that comms are that important but maybe not. If you employ a CRC you can always ask for a re-transmit if an error is detected. Other functions may not be so forgiving.

If, for example you try to use a quad encoder, which is pin interrupt drive, you'll find you get a compile error as soon as you try to take command of PCINT. Some time ago, to get around just that issue, I chopped SoftwareSerial into 3 bits, SSerial, SSerialC and SSerialD. Each commands control over the port specified only and leaves the interrupts on the other two ports alone. In all other aspects, it functions just as SoftwareSerial except you instantiate with SSerialn(Rx,Tx) where the n as B, C or D. As you're using pins 10/11 on the Nano, I've attached SSerialB just in case you need it. If you try to use it on ports C or D, there are no compile or run-time errors, it simply does not work.

Just looking I see they're small files so I'll attach them all, maybe others will find a use for them as well.

SSerialB.zip (5.22 KB)

SSerialC.zip (5.14 KB)

SSerialD.zip (5.14 KB)

I was going to just use the RS485-Arduino-Library, but it has the following

const byte RX_PIN = 2;
const byte TX_PIN = 3;
const byte ENABLE_PIN = 4;

it also uses SerialSoftware.

instead of rewriting the library I just took what I needed

I took the STX ETX protocols for send since I am already using StartEndMarkers on the receive side I know when I have a good packet there

This is the code I added

const int maxMsgLen = 32;
const int STX = 2;
const int ETX = 3;

typedef void (*WriteCallback)  (const byte what);    // send a byte to serial port
typedef int  (*AvailableCallback)  ();    // return number of bytes available
typedef int  (*ReadCallback)  ();    // read a byte from serial port


char Message[maxMsgLen+1] ;
void fWrite (const byte what)
{
  Serial.write (what);  
  //Serial.print((int)what);
  //Serial.print(' ');
}

int fAvailable ()
{
  return Serial.available ();  
}

int fRead ()
{
  return Serial.read ();  
}


byte crc8 (const char *addr)
{
  byte len = 0;
  while((byte)addr[len] != '\0')
  {
    len++;
  }
  
  byte crc = 0;
  while (len--) 
    {
    byte inbyte = *addr++;
    for (byte i = 8; i; i--)
      {
      byte mix = (crc ^ inbyte) & 0x01;
      crc >>= 1;
      if (mix) 
        crc ^= 0x8C;
      inbyte >>= 1;
      }  // end of for
    }  // end of while
  return crc;
}  // end of crc8

void EncodeMessage(char message[maxMsgLen+1], unsigned char data[maxMsgLen+1+3])
{
  int index=0;
  int index2 = 0;
  
  data[index] = STX;
  
  while(message[index2] != '\0')
  {
    index++;
    data[index] = (char)message[index2];
    index2++;
  }
  
  index++;
  data[index] = ETX;
  
  index++;
  data[index] = (char)crc8(message);
  
  index++;
  data[index] = '\0';
}


bool Serial_SendMessage(char message[maxMsgLen+1],WriteCallback fWrite,int EnablePin)
{  
  unsigned char temp[maxMsgLen+1+2];
  
  EncodeMessage(message,temp);
 
  digitalWrite (EnablePin, HIGH);  // enable sending
  int counter =0;
  while(temp[counter] != '\0')
  {
    fWrite(temp[counter]);
  counter++;
  }  
  
  delay(1); // Delay abit or else the Serial will switch off before the last byte has been sent.
  digitalWrite (EnablePin, LOW);  // disable sending  
  return true;
}

I got it all coded up and tested it on TX RX wired to RX TX

Works like a champ

I then wired up the RS485 boards and nothing.

I have 5 so I tried all 5 in various combinations
NOTHING!

What could be the problem?

I am at a loss

The code works fine and it is hard to believe that 4 of the 5 boards I just purchased are bad.

Any ideas??

  delay(1); // Delay abit or else the Serial will switch off before the last byte has been sent.

digitalWrite (EnablePin, LOW);  // disable sending

I don't think you realize how slow Serial is compared to the speed your program runs.

9600 baud is 9600 bits per second. There's 10 bits per byte because you have to add start- and stop-bits to the 8 bits of actual data. So 960 bytes per second. One byte takes just over 1 millisecond to be transmitted. You just sent a bunch so you need to wait more.

Look at the libraries again. See how they turn off the enable pin after the last character has been sent.

Or use a Teensy - those processors have dedicated hardware to do this for you. Use the Serial1.transmitterEnable(pin) function to tell it which pin and sit back and relax.

MorganS:
I don't think you realize how slow Serial is compared to the speed your program runs.

Where could I add a delay and what length would you suggest?

Thanks

There is no delay you can add. You need to either wait until the outgoing Serial buffer is empty (plus a delay) or monitor the state of that buffer and change the pin when required.

Did you find anything useful in the RS485 libraries?

STX and ETX serve the same purpose as < and >; there was no need to change that. The advantage of STX and ETX is that you can use < and > in the text that you send without having to escape them.

sterretje:
STX and ETX serve the same purpose as < and >;

I was not checking <> on TX only RX

I am new to this and just trying to find out why this is not working.

I am following the advice given. then when I do I am told that I should do something else.

I appreciate the help but vague suggestions without any direction is not helping.

I've attached a couple of files for you to have a look at. They used to be on my Github page but since the big M now owns everything there I don't know that I'll be sharing code that way any more.

Anyhow, I use RS485 as the backbone comm link in almost everything I do and have never had the problems that you've encountered. I can only conclude that I'm not making myself understood.

The attached is intended for a 328P and uses SSerial for the additional port. It also has extended packet modes for address, STX/ETX, ACK/NAK and CRC8. There's also a switch that allows it to be compiled for the mega2560.

functions.h (4.12 KB)

P2P485_1.0.ino (10.9 KB)