A simple way to drive many arduinos from a pi

Post here about your Arduino projects, get help - for Adafruit customers!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
dcfusor
 
Posts: 39
Joined: Sun Nov 16, 2014 1:18 pm

A simple way to drive many arduinos from a pi

Post by dcfusor »

This is working so well for me in my "Lan of things" project, I thought I should share it back to the community. While not "perfect" nor designed for very long runs, this protocol and hardware trick lets you have many arduino slaves from one master (I'm using pi here for that). I could have designed this far fancier, but simplicity rules for my own uses of this, so this is about as simple as it gets. It has the advantage of ignoring the little spit of garbage that comes out of the pi serial port when booted or connected to by say, one of my perl programs on the pi. I'm using only human-readable ascii myself, but all values 0-255 are possible in most of the fields. Real engineers don't consider the glass half full/half empty - they make the glass the right size, and that's what this is - for long runs, get into RS485 or similar...this is few-parts, small code - the right size for lots of uses.

The layout is quite simple. The first character is the "slave unit ID" - for a few, I'm just using 1-9 for that, but you could use anything.
The next character is whatever command you want to send the slaves, only the one with matching ID will actually get it. If you wanted to extend this (would be easy) that command could contain further data, I've just not had a use for that so far, so it isn't in the provided sketch (which is meant to be added into your stuff via cut and paste and modified for whatever commands you want). A <lf> triggers the slaves to check out the command and maybe do it if it's for them and one they recognize. Otherwise they simply ignore traffic from the master.

Both pi and arduino serial ports are "high" when resting - the stop condition. With a strong buffer (and this one is superman) - a pi can drive quite a few arduinos, and in fact if there's a problem it's the fact that this buffer is so strong it can more or less power up the slaves via their protection diodes. You may want to add a series resistor on each one to prevent that - series terminate the transmission line. I'm doing this successfully at 20's of feet at 115200 baud with no issues, other than taking care to keep the respective grounds "close together" via fat wire, and alternating signal wires with AC grounds (eg ground and power) in cheap telephone cable or flat cable.
If you have extra wires, make them ground. In my use, I also send +8v from a switcher off my UPS batteries to power the slave arduinos, and let them use their series regulators to get precise (or at least stable) 5v, since the arduino a/d uses that for a reference, and I do use those.

Here's the hardware schematic:
Hardware to drive many slaves and implement wired-or back from them all on one cable.
Hardware to drive many slaves and implement wired-or back from them all on one cable.
MSDrivers.gif (23.88 KiB) Viewed 875 times
Note I'm using an IRF-7105 or similar, wired as a super strong inverting buffer here. (oops forgot to mark the drawing - sources to the rails, drains together in the middle) Most similar complimentary fets would work fine, this one is pretty stout. It in fact needs the low value pullup due to the gate charge issues for high serial port speeds. I was trading off current drain for speed here - this is real close to the biggest (lowest drain) that supports 115kbaud well. You can build the slave-transmitter on Ada's nice screw shield board for UNO if you cut the TX track before you put the other parts on. All the other parts except the fet pair are here someplace, I get my fets at DigiKey.

This protocol is fairly forgiving, as it uses timing in the slaves to ignore any non-contiguous command (such as that garbage the pi sometimes spits out), and assumes simply that you'll try again from the master if things don't work out. I have my slaves coded to report their state on command, so you can see if a command "took".
Since I'm mostly using the arduinos for data acquisition and dumb but fast control, and the pi for the "smart stuff" like MySQL, NGINX, CGIs and the like, this is a pretty ideal setup. In this protocol there is no way for a slave to alarm or interrupt the master - it has to ask. Simplifies things a great deal. The arduino sketch is specifically designed to be non-blocking (assuming your command response subroutines aren't) and called once per pass through loop().

Here's the arduino sketch (also attached in file form):

Code: Select all

/*
 Demonstrate the slave protocol for the homestead arduinos, driven by a host serial input,
 with wired-or output from each UNO.
 
 Command format:
 First byte is unit address (we're using all ascii so far, plenty for this for now)
 Second byte is command
 Further bytes if required for command data, up to 32 total bytes including
 '\n' at end which terminates the command sequence.

 Minor error detection/ignore implemented.  Might need more...
 Protocol strategy is just to have host try again if it doesn't get an answer.
 
 For this demo, typing 1l turns on the led, typing 1o turns it off.  Serial monitor adds required '/n'
 if set to "newline" in the monitor window (which isn't the default for it)
 */
 
 
 
 
 
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
#define LED_PIN 13  // gosh, save a byte

#define BAUD_RATE 115200 // pick yer poison

// stuff for command state machine below

#define MY_ADDRESS '1'  // could be any byte, but we're using all-ascii for now, and '\n' for end of command
#define CMD_BUF_SIZE 32 // in theory, two of these would fit in the uno buffer at once.  Probably don't need this much IRL
#define MAX_TIME 20 // milliseconds to timeout if incomplete command (3ms would do for 115200 and fast commands)
byte Command[CMD_BUF_SIZE]; // command buffer for incoming commands
byte CommandIndex = 0; // where am I in above buffer
byte CommandState = 0; // state variable
unsigned long CommandTime; // time at start of command recieve
////// done with command-slave state machine stuff

byte counter = 0; // for debug

////////////////////////////
void Report()
{ // send whatever we have back to host over serial
 Serial.print(counter++); // dummy code for testing
 Serial.println(); 
}

//////////////
void CommandMachine()
{
 switch (CommandState)
 {
  case 0: // looking for work to do
  if (Serial.available())
  {
   CommandTime = millis(); // for later possible timeout
   CommandState = 1; // get bytes till done or timeout
  }
  break;

  case 1: // getting command
  while (Serial.available())
  {
   Command[CommandIndex] = Serial.read(); // get a byte if available
//   Serial.print ("received:"); // debug
//   Serial.println(Command[CommandIndex]); // debug
   
   if (Command[CommandIndex] == '\n') // single quotes, else compiler thinks pointer to string
    { // got a full command, go parse it
     CommandState = 2;
     return; // get it on next roundy round of loop() 
    }
   CommandIndex++; // bump the position
   if ((CommandIndex >= CMD_BUF_SIZE) || ((CommandTime + MAX_TIME) < millis())) // enough parens?
   { // error of some kind, reset state machine
    CommandIndex = 0;
    CommandState = 0; // ignore what might have been garbage
//    Serial.println ("Command error"); // debug
   }
  } // end while
  break;
  
  case 2: // parse command
   if (Command[0] != MY_ADDRESS) // is this even for us?
   { // no, ditch it
    CommandIndex = 0;
    CommandState = 0;
    return; // forget it ever happened 
   }
   // It might have been for us, any BANNED tests might go after this comment.
   // By convention, address of unit is first byte, command is next, data might follow, should be enough.
   // For time consuming or big stuff, use a subroutine call instead of inlining it like this example.
   // Commands are all-ascii for convienience of host (and we slaves).
   switch (Command[1])
    {
     case 'a': // host requests an ack
        Serial.println("ack");  // should work
     break;
     
     case 'l': // for test, led on
      digitalWrite(LED_PIN, HIGH);   // turn the LED on (HIGH is the voltage level)
     break; // end 'l' command
     case 'o': // led off command
      digitalWrite(LED_PIN, LOW);    // turn the LED off by making the voltage LOW
     break; // end off

    case 'r': // report our "stuff"
     Report();
    break;
    }    
  // we are done with this one   
     CommandIndex = 0;
     CommandState = 0;
//     Serial.println("end state 2"); // debug
    
   break; // end state 2
 } // end switch state machine
} // end command state machine routine


// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(LED_PIN, OUTPUT);
  
  Serial.begin(BAUD_RATE); // someplace to get commands from
}

// the loop routine runs over and over again forever:
void loop() {
// other stuff you're doing goes here (data aq & control?)
  CommandMachine(); // look for work to do from host
}
Hopefully it will be obvious how to add commands to the big switch statement. I've standardized here on ! for "reset" and a for "ack". I generally use r for "report your stuff". The rest - go for it. I'm using this to control arduinos for data aq, pi camera pan/tilt and other things. "It just works".

I see this board will only let me add one file at a time. I'll reply to this with the arduino sketch as a file. This might be a case of our old saw "beware the code that isn't there" because it doesn't have to check things that can't happen. This may change if you modify it too heavily, of course. KISS.
Last edited by dcfusor on Tue May 19, 2015 3:18 pm, edited 4 times in total.

User avatar
dcfusor
 
Posts: 39
Joined: Sun Nov 16, 2014 1:18 pm

Re: A simple way to drive many arduinos from a pi

Post by dcfusor »

OK, here's the zipped arduino sketch folder for the above. FWIW, I've found that simply configuring the pi's serial using raspi-config works fine, just make it a serial port, and it becomes /dev/ttyAMA0 in the pi code when I use perl's Device:SerialPort module to connect to it from the pi. I'll be updating various examples (GPL, have fun) on the link above and other threads in my software forums. Most of what I'm now using is already up there, but is too application specific for this forum. (I'm collecting data from arduinos, stuffing it in a MySQL database, using perl CGI's from NGINX...and gnuplot to plot data from my homestead, and control pan/tilt for the pi camera and some water valves - you probably aren't doing all that?).
Slave.zip
Arduino sketch for a slave device - one master, many slaves. I use a pi mod 2 for a master.
(2.09 KiB) Downloaded 40 times

User avatar
adafruit_support_mike
 
Posts: 67391
Joined: Thu Feb 11, 2010 2:51 pm

Re: A simple way to drive many arduinos from a pi

Post by adafruit_support_mike »

Nice design! Thank you for sharing it.

User avatar
dcfusor
 
Posts: 39
Joined: Sun Nov 16, 2014 1:18 pm

Re: A simple way to drive many arduinos from a pi

Post by dcfusor »

You're welcome, it does seem to work well and it was quite inexpensive. Those screw shields, BTW, are really worth it in work like this, and as the retired owner/CEO of a company that did product dev, I know it's really a good deal to get a board with tons of holes in it cheap - and screw terminals for hooking up sensors on top. In this case, I cut the trace between the arduino serial XMIT pin and the screw to insert the two transistor circuit between them before soldering on the rest of the parts (which hide the track you want to cut).

If anyone wants to see this in use, as part of something much more complex and app-specific (my lan of things) - here's the "real deal" sketch for that.
ArdT1.zip
The above in use with a bunch of other code in a specific app for data aq for my lan of things. You can see how it's integrated with the rest. The pi master just polls this about every 5 seconds, sums or averages the resulting data (depending on type) and then stuffs it into a mysql database once/minute.
(9.45 KiB) Downloaded 41 times
Once it's in the database, the pi then serves up CGI's to my lan from that data, using gnuplot, perl, and other goodies that are suited to the pi, but not an arduino.
I also have a couple commands to the arduino to "do stuff" in here - do valves, autofill my cistern from the rain barrel, and so on - I'll probably add a cistern heater for winter when there's a danger of my plumbing freezing, for example (left a couple pins for later, but I can also add more arduinos).

The lashup seems pretty robust. Worst thing that happens if the pi goes down is we miss a sample or few in the DB, but gnuplot simply interpolates over that when plotting - if you didn't know it happened, it'd be hard to tell. The pi is on a UPS I built specially for it, which in turn also powers the arduino and a 6v gel cell for handling the big current loads of my solenoid valves. (see Lan of things link above for more details).
To extend the life of the SD card in the pi, I used a Sandisk "extreme" USB stick with two partitions, one mounted over /home, and the other mounted over /var, where all the serious writing gets done...A USB3 "extreme" stick can saturate the pi's USB2 speeds, and seems as fast as, or perhaps faster than, even a class 10 SD card, while having better wear leveling...not to mention, it's easier to back up - even if the pi won't boot I can just plug it into some other machine.

User avatar
baccuss
 
Posts: 9
Joined: Thu Nov 29, 2012 9:03 am

Re: A simple way to drive many arduinos from a pi

Post by baccuss »

WOW !! Great of you to share man, this is really something. You haven't posted any videos of your system in action have you? I'd be nice to see the entire 'symphony' in full swing :)

User avatar
baccuss
 
Posts: 9
Joined: Thu Nov 29, 2012 9:03 am

Re: A simple way to drive many arduinos from a pi

Post by baccuss »

Just saw your site 'http://www.coultersmithing.com/forums/v ... f=59&t=904'. AWE-SOME !!!!

User avatar
dcfusor
 
Posts: 39
Joined: Sun Nov 16, 2014 1:18 pm

Re: A simple way to drive many arduinos from a pi

Post by dcfusor »

Thanks, guys. I do have some of this up on youtube as things went along. I am also doing another one with different sensors etc for the "shop" building I just moved from due to a breakthrough in the fusion reactor I've been working on there. So I'm now living in the "remote" building - distance is your friend when the gamma and neutron flux gets really big. All this stuff is (along with labor saving, which it's already a big success at) is to be a dry run for remote controlling and observing the fusor without getting myself fried. I came all too close already, last year, and should be dead (I WAS really sick for a couple months, my detectors blanked when so much radiation came in they couldn't count (eg just went low true and sat there, no clicks or counts), so I didn't know it till too late). Guess I'm hard to kill!
Here's my youtube channel: https://www.youtube.com/user/DCFusor/featured If you scroll the playlists, there's one labeled LAN of things - there might be other things of interest there and on my site, where I do share most of what I do GPL first (after all, it's my site and I can cry if I want to ;~).

The end result will be the ability to sense and remote control my whole campus (50 acres, 4 buildings) from anything with a web browser, via CGI's, and log the data in a MySQL database(s) on the various building-master pi's. For nearly all of this, the model 1 will do, but I happened to get a couple model 2's and they are loafing unless you ask for huge time duration plots of data taken 1/minute, which is a lot of grinding in Gnuplot. I am using a few tricks for pi to avoid wearing out the SD card. I've found a fast USB3 stick will just barely saturate the USB2 interface, reading and writing (while an average USB3 stick will not!) - and I use a partitioned one mounted over /var and /home, where most of the writing happens. Since USB sticks are trivial to copy fast, and have better wear leveling in the first place (and can be mounted with things like noatime) I'm hoping that will make the system more reliable. Obviously time will tell. rsync and phpmyadmin helps keep things backed up to spinning rust media just in case.

Anyway, most of this stuff came from Adafruit, so I figured why not give some back - I've made great use of the software they've developed and made free, why not share some back? In some cases I've made app specific improvements on the basic libraries, more to come. Mostly so things don't block in the "cooperative multitasking" arduino system. A lot of times you can use state machines to keep some variables updated for whenever you want to read them with no blocking at all. If the data isn't there yet, they just return, leaving the old value in the variable in case the master polls. This is fine for slow moving things like temperature or water levels, for example.

The water sensor I describe is based on an idea in a very old National Semiconductor app note - it uses the AC dielectric constant of water, is non-corrosive no matter what (it's all AC, so no electrochemistry) and so far seems dead-reliable. I'd put it on some arduino A/D inputs as I wasn't sure if I'd need to set thresholds etc, but it seems I hit the right numbers and could have used plain digital inputs. Every other water level sensor I've used here in the last 40 or so years has failed young. This looks like it's going to do the job. It might in fact actually be the bigger news here. I pump an AC square wave (12v or so pk-pk, from a 555) into the plastic tank on a long titanium wire, and sense with some Ti wires of various lengths coming down from the top via capacitor coupled voltage doublers with a 100k ohm load on each, and bingo - they go "high" when they touch water, don't care about condensation or corrosion, temperature or anything else. I had the Ti wire as a result of some stuff I acquired for the fusion project, but stainless steel should work fine for house water kinds of things (McMaster-Carr is the source of a lot of neat stuff, even Boron Nitride for HV insulation).

User avatar
adafruit_support_mike
 
Posts: 67391
Joined: Thu Feb 11, 2010 2:51 pm

Re: A simple way to drive many arduinos from a pi

Post by adafruit_support_mike »

dcfusor wrote: Anyway, most of this stuff came from Adafruit, so I figured why not give some back - I've made great use of the software they've developed and made free, why not share some back?
That's pretty much the Open Source/Open Hardware manifesto. ;-)

Locked
Please be positive and constructive with your questions and comments.

Return to “Arduino”