RS-485 on Arduino UNO

Hi There,
I have just been getting familiar with Arduino's software serial port, and sending text messages through the port between two Arduino UNO's. I also printed these messages through the serial monitor so I could see what I had written, and received and acknowledgement from the receiver once they received a sent message.

From there I have shifted to RS-485, and initially I was hoping to do the same thing. I understand it gains complexity through the implementation of protocols, however I just wanted to be able to send text messaging between UNO's as I had done over UART just at the moment, before I read more into using protocols.

I have adapted the initial code I had for the UART communications to include definitions for RO, DI, DE and RE, with digital write high and low in areas I wish to send and receive messages.
I know typically that the master sends out an addressed message to the slave devices, however as mentioned I am just wanting to communicate freely between master and slave at this point, so I have not set this part of it up.

When I send a message from the master, I see what I have printed to the serial monitor, and I also see the slave receive the sent message as it also prints it to the serial monitor. Yes I have two serial monitors open to see the communications between Arduino UNO's.

The issue I have, is it seems as if the slave does not write the Ack: (Received Message) back to the master. I cannot figure out why this is the case, as I have checked the contents of the buffer with the message I wish to write back and it is there so I am unsure what is going on.

Would it be possible to look at the code and provide me with some advice on the next steps I should try?

Below I have copied the Master and Slave sketches.

MASTER RS-485

#include <SoftwareSerial.h>

// Defining the digital IO pins to align with RS-485 definitions.
#define RO    10
#define DI    11
#define DE_RE 12
 
SoftwareSerial swSerial_MasterNode(RO,DI);

// Char array variables to store the serial data/message.
char buffer1_size[32];
char buffer2_size[32];
char ack[6] = "Ack: ";
char CR[3] = {'\n'};
char buffer3_size[39];

// Integer variables.
int bytesRead1;
int bytesRead2;
int charCheck;
int ackFlag;
int txFlag;

void setup() 
{
  // Open serial port and wait for connection.
  Serial.begin(9600);
  while(!Serial)
  {
    // Wait for the serial port to connect - needed for native USB port.
  }
  // Set data rate for the software serial port.
  swSerial_MasterNode.begin(9600);

  // Define DE_RE as an output.
  pinMode(DE_RE, OUTPUT);

  // Master sets this pin to high to begin transmission.
  digitalWrite(DE_RE, HIGH);
}

void loop()
{
  // If there is no serial data/message available, perform the following.
  if(swSerial_MasterNode.available() == 0)
  {
      // If there is data/message written in the serial monitor, perform the following.
      if(Serial.available() > 0)
      {
        // Read the serial port and store data in the integer variable until either a newline character (ENTER KEY) is read, or the byte limit is reached.
        bytesRead1 = Serial.readBytesUntil('\n',buffer1_size,32);

        // Add a null character to the array to terminate the string. 
        buffer1_size[bytesRead1] = '\0';

        // Print the data/message to the serial monitor.
        Serial.print("Transmit message: ");
        Serial.println(buffer1_size);

        // Set write conditions and write data/message to the port.
        digitalWrite(DE_RE, HIGH);
        swSerial_MasterNode.write(buffer1_size);
        digitalWrite(DE_RE, LOW);
        
        txFlag = 1;
        delay(50);
        swSerial_MasterNode.flush();
      }
  }
  
  // If there is a serial data/message available, perform the following.
  if(swSerial_MasterNode.available() > 0)
  {        
    bytesRead2 = swSerial_MasterNode.readBytesUntil('\n',buffer2_size,32);

    buffer2_size[bytesRead2] = '\0';

    // String variable to hold the data/message in the buffer.
    String buffer2_String(buffer2_size);

    // Index through the buffer string and store the integer value in charCheck when the colon is found (':'). This will determine if the data/message is an acknowledgement or not.
    charCheck = buffer2_String.indexOf(':');

    // This nested logic checks if the character is not in the data/message. Received the data/message from the sender, print what is received to the serial monitor.
    if(charCheck != 3)
    {
     // Print the data/message to the serial monitor and set the acknowledgement flag.
     Serial.print("Receive message: ");
     Serial.println(buffer2_size);
     ackFlag = 1;

      // This nested logic checks if the acknowledgement and transmit flag states are as expected.
      if((ackFlag == 1) && (txFlag == 0));
      {
        // Using the sprintf function to compile the strings to the char array buffer3_size.
        sprintf(buffer3_size, "%s%s%s", ack, buffer2_size, CR); 
        buffer3_size[0] = '\0';

        // Copy into buffer and append further strings.
        strcpy(buffer3_size, ack);
        strcat(buffer3_size, buffer2_size);
        strcat(buffer3_size, CR);

        // Set the control pin high for RS-485 data transmission and write Ack: <Message> back to sender.
        digitalWrite(DE_RE, HIGH);
        swSerial_MasterNode.write(buffer3_size);
        digitalWrite(DE_RE, LOW);

        txFlag = 1;
        delay(50);
        swSerial_MasterNode.flush();
      }
    }

    // Other logic condition which checks if the character (':') is in the data/message. Received acknowledgement from slave print to serial monitor. 
    else if(charCheck == 3)
    {
      Serial.println(buffer2_size);
      ackFlag = 0;
    }
  }
}

SLAVE RS-485

#include <SoftwareSerial.h>
 
// Defining the digital IO pins to align with RS-485 definitions.
#define RO    10
#define DI    11
#define DE_RE 12
 
SoftwareSerial swSerial_SlaveNode(RO,DI);

// Char arrays required to store the serial data/message.
char buffer1_size[32];
char buffer2_size[32];
char ack[6] = "Ack: ";
char CR[3] = {'\n'};
char buffer3_size[39];

// Integer variables.
int bytesRead1;
int bytesRead2;
int charCheck;
int ackFlag;
int txFlag;

void setup() 
{
  // Open serial port and wait for connection.
  Serial.begin(9600);
  while(!Serial)
  {
    // Wait for the serial port to connect.
  }
  // Set data rate for the software serial port.
  swSerial_SlaveNode.begin(9600);

  // Define DE_RE as an output.
  pinMode(DE_RE, OUTPUT);

  // Slave sets this pin to low to receive transmission.
  digitalWrite(DE_RE, LOW);  
}

void loop()
{
  // If there is no serial data/message available, perform the following.
  if(swSerial_SlaveNode.available() == 0)
  {
      // If there is data/message written in the serial monitor, perform the following.
      if(Serial.available() > 0)
      {
        // Read the serial port and store data in the integer variable until either a newline character (ENTER KEY) is read, or the byte limit is reached.
        bytesRead1 = Serial.readBytesUntil('\n',buffer1_size,32);

        // Add a null character to the array to terminate the string. 
        buffer1_size[bytesRead1] = '\0';

        // Print the data/message to the serial monitor.
        Serial.print("Transmit message: ");
        Serial.println(buffer1_size);

        // Set write conditions and write data/message to the port.
        digitalWrite(DE_RE, HIGH);
        swSerial_SlaveNode.write(buffer1_size);
        digitalWrite(DE_RE, LOW);

        txFlag = 1;
        delay(50);
        swSerial_SlaveNode.flush();
      }
  }
  
  // If there is a serial data/message available, perform the following.
  if(swSerial_SlaveNode.available() > 0)
  {    
    bytesRead2 = swSerial_SlaveNode.readBytesUntil('\n',buffer2_size,32);

    buffer2_size[bytesRead2] = '\0';

    // String variable to hold the data/message in the buffer.
    String buffer2_String(buffer2_size);

    // Index through the buffer string and store the integer value in charCheck when the colon is found (':'). This will determine if the data/message is an acknowledgement or not.
    charCheck = buffer2_String.indexOf(':');

    // This nested logic checks if the character is not in the data/message. Received the data/message from the sender, print what is received to the serial monitor.
    if(charCheck != 3)
    {
     // Print the data/message to the serial monitor and set the acknowledgement flag.
     Serial.print("Receive message: ");
     Serial.println(buffer2_size);
     ackFlag = 1;

      // This nested logic checks if the acknowledgement and transmit flag states are as expected.
      if((ackFlag == 1) && (txFlag == 0));
      {
        // Using the sprintf function to compile the strings to the char array buffer3_size.
        sprintf(buffer3_size, "%s%s%s", ack, buffer2_size, CR); 
        buffer3_size[0] = '\0';

        // Copy into buffer and append further strings.
        strcpy(buffer3_size, ack);
        strcat(buffer3_size, buffer2_size);
        strcat(buffer3_size, CR);

        // Set the control pin high for RS-485 data transmission and write Ack: <Message> back to sender.
        digitalWrite(DE_RE, HIGH);
        swSerial_SlaveNode.write(buffer3_size);
        digitalWrite(DE_RE, LOW);

        txFlag = 1;
        delay(50);
        swSerial_SlaveNode.flush();
      }
    }

    // Other logic condition which checks if the character (':') is in the data/message. Received acknowledgement from master print to serial monitor. 
    else if(charCheck == 3)
    {
      Serial.println(buffer2_size);
      ackFlag = 0;
    }
  }
}

No more "protocols" that RS232.

But you have to handle enabling the transmitter.

Hello awneil,

Thanks for your reply.

Are you able to be a little more descriptive, or specific with your feedback?

I thought that I was handling the enabling of the transmitter, as I do this initially in the setup, and then again when I wish to send a message.

The setup is also performed in the slave. Although I want to be able to transmit freely from the slave to the master, the master would always send the first transmission.

Are you perhaps eluding to the reason why I am not receiving the Ack back to the master, being related to it still transmitting?

If the write operation is nonblocking, you are turning off the transmitter before all the data have been sent.

Use .flush() to make sure all data have been transmitted before shutting down the transmitter.

1 Like

Hi jremington,

Thanks for you suggestions. I made quite a few changes and managed to get the code to work, however I do still have one annoyance.

When I type a word into the serial monitor, lets say I type the word testing, that is received at the slave and then it sends back, Ack: testing which is what is expected. However, if I then type test the received message in the slave is testng, and obviously this is sent back t the master as Ack: testng

I have since tried printing the size of the buffers and the buffer2_string has a count of six even if I hit the return key at the serial monitor with nothing typed. I cannot explain where they come from.

It seems that the buffer overwrites what is already in there are the very first transmission. I have tried to use memset, however I cannot find the correct place to put it.

I have reposted my code below, for you or any other knowledgeable person to perhaps identify where this issue may lie, if you have time.

Regards.

MASTER RS-485

/* 
 * 
 * This is the RS-485 Master Node.
 * 
 * Define new software serial Port:
 * RO - Receiver Output = TX - digital pin 10
 * DI - Driver Input    = RX - digital pin 11
 * 
 * DE - Driver Enable   = digital pin 12
 * RE - Receiver Enable = digital pin 12
 */

#include <SoftwareSerial.h>

// Defining the digital IO pins to align with RS-485 definitions.
#define RO    10
#define DI    11
#define DE_RE 12
 
SoftwareSerial swSerial_MasterNode(RO,DI);

// Char array variables to store the serial data/message.
char buffer1_size[32];
char buffer2_size[32];
char buffer3_size[32];
char ack[6] = "Ack: ";

// Integer variables.
int bytesRead1;
int bytesRead2;
int charCheck;
int charLen;
int ackFlag;
int txFlag;

void setup() 
{
  // Open serial port and wait for connection.
  Serial.begin(9600);
  while(!Serial)
  {
    // Wait for the serial port to connect - needed for native USB port.
  }
  // Set data rate for the software serial port.
  swSerial_MasterNode.begin(9600);

  // Define DE_RE as an output.
  pinMode(DE_RE, OUTPUT);

  // Master sets this pin to high to begin transmission.
  digitalWrite(DE_RE, HIGH);
}

void loop()
{
  // If there is no serial data/message available, perform the following.
  if(swSerial_MasterNode.available() == 0)
  {
      // If there is data/message written in the serial monitor, perform the following.
//      if(Serial.available() > 0)
      if(Serial.available())
      {
        // Read the serial port and store data in the integer variable until either a newline character (ENTER KEY) is read, or the byte limit is reached.
        bytesRead1 = Serial.readBytesUntil('\n',buffer1_size,32);

        // Add a null character to the array to terminate the string. 
        buffer1_size[bytesRead1] = '\0';

        // Print the data/message to the serial monitor.
        Serial.print("Transmit message: ");
        Serial.println(buffer1_size);

        // Testing lengths.
        Serial.println("The chars in buffer1_size");
        Serial.println(strlen(buffer1_size));

        // Set write conditions and write data/message to the port.
        digitalWrite(DE_RE, HIGH);
        swSerial_MasterNode.write(buffer1_size);
        swSerial_MasterNode.flush();
        digitalWrite(DE_RE, LOW);
        txFlag = 1;
      }
  }
  
  // If there is a serial data/message available, perform the following.
  if(swSerial_MasterNode.available() > 0)
  {                
    bytesRead2 = swSerial_MasterNode.readBytesUntil('\n',buffer2_size,32);

    // String variable to hold the data/message in the buffer.
    String buffer2_String(buffer2_size);

    // Index through the buffer string and store the integer value in charCheck when the colon is found (':'). This will determine if the data/message is an acknowledgement or not.
    charCheck = buffer2_String.indexOf(':');
    charLen = buffer2_String.length();

    // Testing lengths.
    Serial.println("The length of buffer2_String");
    Serial.println(charLen);

    // This nested logic checks if the character is not in the data/message. Received the data/message from the sender, print what is received to the serial monitor.
    if(charCheck != 3 && charLen > 0)
    {
      // Print the data/message to the serial monitor and set the acknowledgement flag.
      Serial.print("Receive message: ");
      Serial.println(buffer2_size);
      ackFlag = 1;

      // This nested logic checks if the acknowledgement and transmit flag states are as expected.
      if(ackFlag == 1 && txFlag == 0)
      {        
        // Using the sprintf function to compile the strings to the char array buffer3_size.
        sprintf(buffer3_size, "%s%s", ack, buffer2_size); 

        // Copy into buffer and append string.
        strcpy(buffer3_size, ack);
        strcat(buffer3_size, buffer2_size);

        // Set the control pin high for RS-485 data transmission and write Ack: <Message> back to sender.
        digitalWrite(DE_RE, HIGH);
        swSerial_MasterNode.write(buffer3_size);
        swSerial_MasterNode.flush();
        digitalWrite(DE_RE, LOW);

        // Testing lengths.
        Serial.println("The size of charLen1");
        Serial.println(sizeof(charLen));
      }
    }
        
    // Other logic condition which checks if the character (':') is in the data/message. Received acknowledgement from slave print to serial monitor. 
    else if(charCheck == 3)
    {
      // Testing lengths.
      Serial.println("The size of charLen2");
      Serial.println(sizeof(charLen));
      
      Serial.println(buffer2_size);
      
//      memset(buffer2_size, 0, sizeof(charLen)-2);

      // Testing lengths.
      Serial.println("The size of charLen3");
      Serial.println(sizeof(charLen));
      ackFlag = 0;
      txFlag = 0;
    }
  }
}

SLAVE RS-485

/* 
 * This is the RS-485 Slave Node.
 * 
 * Define new software serial Port:
 * RO - Receiver Output = TX - digital pin 10
 * DI - Driver Input    = RX - digital pin 11
 * 
 * DE - Driver Enable   = digital pin 12
 * RE - Receiver Enable = digital pin 12
 */

#include <SoftwareSerial.h>
 
// Defining the digital IO pins to align with RS-485 definitions.
#define RO    10
#define DI    11
#define DE_RE 12
 
SoftwareSerial swSerial_SlaveNode(RO,DI);

// Char arrays required to store the serial data/message.
char buffer1_size[32];
char buffer2_size[32];
char buffer3_size[32];
char ack[6] = "Ack: ";

// Integer variables.
int bytesRead1;
int bytesRead2;
int charCheck;
int charLen;
int charLen1;
int ackFlag;
int txFlag;

void setup() 
{
  // Open serial port and wait for connection.
  Serial.begin(9600);
  while(!Serial)
  {
    // Wait for the serial port to connect.
  }
  // Set data rate for the software serial port.
  swSerial_SlaveNode.begin(9600);

  // Define DE_RE as an output.
  pinMode(DE_RE, OUTPUT);

  // Slave sets this pin to low to receive transmission.
  digitalWrite(DE_RE, LOW);  
}

void loop()
{
  // If there is no serial data/message available, perform the following.
  if(swSerial_SlaveNode.available() == 0)
  {
      // If there is data/message written in the serial monitor, perform the following.
      if(Serial.available() > 0)
//      if(Serial.available())
      {
        // Read the serial port and store data in the integer variable until either a newline character (ENTER KEY) is read, or the byte limit is reached.
        bytesRead1 = Serial.readBytesUntil('\n',buffer1_size,32);

        // Add a null character to the array to terminate the string. 
        buffer1_size[bytesRead1] = '\0';

        // Print the data/message to the serial monitor.
        Serial.print("Transmit message: ");
        Serial.println(buffer1_size);

        // Testing lengths.
        Serial.println("The chars in buffer1_size");
        Serial.println(strlen(buffer1_size));

        // Set write conditions and write data/message to the port.
        digitalWrite(DE_RE, HIGH);
        swSerial_SlaveNode.write(buffer1_size);
        swSerial_SlaveNode.flush();
        digitalWrite(DE_RE, LOW);
        txFlag = 1;
      }
  }
  
  // If there is a serial data/message available, perform the following.
  if(swSerial_SlaveNode.available() > 0)
  {     
    bytesRead2 = swSerial_SlaveNode.readBytesUntil('\n',buffer2_size,32);

    // String variable to hold the data/message in the buffer.
    String buffer2_String(buffer2_size);

    // Index through the buffer string and store the integer value in charCheck when the colon is found (':'). This will determine if the data/message is an acknowledgement or not.
    charCheck = buffer2_String.indexOf(':');
    charLen = buffer2_String.length();

    // Testing lengths.
    Serial.println("The length of buffer2_String");
    Serial.println(charLen);

    // This nested logic checks if the character is not in the data/message. Received the data/message from the sender, print what is received to the serial monitor.
    if(charCheck != 3 && charLen > 0)
    {
      // Print the data/message to the serial monitor and set the acknowledgement flag.
      Serial.print("Receive message: ");
      Serial.println(buffer2_size);
      ackFlag = 1;

      // This nested logic checks if the acknowledgement and transmit flag states are as expected.
      if(ackFlag == 1 && txFlag == 0)
      {        
        // Using the sprintf function to compile the strings to the char array buffer3_size.
        sprintf(buffer3_size, "%s%s", ack, buffer2_size); 

        // Copy into buffer and append string.
        strcpy(buffer3_size, ack);
        strcat(buffer3_size, buffer2_size);

        // Testing lengths.
        Serial.println("The chars in buffer3_size");
        Serial.println(strlen(buffer3_size));

        // Set the control pin high for RS-485 data transmission and write Ack: <Message> back to sender.
        digitalWrite(DE_RE, HIGH);
        swSerial_SlaveNode.write(buffer3_size);      
        swSerial_SlaveNode.flush();
        digitalWrite(DE_RE, LOW);

        // Testing lengths.
        Serial.println("The size of charLen1");
        Serial.println(sizeof(charLen));
      }
    }

    // Other logic condition which checks if the character (':') is in the data/message. Received acknowledgement from master print to serial monitor. 
    else if(charCheck == 3)
    {
      // Testing lengths.
      Serial.println("The size of charLen2");
      Serial.println(sizeof(charLen));
      
      Serial.println(buffer2_size);
      
//      memset(buffer2_size, 0, sizeof(charLen)-2);

      // Testing lengths.
      Serial.println("The size of charLen3");
      Serial.println(sizeof(charLen)-2);
      ackFlag = 0;
      txFlag = 0;
    }
  }
}

Sorry, I don't use or recommend Strings, as they cause memory problems and program crashes on AVR Arduinos like the Uno. I also avoid the poorly performing Arduino serial functions like readBytesUntil, and recommend that you switch to character handling functions as described in the Arduino "Serial Input Basics" tutorial.

The logic of your code is convoluted and I have trouble following it. The program logic seems to ignore that you have established a half duplex connection. You cannot simultaneously transmit and receive on such a connection, and must have a mechanism (agreed upon by both ends) to know when the line is available.

Hi,

Yes I do acknowledge what I was doing is unconventional, and I think the whole idea about the agreed mechanism of communication would prevent the issue I described.

What I will do now is shift towards creating a structure which contains the contents of the data message I intend to send and allow responses to be made based on address settings.

Once I have something I will post it.

You can send text messages between units, using the arduino built in examples, tx/rx.

RS-485/RS-232/RS-422 only descripe the hardware to make this happen.

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