10

While planning the lightning infrastructure (wall-switches and lights) of my new home (it's still under-construction) I choosed to go through the "automated route" way and due to my background (I'm an "old" system/network administrator with programming skills and plenty of Open-Source "passion" and "advocacy") I'm seriously trying to implement it with three Arduinos and one RPi2.

Due to number/location of wall-push-buttons and lights, I'd like to use three MEGA interfacing both wall-push-buttons and lights of surrouding rooms. Furthermore, a RPi2 (or similar) will be used as a "controller" to properly programs MEGAs, when needed, and interfacing some other equipments (touch-screen; wifi tablet/smartphone; remote-controls; etc.) via the IP-network.

The schema I'm trying to implement is similar to this one:

Draft Schema where you see:

  • 9 lights (L1 to L9) each one controlled by its own dedicated relay (R1 to R9);
  • 7 wall push-buttons (WPB1 to WPB7), to be used to turn on/off one or more lights;
  • 3 MEGAs interfacing wall-push-buttons and lights;
  • 1 RPi2, acting as "supervisor" and "Internet/Ethernet gateway".

My main architectural problem relates to the interconnection between RPI2 and MEGAs. As each device will be located several tens of meters aways from each other, I ended with two only option (please, correct me if I'm wrong):

  1. Ethernet
  2. RS485

(BTW: I'm explicitely excluding "wireless connections", as I've already placed all the electrical pipes in a "compatible" way. In other words: I'd like to avoid wireless technologies within the "controlling network")

As for Ethernet, I'll choose it as a 2nd option, due both to a slightly higher cost and complexity (need a switch to be powered on; additional cabling issues; etc.).

I've extensively researched the "RS485 bus" and found that it's relatively easy --from a physical point of view-- to implement it with two wires in a multidrop configuration.

Unfortunately, from an "application point of view", things are more complex as it supports only "half-duplex" communications and, even worse, there is no provision to avoid "collisions" when communicating (that's why, probably, the MODBUS protocol --tipically employed on RS485 bus-- provides a "single-master; multiple-slaves" scenario).

Before the questions, I need to add another constraint: I want the infrastructure to be "as much fault-tolerant as possible", expecially towards problems with the BUS and/or with the "controller" (the RPI2). In other words:

  • each MEGA should allow to turn on its own lights when one of its own push-button requires this. For example:
    • if WPB1 controls L2 and L3, it need to be working even if the BUS is broken or the RPI2 is powered off;
    • if WPB3 controls L4 and L9, then should the bus/RPI2 have problems, only L4 will be powered on when WPB3 is pressed;

So, after all of the above, here are my questions:

  1. is a two-wire RS485 multidrop bus, a suitable choice for my scenario?

  2. if not, which (possibly cheap and simple) other solutions may I investigate?

  3. if yes, which is the logic that I need to implement on MEGAs, as:

    • a) they need to act as "slave", with respect to RPI2 when turning on lights commanded by push-button connected to other MEGAs or when the RP2 decide to turn-on some lights (for example, due to remote/Internet access);
    • b) they need to act as "master", when one of their push-buttons is pressed and... this need to be communicated to RPI2 so to send commands to other MEGAs so to turn on "hosted" lights;
  4. as for 3b), insted of having MEGAs acting as master when a WPB is pressed, may I implement a "frequent-polling" logic on the RPI2? If yes, which is a reasonable value for such a polling (1 poll per second? 5 polls per second? too much? too low?)

I understand that this is a too-wide question, but I really did lots of researches and despite lots, and lots, and lots of on-line documentation, I've been unable to find an answer to those questions.


Update 1

  • in terms of total numbers, I'll have 31 lights to be controlled by 46 wall push-buttons, more or less equally distributed in 4 distinct panel-boards;
  • As for the choise of MEGA (vs. UNO), I've choosed MEGA due to the bigger number of I/O PINs. I simply choosed the board with the maximun number of PINs;
  • As for the RPI2, I choosed to employ a proper "computer" (vs. an additional microcontroller), 'cause I want a sort of "decoupling" between the "physical controlling network" and the "User Management Interface". In other words, I want the management of physical buttons/relais to be handled by PLC-like device (Arduino: very quick power on; sort of real-time performance; very reliable; minor/no external "computing" factors introducing delays/problems); while, at the same time, all the user-interface be handled by a real-computer where I can easily write powerful HTTP web-services and/or really complex "logics", using technologies and languages that doesn't fit well with Arduino (later on --much later on--, I plan to add several 150$ full-HD 10.1" android tablets allaround the house, that will act as wireless "clients" towards what need to be a "powerful" (in terms of computing capacities) web-server; Also I plan to add various sensors [temperature; humidity; contacts; power-meters; etc.] whose data need to be stored for trending/archiving reasons). Hence, I tought about RPi2, than can easily be replaced with something more powerful, should it be needed.
4
  • You'll probably want something connected to the RPi that can do 9-bit communications on RS-485. Linux doesn't support 9-bit UARTs so you'd have to play ugly tricks with the parity bit without it. Commented Sep 5, 2015 at 0:11
  • What if I'll connect, via RS485, only ARDUINOs? I mean, the problem you're mentioning is strictly introduced by connecting RPI to RS485, right? I'm asking, 'cause based also on @nick-gammon answer below, I'll probably terminate the RS485 bus on ad additional Arduino, and having the interconnection between RPI and Arduino using Ethernet (and not RS485). In such a case, RPI will not need to speak RS485 and problem shoud disappear, right? Commented Sep 5, 2015 at 8:04
  • 2
    I just wanted to note that the chance of packet collisions is rather low. You only send packets when a switch is pressed, or when a light needs to turn on. Unless you live inside a disco, this only happens a few times a day per light/switch.
    – Gerben
    Commented Sep 5, 2015 at 9:13
  • 1
    @Gerben: with my two young sons (2,5 and 5,5), I can assure you that such a probability is NOT so low as you might expect under "normal" conditions :-). Jokes aside, consider that should everything runs smoothly, I plan to add LOTS of additional sensors/devices to be queried via "bus traffic" (temperature/humidity sensors [one per room]; open/close contacts for doors/windows [at least two for each windows]; electricity-power-meters and water-usage-meters probably generating "pulses" to be collected in some way. So, again, when "collision" will happen, will my MEGAs "survive"? Or they'll burn? Commented Sep 5, 2015 at 21:26

4 Answers 4

10

I did a lengthy post about RS485.


First, your use of Megas seems an overkill, unless you already have them to hand. A Uno, or one of the smaller form-factor boards would seem to be perfectly adequate to monitor a few switches and turn on a couple of lights.

Even the Rpi seems unnecessary. Another Uno could easily monitor your RS485 lines and connect via Ethernet (an Ethernet shield) to the rest of your house or whatever you are doing.


... there is no provision to avoid "collisions" when communicating ...

Well, you build that in. You give each Mega an address (eg. stored in EEPROM) and then you "address" the one you want, and then wait for a response. For example, in the code from my page above:

Master

#include "RS485_protocol.h"
#include <SoftwareSerial.h>

const byte ENABLE_PIN = 4;
const byte LED_PIN = 13;

SoftwareSerial rs485 (2, 3);  // receive pin, transmit pin

// callback routines
  
void fWrite (const byte what)
  {
  rs485.write (what);  
  }
  
int fAvailable ()
  {
  return rs485.available ();  
  }

int fRead ()
  {
  return rs485.read ();  
  }

void setup()
{
  rs485.begin (28800);
  pinMode (ENABLE_PIN, OUTPUT);  // driver output enable
  pinMode (LED_PIN, OUTPUT);  // built-in LED
}  // end of setup
  
byte old_level = 0;

void loop()
{

  // read potentiometer
  byte level = analogRead (0) / 4;
  
  // no change? forget it
  if (level == old_level)
    return;
      
  // assemble message
  byte msg [] = { 
     1,    // device 1
     2,    // turn light on
     level // to what level
  };

  // send to slave  
  digitalWrite (ENABLE_PIN, HIGH);  // enable sending
  sendMsg (fWrite, msg, sizeof msg);
  digitalWrite (ENABLE_PIN, LOW);  // disable sending

  // receive response  
  byte buf [10];
  byte received = recvMsg (fAvailable, fRead, buf, sizeof buf);
  
  digitalWrite (LED_PIN, received == 0);  // turn on LED if error    
  
  // only send once per successful change
  if (received)
    old_level = level;

}  // end of loop

You configure the RS485 transceiver to either send or receive. Normally it is in receive mode, and you switch to send mode to send a "packet" of data. (See "enable sending" above).


Now the addressed device turns its transceiver into "send" mode and replies. The code in the library I wrote has a time-out, so if that particular slave is dead, the receive times out. You might remember that at the master end and try to communicate with it less often. Or you may not care if the time-out is short.

Slave

#include <SoftwareSerial.h>
#include "RS485_protocol.h"

SoftwareSerial rs485 (2, 3);  // receive pin, transmit pin
const byte ENABLE_PIN = 4;

void fWrite (const byte what)
  {
  rs485.write (what);  
  }
  
int fAvailable ()
  {
  return rs485.available ();  
  }

int fRead ()
  {
  return rs485.read ();  
  }
  
void setup()
{
  rs485.begin (28800);
  pinMode (ENABLE_PIN, OUTPUT);  // driver output enable
}

void loop()
{
  byte buf [10];
  
  byte received = recvMsg (fAvailable, fRead, buf, sizeof (buf));
  
  if (received)
    {
    if (buf [0] != 1)
      return;  // not my device
      
    if (buf [1] != 2)
      return;  // unknown command
    
    byte msg [] = {
       0,  // device 0 (master)
       3,  // turn light on command received
    };
    
    delay (1);  // give the master a moment to prepare to receive
    digitalWrite (ENABLE_PIN, HIGH);  // enable sending
    sendMsg (fWrite, msg, sizeof msg);
    digitalWrite (ENABLE_PIN, LOW);  // disable sending
    
    analogWrite (11, buf [2]);  // set light level
   }  // end if something received
   
}  // end of loop

Note: The example code from my linked page does not read the address from EEPROM, however that is trivial to implement.


I can't see any particular reason why you could not interrogate the slaves quite frequently. What else would the master be doing? You could also set up the master as a HTTP server so you could talk to it from your laptop, or some other part of the house.


My wiring:

RS485 wiring


To demonstrate the idea, I set up two Unos, connected via about 8 m of bell-wire (not even twisted pair, and certainly not shielded).

RS485 demo

One Uno was running the standard ASCII table sketch (at 9600 baud), its Tx pin went into the LTC1480, and then the A/B pins went to the bell wire.

The other Uno was connected as a USB interface (Reset connected to ground) and it was just echoing whatever arrived on the Tx pin to the USB.

As far as I could see, it worked perfectly.


I will not need any poll at all, as with your approach I can implement a "single-master/multiple-slave" context... but with a "moving" master. Is this right?

My answer above assumed that the slaves were more likely to fail than the master (can't quite think why this would happen, but perhaps based on the fact that there are more slaves than masters, and the master doesn't control things like lights).

I find my Arduinos extremely reliable when doing simple things (like unlocking a door when a RFID card is presented).

You could conceivably build a fall-back position into the slaves. After all, if they get polled every second, and then no poll arrives, they could conceivably attempt to take over as master, perhaps in ascending order of device number, to avoid conflicts. Part of this polling by the "new master" could be to check the "original master" to see if it was ready to resume its duties.

The library I described on my linked page has error checking built into it, the idea being that by checking a CRC on the packet you ensure that you don't come in half-way through a packet and misinterpret the data in it.

You could also build in some randomness of poll times to resolve a deadlock if two slaves attempted to become master at once. If a slave failed it could wait for a random (and increasing) time before trying again to give another slave a chance to do it.


I just wanted to note that the chance of packet collisions is rather low. You only send packets when a switch is pressed, or when a light needs to turn on.

Gerben is right, but I would be worried that a notification about a switch had gone unnoticed. One possible work-around here is rather than the slaves replying with state changes to a query, they reply with the current state. So it might go:

Master: Slave 3, what is your status?
Slave 3: Lights 1 and 4 on, lights 2 and 3 off.

I will not need any poll at all, as with your approach I can implement a "single-master/multiple-slave" context... but with a "moving" master. Is this right?

I've been thinking about this a bit, and I think now you could make a system that is basically master-free. It could work like this:

  • Each device has its own address, which it gets from EEPROM (or DIP switches). eg. 1, 2, 3, 4, 5 ...

  • You choose a range of addresses you are going to use (eg. maximum of 10)

  • When the device powers up it first listens for other devices "talking" on the bus. Hopefully it will hear at least one other (if not, see below).

  • We decide on a fixed "message packet", say of 50 bytes including address, CRC, etc. At 9600 baud that would take 52 ms to send.

  • Each device gets a "slot" of time, and waits its turn to talk to the bus.

  • When its timeslot arrives, it goes into output mode, and broadcasts its packet which includes its own address. Therefore all the other devices can now read its status (and act upon it if necessary). Eg. device 1 might report that switch 3 is closed, which means device 2 must turn a light on.

  • Ideally, you know your timeslot has arrived because your device address is one greater than the packet you just listened to. Eg. You are device 3. You just heard device 2 announce its status. Now it's your turn. Of course, you wrap around at the maximum number, so after device 10 you go back to device 1.

  • If a device is missing and does not respond, you give it (say) half a timeslot to respond, and then assume it is dead, and every device on the bus now assumes the next timeslot has started. (eg. You heard device 2, device 3 should respond, after 25 ms of inactivity, device 4 can now respond). This rule gives a device 25 ms to respond, which should be plenty even if it is servicing an interrupt or something like that.

  • If multiple devices in sequence are missing you count a 25 ms gap for each missing device, until it is your turn.

  • Once you get at least one response the timing is resynchronized, so any drift in clocks would be cancelled.

The only difficulty here is that upon initial power-up (which might happen simultaneously if the power to the building is lost and then restored) there is no device currently broadcasting its status, and thus nothing to synchronize to.

In that case:

  • If after listening long enough for all devices to be heard (eg. 250 ms) and hearing nothing, the device tentatively assumes it is the first one and makes a broadcast. However possibly two devices will do that at the same time, and thus never hear each other.

  • If a device has not heard from another device, it staggers the time between broadcasts randomly (perhaps seeding the random-number generator from its device number, to avoid all devices "randomly" staggering the broadcasts by the same amount).

  • This random staggering by additional amounts of time won't matter, because there is no-one listening anyway.

  • Sooner or later one device will get exclusive use of the bus and the other ones can then synchronize with it in the usual way.

This random gap between attempts to communicate is similar to what Ethernet used to do when multiple devices shared one coaxial cable.


Demo of master-free system

This was an interesting challenge, so I put together a demo of doing this without having any specific master, as described above.

First you need to set up the current device address, and the number of devices, in EEPROM, so run this sketch, changing myAddress for each Arduino:

#include <EEPROM.h>

const byte myAddress = 3;
const byte numberOfDevices = 4;

void setup ()
  {
  if (EEPROM.read (0) != myAddress)
    EEPROM.write (0, myAddress);
  if (EEPROM.read (1) != numberOfDevices)
    EEPROM.write (1, numberOfDevices);
    
  }  // end of setup

void loop () { }

Now upload this to each device:

/*
 Multi-drop RS485 device control demo.
 
 Devised and written by Nick Gammon.
 Date: 7 September 2015
 Version: 1.0
 
 Licence: Released for public use.
 
 For RS485_non_blocking library see: http://www.gammon.com.au/forum/?id=11428
 For JKISS32 see: http://forum.arduino.cc/index.php?topic=263849.0
*/

#include <RS485_non_blocking.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>

// the data we broadcast to each other device
struct
  {
  byte address;
  byte switches [10];
  int  status;
  }  message;

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 (message) * 2) + 6); // 2 bytes per payload byte plus STX/ETC/CRC
const unsigned long PACKET_TIME =  TIME_PER_BYTE * PACKET_LENGTH * 1000000;  // microseconds

// software serial pins
const byte RX_PIN = 2;
const byte TX_PIN = 3;
// transmit enable
const byte XMIT_ENABLE_PIN = 4;

// debugging pins
const byte OK_PIN = 6;
const byte TIMEOUT_PIN = 7;
const byte SEND_PIN = 8;
const byte SEARCHING_PIN = 9;
const byte ERROR_PIN = 10;

// action pins (demo)
const byte LED_PIN = 13;
const byte SWITCH_PIN = A0;


// times in microseconds
const unsigned long TIME_BETWEEN_MESSAGES = 3000;  
unsigned long noMessagesTimeout;                  

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

SoftwareSerial rs485 (RX_PIN, TX_PIN);  // receive pin, transmit pin
  
// what state we are in
enum {
   STATE_NO_DEVICES,
   STATE_RECENT_RESPONSE,
   STATE_TIMED_OUT,
} state;

// callbacks for the non-blocking RS485 library
size_t fWrite (const byte what)
  {
  rs485.write (what);  
  }
  
int fAvailable ()
  {
  return rs485.available ();  
  }

int fRead ()
  {
  lastCommsTime = micros ();
  return rs485.read ();  
  }
  
// RS485 library instance
RS485 myChannel (fRead, fAvailable, fWrite, 20);

// from EEPROM
byte myAddress;        // who we are
byte numberOfDevices;  // maximum devices on the bus

// Initial seed for JKISS32
static unsigned long x = 123456789,
                     y = 234567891,
                     z = 345678912,
                     w = 456789123,
                     c = 0;

// Simple Random Number Generator
unsigned long JKISS32 ()
  {
  long t;
  y ^= y << 5; 
  y ^= y >> 7; 
  y ^= y << 22;
  t = z + w + c; 
  z = w; 
  c = t < 0; 
  w = t & 2147483647;
  x += 1411392427;
  return x + y + w;
  }  // end of JKISS32
  
void Seed_JKISS32 (const unsigned long newseed)
  {
  if (newseed != 0)
    {
    x = 123456789;
    y = newseed;
    z = 345678912;
    w = 456789123;
    c = 0;
    }
  }  // end of Seed_JKISS32
  
void setup ()
  {
  // debugging prints
  Serial.begin (115200);
  // software serial for talking to other devices
  rs485.begin (BAUD_RATE);
  // initialize the RS485 library
  myChannel.begin ();
  
  // debugging prints
  Serial.println ();
  Serial.println (F("Commencing"));
  myAddress = EEPROM.read (0);
  Serial.print (F("My address is "));
  Serial.println (int (myAddress));
  numberOfDevices = EEPROM.read (1);
  Serial.print (F("Max address is "));
  Serial.println (int (numberOfDevices));  

  if (myAddress >= numberOfDevices)
    Serial.print (F("** WARNING ** - device number is out of range, will not be detected."));
  
  Serial.print (F("Packet length = "));
  Serial.print (PACKET_LENGTH);  
  Serial.println (F(" bytes."));

  Serial.print (F("Packet time = "));
  Serial.print (PACKET_TIME);  
  Serial.println (F(" microseconds."));
  
  // calculate how long to assume nothing is responding
  noMessagesTimeout = (PACKET_TIME + TIME_BETWEEN_MESSAGES) * numberOfDevices * 2;

  Serial.print (F("Timeout for no messages = "));
  Serial.print (noMessagesTimeout);  
  Serial.println (F(" microseconds."));
  
  // set up various pins
  pinMode (XMIT_ENABLE_PIN, OUTPUT);
  
  // demo action pins
  pinMode (SWITCH_PIN, INPUT_PULLUP);
  pinMode (LED_PIN, OUTPUT);
  
  // debugging pins
  pinMode (OK_PIN, OUTPUT);
  pinMode (TIMEOUT_PIN, OUTPUT);
  pinMode (SEND_PIN, OUTPUT);
  pinMode (SEARCHING_PIN, OUTPUT);
  pinMode (ERROR_PIN, OUTPUT);
  
  // seed the PRNG
  Seed_JKISS32 (myAddress + 1000);
  
  state = 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 process an incoming message
void processMessage ()
  {
  
  // we cannot receive a message from ourself
  // someone must have given two devices the same address
  if (message.address == myAddress)
    {
    digitalWrite (ERROR_PIN, HIGH);
    while (true)
      { }  // give up  
    }  // can't receive our address
    
  digitalWrite (OK_PIN, HIGH);
  
  // handle the incoming message, depending on who it is from and the data in it
  
  // make our LED match the switch of the previous device in sequence
  if (message.address == (myAddress - 1))
    digitalWrite (LED_PIN, message.switches [0]);
    
  digitalWrite (OK_PIN, LOW);
  } // end of processMessage
  
// Here to send our own message  
void sendMessage ()
  {
  digitalWrite (SEND_PIN, HIGH);
  memset (&message, 0, sizeof message);
  message.address = myAddress;
  
  // fill in other stuff here (eg. switch positions, analog reads, etc.)
  
  message.switches [0] = digitalRead (SWITCH_PIN);
  
  // now send it
  digitalWrite (XMIT_ENABLE_PIN, HIGH);  // enable sending  
  myChannel.sendMsg ((byte *) &message, sizeof message);
  digitalWrite (XMIT_ENABLE_PIN, LOW);  // disable sending
  setNextAddress (myAddress + 1);
  digitalWrite (SEND_PIN, LOW);
  
  lastCommsTime = micros ();   // we count our own send as activity
  randomTime = JKISS32 () % 500000;  // microseconds
  }  // end of sendMessage
  
void loop ()
  {
  // incoming message?
  if (myChannel.update ())
    {
    memset (&message, 0, sizeof message);
    int len = myChannel.getLength ();
    if (len > sizeof message)
      len = sizeof message;
    memcpy (&message, myChannel.getData (), len);
    lastMessageTime = micros ();
    setNextAddress (message.address + 1);
    processMessage ();
    state = STATE_RECENT_RESPONSE;
    }  // end of message completely received
   
  // switch states if too long a gap between messages
  if  (micros () - lastMessageTime > noMessagesTimeout)
    state = STATE_NO_DEVICES;
  else if  (micros () - lastCommsTime > PACKET_TIME)
    state = STATE_TIMED_OUT;
    
  switch (state)
    {
    // nothing heard for a long time? We'll take over then
    case STATE_NO_DEVICES:
      if (micros () - lastCommsTime >= (noMessagesTimeout + randomTime))
        {
        Serial.println (F("No devices."));
        digitalWrite (SEARCHING_PIN, HIGH);
        sendMessage ();
        digitalWrite (SEARCHING_PIN, LOW);
        }
      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:
      digitalWrite (TIMEOUT_PIN, HIGH);
      setNextAddress (nextAddress + 1);
      lastCommsTime += PACKET_TIME;
      digitalWrite (TIMEOUT_PIN, LOW);
      state = STATE_RECENT_RESPONSE;  // pretend we got the missing response
      break;
      
    }  // end of switch on state
    
  }  // end of loop
  

As it currently stands, if you close a switch on A0 (short to ground) it will turn off a LED (pin 13) on the next highest device in sequence. This proves that the devices are talking to each other. Of course in practice you would have something more sophisticated in the packet which is being broadcast.

I found in testing that the LED appeared to come on and off instantly.

With all devices disconnected the first one connected will "hunt" for other devices. If you have debugging LEDs connected as I did you can see the "search" LED come on at random intervals as it broadcasts its packet with randomly-varying gaps. Once you have a second one connected they settle down and just exchange information. I tested with three connected at once.

It would probably be more reliable with HardwareSerial - I used SoftwareSerial to help with debugging. A few small changes would accomplish that.


Amended schematic

RS485 moving master schematic


Screen-shots of the code in action

These images show the code working. First, with only one device connected:

RS485 - one device

You can see from the pulses there that the device is broadcasting its data at randomly-varying intervals, to avoid continuing to clash with another device that powered up at the same moment.


RS485 - two devices

Now we see blocks of data from two devices, with roughly the same size gaps in the middle. I configured it for four devices, but only two are present, so we see two blocks of data, and two gaps.


RS485 - three devices

Now with three devices online, we see three blocks of data, and one gap, as the missing device is bypassed.


If you are checking the figures, these were taken with the baud rate doubled, as a test, to 19200 baud.


Cable run test

For a proper hardware test, I connected the devices up to my in-house UTP cabling. I have cat-5 cable running from various rooms to a central plug-board. Going from one end of the house to another (a reasonable length run) it still works fine. There is 5 m cable between the Arduino and the wall socket, for a start. Plus another 5 m cable at the other end. Then there are about 2 x 15 m runs from the rooms to the switch room, and inside that there is a short bridging cable to connect them together.

This was with the boards still programmed to run at 19200 baud.

6
  • 1st) Thanks VERY MUCH for your answer and RS-485 tutorial. I'll surely use it ; 2nd) as for MEGA and RPIs involvement, I've updated the OP. Please, refer to it for details; 3rd) as for "I can't see any particular reason why you could not interrogate the slaves quite frequently", is a 10-polls per second, on a 9600 baud rate, a reasonable choice? And finally: if I understood correctly, I will not need any poll at all, as with your approach I can implement a "single-master/multiple-slave" context... but with a "moving" master. Is this right? Commented Sep 5, 2015 at 8:18
  • See updated answer.
    – Nick Gammon
    Commented Sep 5, 2015 at 21:08
  • See amended answer which demonstrates a moving master concept.
    – Nick Gammon
    Commented Sep 7, 2015 at 7:49
  • is a 10-polls per second, on a 9600 baud rate, a reasonable choice? - in my current test (see screenshots) I am testing 4 devices repeatedly in 75 ms at 19200 baud, so that is 13 status reports a second. Basically that means one device should react to a status change (eg. a switch closure) from another device in 1/10th of a second. Your initial question mentioned 4 devices, so you should be able to get similar results.
    – Nick Gammon
    Commented Sep 7, 2015 at 22:43
  • could you please tell me where you got the white wire clip that is in the picture with the red background (the clip is attached to the wire bundle)
    – jsotola
    Commented Jan 2, 2018 at 3:07
2

Remember RS485 is not a protocol, it is a definition of a physical transport layer. With the Mega as you chose you can use serial 1,2,3 and run them in full duplex mode over the RS485 network e.e. you can receive what you send.

I have it operating in a multi master configuration. Each mega when it transmits it also receives what it just sent and can determine on a byte by byte basis if it needs to back off. The delay before coming back on line is determined by the node address and when the bus becomes available.

The code will use the write function instead of the print function allowing each byte to be checked. (In time I probably will do it via software and emulate CAN or just use a can controller). Yes 8 bits works just fine, the ninth bit is a parity bit or other uses depending on who is defining it.

My test system is operating with packets and sends in this format:

|Length|Target|Source|Sequence|Command|Flags|Data|Check

and expects an ACK in response. This is relative simple first pass and has no illegal codes. All bytes are Hex but you can use what you want.

This gives me Carrier Sense, multiple access but with destructive arbitration. If you want CSMANDA just use a CAN physical layer or bit bang the data you send then you can arbitrate on a bit by bit basis. It won't be that much different in software other then the transmit portion.

I am planning on using the RPi with Linux as the main node. With 8 bit data it will work just fine. I just started about two weeks ago on this configuration and so far it is going well. I am operating at 9600 baud and have a lot of free time.

Gil

1
  • How do you have things set up to sense collisions? Does the particular chip you're using for the RS-485 interface return the sensed data on the link on the RX line at the same time as you send it data on the TX line and it transmits that?
    – cjs
    Commented Apr 15, 2017 at 14:40
0

If you want to use Nick Gammon's protocol you might find this useful: https://github.com/Sthing/Nick-Gammon-RS485

It is my implementation of the protocol in Python (for use on a RaspberryPi). I have only tested python-to-python yet, testing against Nick Gammon's code is next on my to do list.

/Thing

1
  • I've been succesfull, lately, in testing Nick Gammon's RS485 non-blocking protocol, between three MEGAs in a "one master, two slaves" scenario. I've encountered several problems, mainly introduced by the usage of physical serials (and not SoftwareSerials). Luckily, I've been able to fix everything. I've published the running code (with some other details) here: github.com/verzulli/arduino-smart-home - I'm currently actively working on such project, and plan to update it during upcoming weeks. Commented Oct 29, 2015 at 23:57
0

I think the CDBUS protocol for RS485 is exactly what you want, it introduce an arbitration mechanism that automatically avoids conflicts like the CAN bus. I can even transfer video stream through it:

enter image description here Full video: https://youtu.be/qX5dh4wcfSk

The Raspberry Pi can output preview video and control command at the same time. We can monitor the recognition process on the PC. When problems are encountered, it is convenient to know the reason and adjust the parameters, and disconnecting the PC will not affect the demo operation.

enter image description here In addition, the Raspberry Pi can access the internet at any time through the PC, and it is easy to update software and for remote control.

Details about the CDBUS:

Update: Connect the CDCTL-Bx controller to an Arduino: arduino and cdctl

2
  • The mention of video is irrelevant to the question asked and the Arduino subject of this site, and so a bit distracting from it. Such a volume of data could well cause problems for resource constrained systems unless elegantly handled. If you want to propose this scheme, you should explain how it is suitable for the problem of the question, and for an Arduino... if it actually is. Commented Apr 8, 2018 at 4:33
  • It's the same for Arduino, replace the Raspberry Pi Zero in my diagram to an Arduino, in other words, add an external shield board of RS485 controller for Arduino, communicate through I2c or SPI interface.
    – dukelec
    Commented Apr 9, 2018 at 4:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.