RS485 using Serial.available() only scans one character

Hello everyone,

further experimenting with the RS485 modules leads to more problems. I try to build a custom RS485 class in order to understand the processing and handling of a multi-master bus system.

Master

#include <Arduino.h>
#include<RS485.h>

const int EnableTX = 2;

void setup() {
  Serial.begin(9600);
  //Logger::log("--- RS485-Master ---");
  rs485.setup(EnableTX);
}

void loop() {  
  char* c = "x12";
  rs485.send(c);  
  delay(3500);
}

Slave

#include <Arduino.h>
#include <RS485.h>

const int EnableTX = 2;

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  //Logger::log("--- Slave ---");
  rs485.setup(EnableTX);
}

void loop() {

  bool incoming = false;
  while(Serial.available()>0) {

    byte r = Serial.read();

    if(r == '\n') {
      incoming = true;
      Serial.println("|");
      break;
    } else {
      Serial.print(r);
    }
    // delay(5);
  }

  delay(1);
}

And the helper class for the communication:

RS485.h

#ifndef rs485_h
#define rs485_h

#include <Arduino.h>
#include <Logger.h>

const unsigned int MAX_BUS_INPUT = 10;

class RS485 {
public:
    RS485();
    void test();

    void setup(uint8_t pin);
    void send(const char* c);
    void test(const char* c);
    unsigned int scan(char* data);


    uint8_t exPin;

    

};

extern RS485 rs485;

#endif

RS485.cpp

#include "RS485.h"

RS485::RS485() {

}

void RS485::test() {

}

void RS485::setup(uint8_t pin) {
    exPin = pin;
    pinMode(exPin, OUTPUT);

    digitalWrite(exPin,HIGH);
    digitalWrite(exPin,LOW);

    Serial.flush();
}

void RS485::send(const char* c) {
  digitalWrite(exPin,HIGH);
  Serial.print(c);
  Serial.print('\n');
 // Serial.println(c);
  // delay(1);
  digitalWrite(exPin,LOW);
}

void RS485::test(const char* c) {
  Serial.println(c);
}

unsigned int RS485::scan(char* data) {

  while(Serial.available()>0) {

    unsigned int pos = 0;
    // static char d[MAX_BUS_INPUT];

    delete[] data;
    
    byte read = Serial.read();
    Serial.print("|"); Serial.print(read); Serial.print("|");

    switch(read) {

      case '\n': // terminating null byte
        data[pos] = 0;
        break;
      
      default:
        if(pos<(MAX_BUS_INPUT-1)) {
          data[pos++] = read;
        }
        break;
    }

    // data = &d;

    Serial.println("");

    return pos;

  }

  return false;
}


RS485 rs485;

The master sends the desired char(s) "x12". The RS485 sniffer (read over a terminal program on my pc) also read 'x12'. The slave on the other hand only detects the FIRST character '120' (which equals 'x').

Why does the Serial.available() not wait for the terminator?
Can I only send one character, terminate and then the next one? (This would seem very inefficient when sending larger telegrams)

I tried sending the characters using Serial.println(c) and Serial.print(c); Serial.print('\n'). The terminator does not seem to get scanned (because its the next character which is not detected).

Serial.available() NEVER waits for ANYTHING. It tells you IF there is ANY data ready RIGHT NOW. It is YOUR job to wait, if that is what you need. Serial data arrives slowly. That is how it is.

Serial.read() function reads only one character from the serial buffer. So, this part of your code reads that character and prints it. You´will have to read and store multiple characters before printing the whole message.

I suggest you taking a look at Serial Input Basics - updated to get a clue on what to change in this part of the code.

Yes. That makes sense. How should the Slave know how much and when data is arriving?

I wrote a listener as soon as a Serial.available() is true

void loop() {

  // unsigned int incoming = rs485.scan(data);
  
  // if(incoming>0) {
  //   Logger::log(data,incoming);
  // }

  // delay(5);

  if(Serial.available()) {

    while(true) {

      byte r = Serial.read();
      if(r==120 || r==49 || r==50) {
        Serial.println(r);
      }
      
    }
  }

  delay(1000);

}

The while loop is constantly scanning the serial line. There should not be a timing issue, since I am constantly listening. Here also only '120' is detected.

Removing the if and constantly reading results in the output

120 // for the send character(s)
-1
-1
-1
...
120
-1
-1
...

but never '49' or '50'.

Shouldnt at least sometimes the other characters appear?

Read the link above, and learn how to handle serial data correctly.

Using the outlined examples I copied the code from Example_2. The problem remains the same

void loop() {

  unsigned int pos = 0;
  bool newData = false;

  while(Serial.available()>0 && newData == false) {

    Serial.print ("T "); // test if loop is triggered

    byte r = Serial.read();

    Serial.println(r); // test if loop is triggered


    if(r != '\n') {
      data[pos] = r;
      pos++;
    } else {
      Serial.println("N"); // test path is triggered
      data[pos] = '\0';
      newData = true;
      break;
    }

  }

  Serial.println("O"); // outside while

  if(newData) {
    Serial.println(data);
  }
}

The output constantly reads

T 120
T 120
...

Apparently Serial.available() becomes false after '120' is transmitted and no newline or other characters are detected over all the Serial.read() command that follows. Until the next telegram is transmitted ("x12").

Removing the debugging prints (T,O,N) does not affect the output, so it should not be a timing issue.

Can it be the interference from all the listening terminals?

On the other hand: Every device on the bus should react to the same message and not the one who gets the bytes firs?

Serial data arrives SLOWLY. You will fine Serial.available() returns 0 for a LONG time, then will return 1. You read the ONE available character, then Serial.available() again returns 0 for a LONG time, until another character arrives. But, you've now exited your while loop, BECAUSE Serial.available returns 0, so newData is again set to false, and pos is again rsset to 0, so you're starting over from scratch each time a character is received.

See the problem? Read, and study, more of the tutorial, to understand how to properly either wait for ALL characters to arrive, or properly handle the characters one at a time, with the unavoidable delays between each character being received.

I understand that the serial communication is 'slowish'. But the telegrams are sent in 3500ms intervals.

I had bad experience with EventTriggers (which are also not recommended in the article). The other examples focus on handling the received data.

Letting the loop run until Serial becomes available again results again only in one character '120'. The else-path is never triggered.

unsigned int pos = 0;

void loop() {

  bool newData = false;

  while(Serial.available() > 0 && newData == false) {

    // Serial.print ("T "); // test if loop is triggered

    byte r = Serial.read();

    if(r != '\n') {
      data[pos] = r;
      pos++;
    } else {
      Serial.println("N"); // test path is triggered
      data[pos] = '\0';
      newData = true;
    }

  }

  if(newData) {
    Serial.println(data);
    pos = 0;
  }
}

Either the other characters are flushed somehow or never sent (doubtful since the sniffer reads 'x12').

Maybe I am searching on the wrong side of the code. Since I am using two RS485 modules for the transmissions: Do I need to LOW\HIGh the external trigger after each character or can I send them all at once?

I may have found the error. Since all the Serial.read() function did not yield a consistent result I looked deeper in the RS485 library and example.

In the code I am using the transmitting telegram does not wait until transmission is completed. With the Serial.flush() (link) function, before the enable pin (active write), the transmission can securely and completely go through.

Thanks to @RayLivingston and @Brazilino for the helping hands.
The linked article is an excellent source.

Also this articles from Nick Gammon are always helpful

1 Like