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: 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
}
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.