ArduinoMega2560 - communicating through RS485 in both directions using MAX485

Hi everyone!

Just a little introduction to my problem:

  • need to connect two devices. Control unit “Amap99”, which will be the master, and “ArduinoMega2560”, which is going to be a slave. To solve this problem, I decided to use interface/bus “RS485”. Btw I’m so sorry for my terminology …
  • I manage create a program for “Amap99”, which can successfully send everything I want it to, through “RS485”. It can also without any problem receive data, again through “RS485” (all in one program). Everything has been tested on oscilloscope and looks fine (I mean the data it is sending out).
  • The problem is with “Arduino” – I can make a program, what will ONLY SEND data, and it works fine. And I can also make a program, what will ONLY RECEIVE data, and again – it works fine. But if I want to unite them – it will do some incredible things I though in this universe were impossible.

What I already have:

  • “Amap99” is sending every three seconds the following string: “{A1}{BB}{41}{42}{CC}{DD}”
    -I can also send “… {43}{44} …” instead of “… {41}{42} …”, by pressing a button. This all works fine so there’s no problem on the “Amap99”.
  • “Amap99” can also without problems receive any data send to him through “RS485”.
  • “Arduino” can only receive the data in one program, and then send them to the PC through USB port. OR in other program it can send the following string to “Amap99” without problem: “{A1}{BB}{41}{42}{CC}{DD}”.
  • But it can’t do both in ONE program.

What I’m using:

  • TTL to RS485 converter, using MAX485 (which can be found here: Amazon.com)
  • Arduino ATmega2560
  • Compact control system Amap99 (everything for it is on this side: AMiT Automation)
  • "Hercules utility Setup", for watching data sended from Arduino through USB port.

- If I get it right the converter should be used like this:
1)To receive I need to CLOSE the SerialTxControlPin for few milliseconds.
2)To send data I need to OPEN the SerialTxConctrolPin for few milliseconds.
Is this the right way to use it?

What I want to achieve with “Arduino”:

  • I need the “Arduino” to act as a slave. So whenever it receive, some data it needs to immediately respond. For this example I just want it to send back what it got through the “RS485”.
    - So when it receives “{A1}{BB}{41}{42}{CC}{DD}”, I want it to send back the exact same string. This is something I just can’t fit into one program and make it work properly.
  • In one program I can send the data, without any problem. And in other program I can receive them. But just can’t do both!
    - So pleaseeee – if you would have any suggestion for the following programs I would be reaaaally thankful!

[1st. program] This following code I used as a "receiver":

#include <SoftwareSerial.h>

#define SSerialTxControl 3   // Pin for MAX485 - HIGH send, LOW receive

 
int byteReceived;
int rx_buff[64]; 
int rx_lenght = 0; 

void setup()  
{
  Serial.begin(9600);   //Serial communication for USB.
  Serial3.begin(9600);  //Serial communication for RS48 - 14 pin as Tx and 13 pin as Rx.
 
  pinMode(SSerialTxControl, OUTPUT);  
  digitalWrite(SSerialTxControl, LOW); //Just receive data from RS485
}
 
 
void loop()  {
  digitalWrite(SSerialTxControl, LOW); //Just receive data from RS485
}

void serialEvent3()
{
   //When receive data read it.
   while (Serial3.available())  
   {   
   //Save it into variable "rx_buff" 
    rx_buff[rx_lenght] = Serial3.read();
    rx_lenght++;
   }
   if(rx_buff[0] != 0x00)
   {
    for(int i=0;i<rx_lenght;i++){Serial.write(rx_buff[i]);} //Then just send it one by one through USB to the PC
    rx_lenght = 0;
   }
   else
   {
   rx_lenght = 0;
   }
}

[2st. program] This one I use as a "sender":

#include <SoftwareSerial.h>
 
#define SSerialTxControl 3

#define LED 13

int rx_buff[64];     //RX buffer
int tx_buff[64];     //TX buffer
unsigned int rx_lenght = 0;
 
void setup()  
{
  Serial3.begin(9600);
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
  pinMode(SSerialTxControl, OUTPUT); 
} 
 
void loop()  {
   digitalWrite(LED, HIGH);
   delay(500);
   digitalWrite(SSerialTxControl, HIGH); //Prepare MAX485 for sending data.
   delayMicroseconds(28);
   Serial3.write(0xA1); // First two starting bytes
   Serial3.write(0xBB); // Just some another two bytes
   Serial3.write(0x41); // Data
   Serial3.write(0x42); // Data
   Serial3.write(0xCC); // Just some another two bytes
   Serial3.write(0xDD); // Stop bytes
   delayMicroseconds(28); 
   digitalWrite(SSerialTxControl, LOW); //Prepare MAX485 for receiveing data
   delay(500);

   digitalWrite(LED, LOW);
   delay(500);
   digitalWrite(SSerialTxControl, HIGH); //Prepare MAX485 for sending data.
   delayMicroseconds(28);
   Serial3.write(0xA1); // First two starting bytes
   Serial3.write(0xBB); // Just some another two bytes
   Serial3.write(0x43); // Data
   Serial3.write(0x44); // Data
   Serial3.write(0xCC); // Just some another two bytes
   Serial3.write(0xDD); // Stop bytes
   delayMicroseconds(28);
   digitalWrite(SSerialTxControl, LOW); //Prepare MAX485 for receiveing data
   delay(500);
}

[3rd program] But when I want to unite them it doesn't work. It doens't receive data properly and it doesn't even send BIT to the "Amap99"

#include <SoftwareSerial.h>
 
#define SSerialTxControl 3              // Pin for MAX485 - HIGH send, LOW receive
 
volatile int rx_buff[64];
volatile int rx_length=0;
 
void setup()  
{
  Serial.begin(9600);                  //Serial communication for USB.
  Serial3.begin(9600);                 //Serial communication for RS48 - 14 pin as Tx and 13 pin as Rx.
  
  pinMode(SSerialTxControl, OUTPUT);  
  digitalWrite(SSerialTxControl, LOW);   //Prepare MAX485 for receiveing data

}
 
 
void loop()  {
  digitalWrite(SSerialTxControl, LOW);   //Prepare MAX485 for receiveing data
}


void serialEvent3 ()
{
   while(Serial3.available())      //When you receive some data save it into the rx_buff[] variable
   {    
    rx_buff[rx_length] = Serial3.read();
    rx_length++;
   }  
   
   for(int i=0;i<rx_length;i++)         //Then send it to the USB
   {
   Serial.write(rx_buff[i]);
   }
   
   digitalWrite(SSerialTxControl, HIGH);    //Prepare MAX485 for sending data.
   delay(100);                              //Give him some time to open
   for(int i=0;i<rx_length;i++)
   {
   Serial3.write(rx_buff[i]);              //And then send the data you got back through the RS485
   }
   digitalWrite(SSerialTxControl, LOW);    //Prepare MAX485 for receiveing data
   delay(100);
   
   rx_length=0;
}

{A1}{41}{DD}.png

Array send by receiver.png

Serial is

S

L

O

W

...really slow!

Your little while(Serial3.available) loop will usually only ever see one character. There's probably more characters coming down the pipe but you can't tell until after they arrive. So if you immediately turn around and try to transmit, you'll probably collide with the next character.

To make this work, your receiver-retransmitter needs to understand the packets of data that it's being sent. It needs to identify the end of a transmission so that it can turn over to transmit mode without clashing with the incoming data.

Also, get rid of the serialEvent3() function. It's a relic of an old idea from Arduino which never really worked out. You can do all of this in the main loop.

You have an Arduino Mega2560 with four hardware serial ports, so I do not see any reason that you need to use SoftwareSerial, so get rid of it in all cases. You do not appear to have any code that uses it anyhow.

I looked at the company which produce the AMAP99 series boards, in Czech Republic and for reference this is a link to the enhanced model which has Ethernet: AMiT Automation

You do not say what communications module is in the AMAP99, as it states on the web site that communications modules need to be ordered separately ?

Here is a link I think you will find useful, put together by one of our forum mods down here in Australia, Nick Gammon. It details a lot of good information on formerly RS-485, which is now TIA-485: Gammon Forum : Electronics : Microprocessors : RS485 communications

In the document above, you will learn that you will need time for the sending of data out through the serial port before you can take the pin that controls Tx/Rx direction low from transmitting to receiving.

Anyhow, I'll be interested to hear how you go.
Once you understand the AMAP99 data protocol, you will then be able to implement the correct communications code into the Arduino.


Paul - VK7KPA

Hi again!
I’am so sorry for late answer – didn’t had time yesterday at all + I won’t be able to try out new programs until monday :C

To "MorganS":

  1. Okey okey man thanks! I actually kept the baudrate pretty low, becase I have read somewhere that on higher baudrates “SoftwareSerial” isn’t that effective :open_mouth: Is it true or just a rumor? So if I speed it up, up to 57600 do you think SWserial will handle it?
  2. Alright! So if I add a little while function like this:
boolean stop_byte = false;
…
while( stop_byte != true){
   if(Serial3.available())//When you receive some data save it into the rx_buff[] variable
   {    
    rx_buff[rx_length] = Serial3.read();
    if(rx_buff[rx_length] == 0xDD)stop_byte = true;  
    rx_length++;  
   }    
}

Should it solve the problem? :open_mouth:
3) Why? :C I really liked this function because I don’t need to ask for Serial.availeble() in loop function (I have a feeling like its “unprofessional” to ask the program constantly “Is there something in the buffer …. Is there something in the buffer … ” :D). Doesn’t it work like an external interrupt? But okey! Will try it out without the serial event!

To "Rockwallaby":

  1. Umm – “hardwareSerial”? What is it? :open_mouth: Tried to look it up on the internet but didn’t find much useful information :open_mouth: I just found some libraries but not description how it works or what it does or what it is :smiley:
  2. As I said! “Amap99” configuration is completely fine! It has all the modules it needs to have and is working perfectly! 
  3. Yeah I know they exist – but it’s a school project and those are sooo expensive! And school won’t support me with such amount of money …
  4. Thanks so much for it!
  5. Surly will inform you! I will try new programs on Monday! Looking forward to it :slight_smile:
    [1st. program] So I made a new program from what you said – do you think this one has a better chance to work? :smiley:
#include <SoftwareSerial.h>

#define SSerialTxControl 3   // Pin for MAX485 - HIGH send, LOW receive

 
int byteReceived;
int rx_buff[64]; 
int rx_lenght = 0; 
boolean stop_byte = false;

void setup()  
{
  Serial.begin(57600);   //Serial communication for USB.
  Serial3.begin(57600);  //Serial communication for RS48 - 14 pin as Tx and 13 pin as Rx.
 
  pinMode(SSerialTxControl, OUTPUT);  
  digitalWrite(SSerialTxControl, LOW); //Just receive data from RS485
}
 
 
void loop()  
{

  if(Serial3.available()){
   while( stop_byte != true)
   {
     if(Serial3.available())//When you receive some data save it into the rx_buff[] variable
     {    
      rx_buff[rx_length] = Serial3.read();
      if(rx_buff[rx_length] == 0xDD)stop_byte = true;  //if the received pair of bytes is equal to 0xDD then stop reading
      rx_length++;  
     }    
   }

   stop_byte=false;
   
   if(rx_buff[0] != 0x00)
   {   
   digitalWrite(SSerialTxControl, HIGH); // Get ready for sending data 
    for(int i=0;i<rx_lenght;i++)
      {
        Serial.write(rx_buff[i]);    //Then just send it one by one through USB to the PC
        Serial3.write(rx_buff[i]);   //And also send the data through RS485 to "Amap99"
      } 
    rx_lenght = 0;
    delay(10);   //Wait some time for all the characters to be sended out!
    digitalWrite(SSerialTxControl, LOW); //Get ready for receiving data
   }
   else
   {
   rx_lenght = 0;
   }
   }
   
}

Thanks so much again for everything!

JacobSzlaur:

  1. Umm – “hardwareSerial”? What is it? :open_mouth: Tried to look it up on the internet but didn’t find much useful information :open_mouth: I just found some libraries but not description how it works or what it does or what it is :smiley:

Hardware serial is 'normal' serial with hardware support on the chip. On Uno it is only on pins 0 and 1. Mega has 4 hardware serial pairs. Software serial is serial communication implemented in software library and can use any pair of GPIO pins. The baudrate is limited with SoftwareSerial.

  1. You're already using hardware serial. Serial3 is one of the hardware serial ports on a MEGA. The #include at the top can be removed with no adverse effects.

  2. Don't use a while() here. You will get stuck waiting for the end character to arrive. Just follow Robin's examples which add characters to the buffer when they arrive and then send the buffer out to be processed after the end is detected.

  3. Up until a year or two ago, the description for serialEvent() used to say that it worked like an interrupt. It doesn't. It never did. It was planned to eventually work that way but it turned out to be unnecessary. It really makes no difference if you use it or not.

Try this...

#define SSerialTxControl 3   // Pin for MAX485 - HIGH send, LOW receive

const int RXBuffLength = 64; //expect all incoming messages to be shorter than this

int byteReceived;
int rx_buff[RXBuffLength];
int rx_length = 0;
boolean stop_byte = false;


void setup()
{
  Serial.begin(57600);   //Serial communication for USB.
  Serial3.begin(57600);  //Serial communication for RS48 - 14 pin as Tx and 13 pin as Rx.

  pinMode(SSerialTxControl, OUTPUT);
  digitalWrite(SSerialTxControl, LOW); //Just receive data from RS485
}


void loop()
{
  if (Serial3.available()) //When you receive some data save it into the rx_buff[] variable
  {
    rx_buff[rx_length] = Serial3.read();
    if (rx_buff[rx_length] == 0xDD) stop_byte = true; //if the last byte received is equal to 0xDD then we know we have a finished message
    rx_length++;
    if (rx_length > RXBuffLength) rx_length = 0; //if we ran off the end of the buffer, reset back to the start (discard the data already collected)
  }

  if (stop_byte)
  {
    //we have received a valid message with a terminating character
    stop_byte = false;

    if (rx_buff[0] != 0x00)
    {
      assertSerial3Tx();
      for (int i = 0; i < rx_length; i++)
      {
        Serial.write(rx_buff[i]);    //Then just send it one by one through USB to the PC
        Serial3.write(rx_buff[i]);   //And also send the data through RS485 to "Amap99"
      }
    }
    rx_length = 0;
  }

  deAssertSerial3Tx();
}

unsigned long lastSerial3Transmit;
void assertSerial3Tx() {
  digitalWrite(SSerialTxControl, HIGH); // Get ready for sending data
  lastSerial3Transmit = millis();
}
void deAssertSerial3Tx() {
  //check if we can de-assert the Tx control
  static int highWaterMark = 0;
  const unsigned long TransmitHoldTime = 3; //milliseconds - the amount of time to hold TxControl after the last byte leaves the buffer. Use 1 for 115200, 5 for 19200, 16 for 9600
  if(Serial3.availableForWrite() < highWaterMark) 
  {
    lastSerial3Transmit = millis();
  }
  else 
  {
    highWaterMark = Serial3.availableForWrite();
    if(millis() - lastSerial3Transmit > TransmitHoldTime) 
    {
      //no longer transmitting
      digitalWrite(SSerialTxControl, LOW);
    }
  }  
}

The significant differences to your code are:
A) It doesn't get stuck in a while() loop forever waiting for a character which may never come.
B) It won't overrun the end of the buffer if it gets characters which are not the ones it's looking for.
C) You can't just delay(10) and hope that all the characters were sent. That won't even cover 1 character at 9600 baud. I put in a totally different system which looks at the outgoing buffer and turns off the Tx control a few milliseconds after the last character is sent.

Hi guys!

To “Juraj”:

  • oh thanks man !

To “MoranS”:

  1. Oh okey thanks man!
  2. Yeah makes sense … gonna get rid of it.
  3. Alright.
  4. I’am little bit confues by two parts of the code.
if(Serial3.availableForWrite() < highWaterMark) 
  {
    lastSerial3Transmit = millis();
  }
  • Won’t the condition be always false? Like - I mean even if theres “0” chars in the TX buffer then “0<0“ will always be false? Because “highWaterMark” will be always “0” same ass “Serial3.availableForWrite()”? And then the “<” condition won’t be true?
  • why are you saving data into the “highWaterMark” variable right in the end of the function, because when it is called next time, it will overwrite it selfe?
static int highWaterMark = 0;      //but here it will overwrite it self?
…
{
    highWaterMark = Serial3.availableForWrite(); //here you are saving some data in it
    if(millis() - lastSerial3Transmit > TransmitHoldTime) 
    {
…
  1. Can’t I just use this equation for calculating how much time I need t wait until all the chars are sended?

TimeToWait~~=((DataSize+StopBits+StartBits)*CharsGoingToSended)/BaudRate~~

if variable is static like your highWaterMark, it holds it's value globally, but is visible only for the function where it is declared

JacobSzlaur: it appears you may have not read my post #2 where I provide a link to Nick Gammon site where you will learn how to take a different approach to knowing when the UART transmitter buffer is empty rather than guessing.


Paul - VK7KPA

Paul, Nick's code was written before Serial.availableForWrite() existed as part of the standard Arduino framework. It is also highly non-portable, although since the OP did specify an ATMega, it would work.

Jacob, the availableForWrite stuff is kind of a trick. If you are writing this to run on many different Arduinos, you don't always know which register has the value you need. But availableForWrite tells you how many characters are "free" in the Arduino's outgoing serial buffer. It's part of the Serial software, not part of the chip. You can't ask the opposite question howManyCharactersAreWaitingToSend() because that information is not available outside the Serial object.

So we record the biggest number of availableForWrite as a high-water mark. It will usually be a number like 64. The next time we see 64, we know that the buffer must be empty. IF we saw 65, then we would know the the buffer is bigger than we originally suspected, so we move up the high-water mark.