Nick Gammon's rs485 code

Pulling my hair out here. I had a working prototype a few days ago, and now cannot for the life of me get it to work again.

I'm using Gammon's rolling master code from here. Right now, I'm just trying to get it to work with two pro micros, using serial1.

I don't have time to post a detailed schematic right now (I'm hoping the problem will be obvious to someone and it won't come to that), but for each Arduino I have:

TX to D1 on its respective 485 module
RX to R0
pin 4 to DE/RE

For the two max485 modules, I have A to A, B to B, common ground and VCC. B is connected to GND by 680 ohm resistor, and A to VCC by 680 ohm resistor. I've followed Nick's schematics in the link to hook it up. I have swapped out both the modules and arduinos many times and have had the same issue persist.

This is the weird behavior, and I'm hoping the "tell" for my problem: When I disconnect pin 4 (the xmit enable pin) from Arduino #1, Arduino #2 will correctly receive the struct from #1 (though at a sporadic rate, as #1 is randomly sending the message since it's getting no feedback). And the opposite is true when pin 4 from #2 is disconnected. Leaving both pins connected, I get nothing back and forth from either.

My code is basically identical to Gammon's, the main changes being the struct and that I'm using hardware serial instead of soft. The addresses for the two arduinos are 0 and 1.

I will work on posting the code in a readable manner now, but please let me know if you see a glaring issue right off the bat.

Much appreciated.

Here’s the code. I moved it around a little bit so my arduino sketches are a little easier to work with.

“rs485network.h” (Nick’s code, but “message” is now called “networkPacket”, and the loop and setup functions were made into separated functions called from my actual setup and loop. Also, his debugging code removed for the sake of brevity in this post)

#include <RS485_non_blocking.h>

byte rs485_xmit = 4;

const unsigned long BAUD_RATE = 9600;
const float TIME_PER_BYTE = 1.0 / (BAUD_RATE / 10.0);  // seconds per sending one byte
const unsigned long PACKET_LENGTH = ((sizeof (networkPacket) * 2) + 6); // 2 bytes per payload byte plus STX/ETC/CRC
const unsigned long PACKET_TIME = TIME_PER_BYTE * PACKET_LENGTH * 1000000;  // microseconds


const unsigned long TIME_BETWEEN_MESSAGES = 3000;
unsigned long noMessagesTimeout;

byte nextAddress;
unsigned long lastMessageTime;
unsigned long lastCommsTime;
unsigned long randomTime;

void processMessage(); // defined in sketch
void updateNetwork(); // defined in sketch

// what state we are in
enum {
   STATE_NO_DEVICES,
   STATE_RECENT_RESPONSE,
   STATE_TIMED_OUT,
} rs485state;

// callbacks for the non-blocking RS485 library
size_t fWrite (const byte what)
  {
  Serial1.write (what);
  }

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

int fRead ()
  {
  // lastCommsTime = micros ();
  return Serial1.read ();
  }

// RS485 library instance
RS485 myChannel (fRead, fAvailable, fWrite, 20);


byte myAddress;        // who we are

// Initial seed for JKISS32
static unsigned long x485 = 123456789,
                     y485 = 234567891,
                     z485 = 345678912,
                     w485 = 456789123,
                     c485 = 0;

// Simple Random Number Generator
unsigned long JKISS32 ()
  {
  long t485;
  y485 ^= y485 << 5;
  y485 ^= y485 >> 7;
  y485 ^= y485 << 22;
  t485 = z485 + w485 + c485;
  z485 = w485;
  c485 = t485 < 0;
  w485 = t485 & 2147483647;
  x485 += 1411392427;
  return x485 + y485 + w485;
  }  // end of JKISS32

void Seed_JKISS32 (const unsigned long newseed)
  {
  if (newseed != 0)
    {
    x485 = 123456789;
    y485 = newseed;
    z485 = 345678912;
    w485 = 456789123;
    c485 = 0;
    }
  }  // end of Seed_JKISS32

void rs485setup (){
  // serial for talking to other devices
  Serial1.begin(BAUD_RATE);
  // initialize the RS485 library
  myChannel.begin();

  // calculate how long to assume nothing is responding
  noMessagesTimeout = (PACKET_TIME + TIME_BETWEEN_MESSAGES) * numberOfDevices * 2;

  pinMode(rs485_xmit, OUTPUT);

  // seed the PRNG
  Seed_JKISS32(myAddress + 1000);

  rs485state = STATE_NO_DEVICES;
  nextAddress = 0;

  randomTime = JKISS32() % 500000;  // microseconds
}  // end of setup

// set the next expected address, wrap around at the maximum
void setNextAddress (const byte current) {
  nextAddress = current;
  if (nextAddress >= numberOfDevices){
    nextAddress = 0;
  }
}  // end of setNextAddress



// Here to send our own message
void sendMessage ()
  {
  memset (&networkPacket, 0, sizeof networkPacket);
  networkPacket.address = myAddress;

  updateNetwork(); // defined in sketch

  digitalWrite (rs485_xmit, HIGH);  // enable sending
  myChannel.sendMsg ((byte *) &networkPacket, sizeof networkPacket);
  digitalWrite (rs485_xmit, LOW);  // disable sending
  setNextAddress (myAddress + 1);

  lastCommsTime = micros ();   // we count our own send as activity
  randomTime = JKISS32 () % 500000;  // microseconds
  }  // end of sendMessage

void rs485loop ()
  {

  // incoming message?
  if (myChannel.update ())
    {
    memset (&networkPacket, 0, sizeof networkPacket);
    int len = myChannel.getLength ();
    if (len > sizeof networkPacket){
      len = sizeof networkPacket;
    }
    memcpy (&networkPacket, myChannel.getData (), len);
    lastMessageTime = micros ();
    setNextAddress (networkPacket.address + 1);
    processMessage ();
    rs485state = STATE_RECENT_RESPONSE;
    }  // end of message completely received

  // switch states if too long a gap between messages
  if  (micros () - lastMessageTime > noMessagesTimeout){
    rs485state = STATE_NO_DEVICES;
  }
  else if  (micros () - lastCommsTime > PACKET_TIME){
    rs485state = STATE_TIMED_OUT;
  }


  switch (rs485state)
    {
    // nothing heard for a long time? We'll take over then
    case STATE_NO_DEVICES:
      if (micros () - lastCommsTime >= (noMessagesTimeout + randomTime))
        {
        sendMessage ();
        }
      break;

    // we heard from another device recently
    // if it is our turn, respond
    case STATE_RECENT_RESPONSE:
      // we allow a small gap, and if it is our turn, we send our message
      if (micros () - lastCommsTime >= TIME_BETWEEN_MESSAGES && myAddress == nextAddress)
        sendMessage ();
      break;

    // a device did not respond in its slot time, move onto the next one
    case STATE_TIMED_OUT:
      setNextAddress (nextAddress + 1);
      lastCommsTime += PACKET_TIME;
      rs485state = STATE_RECENT_RESPONSE;  // pretend we got the missing response
      break;

    }  // end of switch on state

  }  // end of loop

Arduino #1:

#include "rs485network.h"

struct {
  byte address;
  byte num;
  char messageChar;
  byte buttonPressed;
} networkPacket;


const byte numberOfDevices = 2;

char updatedChar = 0;
byte updatedDigit = 0;

const byte button = 2;
byte buttonState = 0;

void setup()
{
  Serial.begin(9600); // debugging. Serial1 is begun inside rs485setup and is for the network
  pinMode(button, INPUT_PULLUP);
  myAddress = 0;
  rs485setup();
  Serial.println("Setup complete.");
}

void loop()
{
  rs485loop();
  checkSerial();
}

void checkSerial(){
  if(Serial.available()){
    char input = Serial.read();
    
    if(isDigit(input)){
      updatedDigit = input - '0';
      Serial.print("Updated digit: ");
      Serial.println(updatedDigit);
    }
    else{
      updatedChar = input;
      Serial.print("Updated char: ");
      Serial.println(updatedChar);
    }
  }
}

void processMessage() // called from rs485loop() in rs485network.h
{
  Serial.print("From ");
  Serial.print(networkPacket.address);
  Serial.print(": ");
  Serial.print(networkPacket.num);
  Serial.print(", ");
  Serial.print(networkPacket.messageChar);
  Serial.print(", ");
  Serial.println(networkPacket.buttonPressed);
}

void updateNetwork() // called from sendMessage() in rs485network.h
{
  networkPacket.messageChar = updatedChar;
  networkPacket.num = updatedDigit;
  networkPacket.buttonPressed = !digitalRead(button);
}

Arduino #2:

#include "rs485network.h"

struct {
  byte address;
  byte num;
  char messageChar;
  byte buttonPressed;
} networkPacket;


const byte numberOfDevices = 2;

char updatedChar = 0;
byte updatedDigit = 0;

const byte led = 9;

void setup()
{
  Serial.begin(9600); // debugging. Serial1 is begun inside rs485setup and is for the network
  pinMode(led, OUTPUT);
  myAddress = 0;
  rs485setup();
  Serial.println("Setup complete.");
}

void loop()
{
  rs485loop();
}

void processMessage() // called from rs485loop() in rs485network.h
{
  digitalWrite(led, networkPacket.buttonPressed);
  updatedChar = 'f';
  updatedDigit = networkPacket.num + 1;

}

void updateNetwork() // called from sendMessage() in rs485network.h
{
  networkPacket.messageChar = updatedChar;
  networkPacket.num = updatedDigit;
}

When the button on arduino 1 is pressed, the led in 2 goes on (only works when the enable pin to arduino #1 is disconnected). #2 just sends back the updated struct (if pin 4 from #2 is disconnected). When both pins are connected, nothing happens. When neither pin is connected, nothing happens.

Alright, so this is odd. After switching from Serial1 to software serial, it works... but only if I have my serial monitor open for debugging over USB.

Feedback?

Edit: But, BUT! If I have the USB cable disconnected entirely, it works. I'm gonna go set the nearest building on fire, be right back.

Wiring diagram is wrong. There should be no pull-up or pull down resistors on A or B! The only resistors there should be at the end of a long cable run between A and B to prevent reflections of the signal along the wire.

RS485 works by measuring the voltage difference between A and B to work out if it’s a 1 or 0.

RobSmithDev:
Wiring diagram is wrong. There should be no pull-up or pull down resistors on A or B! The only resistors there should be at the end of a long cable run between A and B to prevent reflections of the signal along the wire.

RS485 works by measuring the voltage difference between A and B to work out if it’s a 1 or 0.

Did you read the Texas Instruments application note that tells you why you need biasing resistors?

RobSmithDev:
Wiring diagram is wrong. There should be no pull-up or pull down resistors on A or B! The only resistors there should be at the end of a long cable run between A and B to prevent reflections of the signal along the wire.

RS485 works by measuring the voltage difference between A and B to work out if it’s a 1 or 0.

I have a 680 ohm resistors between A and VCC, and another between B and GND, as the link suggests. However, my problem was happening with or without those resistors.

I ended up switching to nanos and using Software Serial, and none of the issues have carried over. It's something weird to do with the pro micro boards. Or, at least, something weird to do with my cheap Chinese pro micros.

The problem, of course, is that I didn't read thoroughly enough. Gammon covers this issue in his post.

After the transmit pin goes high and the package is sent, the program needs to halt until the packet is completely sent. Software serial doesn't have that problem, perhaps because it's using an interrupt and already halts the program? Not sure on that.

So, I just added flush after sending the packet.

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