Implementing collision detection with RS485

Status
Not open for further replies.
My project has ~20 teensys connected via half-duplex RS485. One is a slave, others are masters. The masters are sending bursts of data that is pretty time-sensitive so implementing 1 master - 19 slaves isn't going to work. Only a fraction of masters is transmitting at the same time.

For hardware, I'm using my own board based on Arduino MKR 485 Shield. I'm also using their ArduinoRS485 library which has been working fine.

The problem is that I get collisions. I think the easiest way to fix it would be to read the bits as they are being transmitted and if a collision is detected, abort and try again after a random interval.

The only way to get access the serial port at bit level that I can think of is to use a software (bit-bang) serial library. Is that the best way?

Is my collision strategy likely to work or does it need to be more complex than that?

I'm aware of this RS485 library which implements collision avoidance but it's pretty intimidating in it's complexity.
 
One way is to make a short random delay before a master begins the transmission so they don't start at the same time. The normal procedure is to make a simple protocol with a checksum so you can detect errors. You can find many open source examples on Google if you need it.
 
Last edited:
> One way is to make a short random delay before a master begins the transmission so they don't start at the same time.
The masters don't have a fixed transmitting rate, so that's not going to help.

> The normal procedure is to make a simple protocol with a checksum so you can detect errors.
I'd like to avoid going the ACK route as it would make the protocol a lot more complex and introduce additional traffic from the slave to the masters for each message received.
 
Last edited:
Is CAN bus fast enough for you, its a solution without collisions. You can only reduce collisions not avoid them in a multi master setup on rs485.
 
Which Teensy are you using?

CAN bus really sounds like the correct solution.

If you're willing to use a not-so-correct solution, why reject the idea of random delays?

The only sure way to avoid RS485 collisions is to build a bus access method into your protocol. For multiple masters, the usually approach is "token passing".
 
> Which Teensy are you using?
Using LC or 3.2.

> CAN bus really sounds like the correct solution.
Thanks I'll look into it. Naturally I'm a little averse to switching protocols as I'm pretty deep into this project.

> why reject the idea of random delays?
Only because it doesn't make sense in my case. The masters are not synchronized, the times at which they transmit are completely independent from each other. I understand how random delays would work if the masters transmitted periodically, but they don't.

I should clarify that I'm not that concerned about the collisions per se, it's dropped messages and lag that I worry about. I guess there's no way to figure out if the 'detect-and-transmit-after-random-delay' hack is going to achieve the low lag and low dropped packets without actually implementing and testing it.
 
What I would do, when confronted with too many talkers and one listener is that the _listener_ tells the talker who can talk (i.e. listener is moderator).
that is, every talker sends first an ID and the listener authorizes the talker of which he understood the ID by sending the ID back. then the named talker will send its info. All other talker will repeat procedure when line is again silent.
(There may be a name for such a protocol, but I don't know)
 
What I would do, when confronted with too many talkers and one listener is that the _listener_ tells the talker who can talk (i.e. listener is moderator).
that is, every talker sends first an ID and the listener authorizes the talker of which he understood the ID by sending the ID back. then the named talker will send its info. All other talker will repeat procedure when line is again silent.

I think what you're describing is modbus. Not going to work in my case because talkers send blurbs of time-sensitive information and don't have anything to say most of the time.
 
Enabled both the receiver and transmitter at the same time so that you can receive what you are sending. Then compare the received packet to what was transmitted. If they are the same, there was no collision.
 
Ok, I think I have a working solution.

What I've done is connected the TX pin to another GPIO. That GPIO has an interrupt attached to it which gets triggered every time a transmission begins (falling edge). In that interrupt, I start a timer that fires every 100us (@9600 baud). In the timer interrupt, I check if the TX and RX pins have the same voltage. If not, I set a collision flag.

collision.jpg

Code:
#include <ArduinoRS485.h>

#define MAX3157_RO 0
#define MAX3157_DI 1
#define MAX3157_nRE 2
#define MAX3157_DE 3

#define TRG 7 // Connected to MAX3157_DI (TX)

void trg_isr(void);

void setup() {
  RS485.setPins(MAX3157_DI, MAX3157_DE, MAX3157_nRE);
  RS485.begin(9600);
  RS485.receive();

  pinMode(TRG, INPUT);
  attachInterrupt(TRG, trg_isr, FALLING);
}

#define TIMER_PERIOD_US 52
IntervalTimer RS485_timer;
void rs485_timer_isr(void);

volatile bool timer_running  = false;
volatile bool collision_flag = false;
volatile int8_t timer_counter = 0;

void trg_isr(void) {
  if (timer_running == false) {
    RS485_timer.priority(0);
    RS485_timer.begin(rs485_timer_isr, TIMER_PERIOD_US);
    timer_counter = 0;
    timer_running = true;
  }
}

void rs485_timer_isr(void) { 
  // 40 roughly corresponds to the number of timer interrupt gets triggered we get when transmitting 2 bytes
  timer_counter++;
  if (timer_counter > 40) {
    timer_running = false;
    return;
  }
  
  // Timer fires every 52us, but we need to check every other time, so drop every other interrupt
  if (timer_counter % 2 == 1) {
    int32_t ro = digitalRead(MAX3157_RO);
    int32_t di = digitalRead(MAX3157_DI);
    if (ro != di) {
      collision_flag = true;
    }
  }
}
 
Status
Not open for further replies.
Back
Top