RS485 address ghost when sending a struct

Could use some advice on my networking project (although this could be about ignorance when it comes to language, not networking)!

I'm attempting to get three (knock-off) Mega2560's communicating over RS485 (using Nick Gammon's libraries), one as a master, the other two as slaves. I've been able to work through bugs with Uno's and the Mega's using his normal (blocking) library, but am having trouble getting things working with the non-blocking version. I'm suspicious this is ignorance in my understanding of C/C++ and how I'm manipulating memory.

Communication is over Serial1. For inputs and outputs on my master, each slave has its own button and green and red LED (so 2 buttons, 4 LED's total). The slaves each have one LED connected. The goal is to be able to press the button at the master, and have the corresponding slave receive the signal that it's LED should be on, and then it send a reply that it acted on that command, which would turn on the corresponding green LED on the master. A second button press on the master tells the slave to turn off; the slave turns it's LED off, and the reply turns the red LED on (and the green off) on the master. The slaves are designated "left" and "right" in my code, and shouldn't interfere with the other slave when they communicate.

I'm using a struct with four data types to send my info (not all are necessary, but I want to know I can send all types of data, particularly the integer). Everything is getting transmitted successfully going from master to slaves: the light on the slaves is turning on and off when it should. But when I send a signal to one slave, and then a signal to the other, the last reply coming back to the master, still contains the address from the slave in the second last transmission. If I alternate the button presses from there, the master is always one behind in who it's receiving a reply from (so the wrong green and red LED's will be switching on the master). However after sending two consecutive signals to the same slave, the address will be correct on the second transmission.

The glitch is very consistent. I've only been looking at the Serial feedback from one board at a time, but I don't think the wrong slave is replying, I think it's something in my code on the master.

What am I missing? I believe memset is completely wiping the memory of the struct I'm using to send my data, but is something being left over? When I check what's contained in it after wiping it, I notice that no value is given for my char (I just get 3 zero's); has that got something to do with it?? Maybe that comment explains my level of knowledge on all this!

Master code:

#include <RS485_non_blocking.h>

/* The current issue with this is (along with it's receiving partner) is that when I send a message to one
base but then switch who I'm sending to, the first transmission BACK from the receiver still has the old
receivers address in it. However after a couple (and this happens consistently) cycles, then the receiver's
address will show up correctly. I don't know why this is...*/

//easy to get confused on this: base LEFT = 1
//                              base RIGHT = 2

// callback routines
size_t fWrite(const byte what) {
  return Serial1.write(what);
}
int fAvailable() {
  return Serial1.available();
}
int fRead() {
  return Serial1.read();
}

const byte ENABLE_PIN = 2;
const byte LEFT_GREEN = 3;
const byte LEFT_RED = 4;
const byte RIGHT_GREEN = 5;
const byte RIGHT_RED = 6;
const byte RIGHT_BUTTON = 8;
const byte LEFT_BUTTON = 9;

//variables for buttons pressing
unsigned long debounceDelay = 50;  // the debounce time
bool leftState = false;
bool lastLeftState = false;
unsigned long lastLeftDebounceTime = 0;  // the last time the output pin was toggled
bool rightState = false;
bool lastRightState = false;
unsigned long lastRightDebounceTime = 0;  // the last time the output pin was toggled

//data for sending
byte leftAddress = 1;
bool leftActive = false;
char leftStatus = 'X';
byte rightAddress = 2;
bool rightActive = false;
char rightStatus = 'X';
int gameTime = 300;

//the structure of our message
struct message {
  byte address;
  bool active;
  char status;
  int theTime;
};

//default data for our message
struct message commPacket;

RS485 myChannel(fRead, fAvailable, fWrite, 20);

//-------------------------------------------------------------------------------------------------------------------------------------
//                 SETUP              SETUP              SETUP              SETUP              SETUP
//-------------------------------------------------------------------------------------------------------------------------------------

void setup() {
  Serial.begin(9600);
  Serial1.begin(28800);
  myChannel.begin();
  pinMode(ENABLE_PIN, OUTPUT);  // driver output enable
  pinMode(LEFT_GREEN, OUTPUT);
  pinMode(LEFT_RED, OUTPUT);
  pinMode(RIGHT_GREEN, OUTPUT);
  pinMode(RIGHT_RED, OUTPUT);
  pinMode(RIGHT_BUTTON, INPUT_PULLUP);
  pinMode(LEFT_BUTTON, INPUT_PULLUP);
}

//-------------------------------------------------------------------------------------------------------------------------------------
//              LOOP                LOOP                LOOP                LOOP                LOOP
//-------------------------------------------------------------------------------------------------------------------------------------

void loop() {

  userInterface();
  updateLights();
}

//-------------------------------------------------------------------------------------------------------------------------------------
//                 UPDATE LIGHTS                   UPDATE LIGHTS                  UPDATE LIGHTS
//-------------------------------------------------------------------------------------------------------------------------------------

void updateLights() {

  if (leftStatus == 'X') {
    digitalWrite(LEFT_GREEN, LOW);
    digitalWrite(LEFT_RED, LOW);
  }
  if (leftStatus == 'A') {
    digitalWrite(LEFT_GREEN, HIGH);
    digitalWrite(LEFT_RED, LOW);
  }
  if (leftStatus == 'D') {
    digitalWrite(LEFT_GREEN, LOW);
    digitalWrite(LEFT_RED, HIGH);
  }
  if (rightStatus == 'X') {
    digitalWrite(RIGHT_GREEN, LOW);
    digitalWrite(RIGHT_RED, LOW);
  }
  if (rightStatus == 'A') {
    digitalWrite(RIGHT_GREEN, HIGH);
    digitalWrite(RIGHT_RED, LOW);
  }
  if (rightStatus == 'D') {
    digitalWrite(RIGHT_GREEN, LOW);
    digitalWrite(RIGHT_RED, HIGH);
  }
}

//-------------------------------------------------------------------------------------------------------------------------------------
//           USER INTERFACE             USER INTERFACE             USER INTERFACE             USER INTERFACE
//-------------------------------------------------------------------------------------------------------------------------------------

void userInterface() {

  //int leftReading = !(PINH & B00100000);               //read digital pin 9 (H6)
  int leftReading = digitalRead(LEFT_BUTTON);
  if (leftReading != lastLeftState) {  //if the button changed...
    lastLeftDebounceTime = millis();   //...remember the time
  }
  if ((millis() - lastLeftDebounceTime) > debounceDelay) {  //if the time since the last change was large enough
    if (leftReading != leftState) {                         //if the state of the pin is different than it was before
      leftState = leftReading;                              //remember the new state
      if (leftState == LOW) {                               //once the button goes off?? (pulldown resistor)
        leftActive = !leftActive;                           //flip the active status of the base

        Serial.print("AFTER press, LEFT base, local variables are: ");
        Serial.print(leftAddress);
        Serial.print(leftActive);
        Serial.print(leftStatus);
        Serial.println(gameTime);

        messageLeft();
      }
    }
  }
  lastLeftState = leftReading;

  //int rightReading = !(PINH & B00010000);                   //read digital pin 8 (H5)
  int rightReading = digitalRead(RIGHT_BUTTON);
  if (rightReading != lastRightState) {  //check if the button changed due to noise or pressing
    lastRightDebounceTime = millis();    //if so remember the time
  }
  if ((millis() - lastRightDebounceTime) > debounceDelay) {
    if (rightReading != rightState) {
      rightState = rightReading;
      if (rightState == LOW) {
        rightActive = !rightActive;

        Serial.print("AFTER press, local variables for RIGHT: ");
        Serial.print(rightAddress);
        Serial.print(rightActive);
        Serial.print(rightStatus);
        Serial.println(gameTime);

        messageRight();
      }
    }
  }
  lastRightState = rightReading;
}

//-------------------------------------------------------------------------------------------------------------------------------------
//           MESSAGE BASES              MESSAGE BASES              MESSAGE BASES              MESSAGE BASES
//-------------------------------------------------------------------------------------------------------------------------------------

void messageLeft() {

  memset(&commPacket, 0, sizeof commPacket);  //zeros data
  //fill message for base with data
  commPacket.address = leftAddress;
  commPacket.active = leftActive;
  commPacket.status = leftStatus;
  commPacket.theTime = gameTime;

  delay(1);

  digitalWrite(ENABLE_PIN, HIGH);  // enable sending
  myChannel.sendMsg((byte *)&commPacket, sizeof commPacket);
  Serial1.flush();
  digitalWrite(ENABLE_PIN, LOW);  // disable sending

  Serial.print("Sent to LEFT: ");
  Serial.print(commPacket.address);
  Serial.print(commPacket.active);
  Serial.print(commPacket.status);
  Serial.println(commPacket.theTime);

  getReply();
}

void messageRight() {

  memset(&commPacket, 0, sizeof commPacket);
  //fill message for base with data
  commPacket.address = rightAddress;
  commPacket.active = rightActive;
  commPacket.status = rightStatus;
  commPacket.theTime = gameTime;

  delay(1);

  digitalWrite(ENABLE_PIN, HIGH);  // enable sending
  myChannel.sendMsg((byte *)&commPacket, sizeof commPacket);
  Serial1.flush();
  digitalWrite(ENABLE_PIN, LOW);  // disable sending

  Serial.print("Sent to RIGHT: ");
  Serial.print(commPacket.address);
  Serial.print(commPacket.active);
  Serial.print(commPacket.status);
  Serial.println(commPacket.theTime);

  getReply();
}

//-------------------------------------------------------------------------------------------------------------------------------------
//                GET REPLY              GET REPLY              GET REPLY              GET REPLY
//-------------------------------------------------------------------------------------------------------------------------------------

void getReply() {

  if (myChannel.update()) {

    memset(&commPacket, 0, sizeof commPacket);  //zero's out data...?

    Serial.print("after memset: ");
    Serial.print(commPacket.address);
    Serial.print(commPacket.active);
    Serial.print(commPacket.status);
    Serial.println(commPacket.theTime);

    int len = myChannel.getLength();  //this saves the length of the message as an int
    if (len > sizeof commPacket) {
      len = sizeof commPacket;
    }
    memcpy(&commPacket, myChannel.getData(), len);  //this actually dumps the data into message

    Serial.print("Received: ");
    Serial.print(commPacket.address);
    Serial.print(commPacket.active);
    Serial.print(commPacket.status);
    Serial.println(commPacket.theTime);

    if (commPacket.address == 1) {     //if message was from base 2
      leftStatus = commPacket.status;  // save the data from what we received
    }
    if (commPacket.address == 2) {      //if message was from base 2
      rightStatus = commPacket.status;  // save the data from what we received
    }
  }
}

Slave code:

#include <RS485_non_blocking.h>

// callback routines
size_t fWrite(const byte what) {
  return Serial1.write(what);
}
int fAvailable() {
  return Serial1.available();
}
int fRead() {
  return Serial1.read();
}

//local data
byte myAddress = 1;
bool myActive;
char myStatus;
int gameTime = 200;

const byte ENABLE_PIN = 2;
const byte BLUE_STATUS = 8;

//the structure of our message
struct message {
  byte address;
  bool active;  //inverted order from controller: what control sends, we receive...
  char status;  //...and what we send, control receives
  int theTime;
};

//messages
struct message commPacket;

RS485 myChannel(fRead, fAvailable, fWrite, 20);


//-------------------------------------------------------------------------------------------------------------------------------------
//                 SETUP              SETUP              SETUP              SETUP              SETUP
//-------------------------------------------------------------------------------------------------------------------------------------

void setup() {
  Serial.begin(9600);
  Serial1.begin(28800);
  myChannel.begin();
  pinMode(ENABLE_PIN, OUTPUT);  // driver output enable
  pinMode(BLUE_STATUS, OUTPUT);
}

//-------------------------------------------------------------------------------------------------------------------------------------
//              LOOP                LOOP                LOOP                LOOP                LOOP
//-------------------------------------------------------------------------------------------------------------------------------------

void loop() {

  if (myChannel.update()) {

    memset(&commPacket, 0, sizeof commPacket);  //I believe this zero's out commPacket...
    int len = myChannel.getLength();            //this saves the length of the message as an int
    if (len > sizeof commPacket) {
      len = sizeof commPacket;
    }
    memcpy(&commPacket, myChannel.getData(), len);  //dumps RS485 data into message

    Serial.print("This is what we received: ");
    Serial.print(commPacket.address);
    Serial.print(commPacket.active);
    Serial.print(commPacket.status);
    Serial.println(commPacket.theTime);

    if (commPacket.address == myAddress) {  //if message was for us
      myActive = commPacket.active;         // save the data from what we received
      if (myActive == true) {
        digitalWrite(BLUE_STATUS, HIGH);
        myStatus = 'A';
      }
      if (myActive == false) {
        digitalWrite(BLUE_STATUS, LOW);
        myStatus = 'D';
      }

      Serial.print("This is our local data: ");
      Serial.print(myAddress);
      Serial.print(myActive);
      Serial.print(myStatus);
      Serial.println(gameTime);
      ;

      sendReply();
    }
  }
}

void sendReply() {

  //zero out commPacket data
  memset(&commPacket, 0, sizeof commPacket);
  //fill message for controller with updated data
  commPacket.address = myAddress;
  commPacket.active = myActive;
  commPacket.status = myStatus;
  commPacket.theTime = gameTime;

  Serial.print("This is what we're sending: ");
  Serial.print(commPacket.address);
  Serial.print(commPacket.active);
  Serial.print(commPacket.status);
  Serial.println(commPacket.theTime);
  Serial.println();

  delay(1);

  digitalWrite(ENABLE_PIN, HIGH);  // enable sending
  myChannel.sendMsg((byte *)&commPacket, sizeof commPacket);
  Serial1.flush();
  digitalWrite(ENABLE_PIN, LOW);  // disable sending
}

You are missing the resistors at the end of the RS-485 data line. They are there to absorb the signal so there is no reflection, which is what you are seeing. Look up the RS-485 documentation.

They're there, a 120 ohm at each end of the line (and nothing in the middle).

A "feature" of RS485 transceivers is that they can receive data that is being sent. This could be used to verify the outward packet has been sent. Is that a possibility here?

It seems you are using a single ENABLE line to the RS485 transceiver. Some modules are wired so that transmit and receive are controlled by a single enable ("WREN/~RDEN"), bit others have separate enables for Tx and Rx.

That's correct I've got those wired together, and that's working fine for my protocol--I set enable to high on the master for it's transmission, while the slaves are both set low, so they're listening. Then after the slaves have received a response, whoever the message is for will wait a moment to let the master get ready, and then its enable pin will get set high and the transmission is sent.

In terms of verifying what the master is sending out and the slave is replying with, the slave that isn't a part of a message exchange can see both sides of what's being sent, and it agrees with what I'm seeing the other slave send, but NOT what the master hears (when I connect to it with my USB). This is what makes me think I don't understand something about memset or memcpy--either my messages are longer or shorter than I realize? I'm digging into that currently.

Okay, pretty sure I solved it. I was treating this library like the original blocking version of the library that I was used to (which just waits until a message is received). I'd become a creature of habit...

Since I was only giving the program one try for each time the button was pressed and a message was sent, the buffer would only partially fill up. I assume the info was being saved there until I hit the button again, and then it would have time to finish receiving and get the ETX byte. Just needed to keep calling the function in loop.

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