On top of this, it needs to be very lightweight so that I can send data down through all of the performers without using too many resources of the device.
I also want it to be cheap, both with cost of parts for implementation, as well as for system resources in the chip firmware.
The solution I came up with is to use the builtin UART, which usually is set for a standard RS232 connection, but in a cascading daisy chain fashion. This means that the hardware-assisted UART in each chip, plus one data line down to the next chip is all that's needed to implement this. The networking stack running on the device is light enough that it should not impact performance of the main usage of the Arduino.
The above diagram shows how four Arduino boards are wired for this. Basically instead of the TX and RX of a board connected to its host, instead its TX goes to the next device in the chain, and the RX comes from the previous device on the chain. The actual implementation of this uses my D15-based ATmega widgets, and thus I followed this diagram:
Four wires are needed to hook up each node: power, ground, serial in, serial out. In the above configuration, the final node does not output to anything. I had the above working quite well with a simple firmware program.
Before I constructed this, I had no idea if it would even work. But I spun up five of these, networked together, and it all seems to work perfectly. Here's a video where I demonstrate it:
The program reads in a character from serial input, if it's a "b", it sends the "b" to the next device immediately, then turns off the LED for 1/4 second. If it is a number, it will turn off the LED for that many seconds, then subtracts one from that number and sends that new number down to the next node.
So if it got a "6", it will turn off its LED for 6 seconds, then sends a "5" to the next one. This was the simplest program I could think of to check if this concept would even work. Turns out, it does!
This diagram shows an updated version which adds a few features which will be helpful for getting the software design debugged. The reset line is connected to the FTDI header, as well as a jumper to feed the output of the first device or the last device back to the host. This will enable me to reprogram the first node quickly, without needing to plug the micro into an FTDI interface cable. This also will let me experiment with adding a "return". The "return" line takes the output of the final node in the daisy chain, and sends it back to the host. I may use this as a trigger for a mechanism to continuously send data states to all nodes. (Once the computer host receives something from the final node, it can send down the next data envelope.)
So the basic idea of how this will be used is as follows.
The host will be playing back an audio track, and looking at a few data tracks. At any particular timeslice, it will prepare a data envelope of bytes, containing all of the synchronization events for the animatronics. This includes digital bits to turn LEDs, motors, and solenoids on and off. There are also analog bytes that will be for controlling servos. Let's try out a use case to better understand how this will work in practice...
First, the host looks at the data for the current timeslice. For our example, we will have three animatronic objects on the serial bus, each of which has 5 data bytes associated with it. The data is prepared and would look something like this:
- 15, 1, 1, 1, 1, 1, 200, 200, 200, 200, 200, 30, 30, 30, 30, 30
The first number is the count of the numbers that follows. In this case, 15 digits will be included. This is sent out from the computer host via RS232, 9600 baud. The first animatronic microcontroller receives this, and will remove the numbers that it needs from the end. In this case, it will pull off the five "30"s from the end. It will also reduce the count from 15 to 10. This will look like this:
- 10, 1, 1, 1, 1, 1, 200, 200, 200, 200, 200
It now sends this modified data envelope to the next device on the bus. That device will pull off the five "200"s from the end, and then send down the new envelope.
- 5, 1, 1, 1, 1, 1
The final micro on the list will pull off the five "1"s, the count reduces to 0, and it refrains from sending anything further.
The specifics of the protocol are yet to be worked out.
Also, the specifics of the interconnect are yet to be worked out too. It'll probably be four pin header for input, four pin socket for output. (+5v, Ground, data, return)
The latency of this will be imperceptible for the use cases I plan to use this for, and it will enable me to have oodles of devices on the serial bus, all controlled by one host computer. And best of all, no additional hardware (MIDI, DMX, I2C, SPI) is required, and no additional IO pins will be used for it.
Here's the Arduino program if you want to try it out yourself and you have a couple of Arduinos lying around...
/* Cascading Blinker
**
** 2012-December-01 Scott Lawrence yorgle@gmail.com
**
** A number character when received will turn on the LED for that number of seconds
** It is then decremented then sent out.
**
** Boring on its own, but it's a test to see if I can cascade RS232 devices.
**
** Host--->RX(device 0)TX--->RX(device 1)TX--->RX(device 2)TX---> Etc
*/
const int ledPin = 8; // 13 for standard arduino
void setup() {
// initialize serial:
Serial.begin(9600);
// make the pins outputs:
pinMode(ledPin, OUTPUT);
digitalWrite( ledPin, LOW );
}
void blinkFor( long ms )
{
digitalWrite( ledPin, HIGH );
delay( ms );
digitalWrite( ledPin, LOW );
}
void loop()
{
int ch, digit;
long d = 0;
// wait for something to come in
if( Serial.available() == 0 ) return;
// read in a character.
// If it's within '1' .. '9', set the timer for that number of seconds.
ch = Serial.read();
if( ch >='0' && ch <= '9' ) {
digit = ch - '0';
if( digit > 0 ) {
d = digit * 1000;
}
}
if( ch == 'b' ) {
// turn off for 1/4 second, send down echo immediately
Serial.write( 'b' );
blinkFor( 250 );
}
if( d > 0 ) {
// turn on the LED for the requested number of seconds
blinkFor( d );
// cascade out our digit, one less than ourselves
Serial.write( (digit - 1) + '0' );
}
}
This is interesting but I am wondering why would you do this instead of I2C and the Wire library? I would think I2C would be more efficient because devices don't have to 'pass on' messages, they just only pay attention to messages which are addressed to themselves.
ReplyDeleteIs it that this gives a longer range?
A few reasons: 1, I2C would require the use of two additional lines, and on these widgets, i'm already pressed for pin usage... it would have to be software based as the I2C lines are not broken out to any headers at all. On top of that, the additional use of the library would use resources as well.
ReplyDeleteOn top of that, each device would have to be specifically programmed with an ID for it to listen to. I'd rather have their ids be defined just as their physical connection to the linear network.
Also, the devices will be placed along a 50-100' connection range, and I'm not sure that I2C would support that.
Not to mention of course that special hardware would be required to work on debugging it if i need to see what the controller is sending out. using serial, i can just hook up a terminal (with standard ttl-RS232 voltage level converter)and i can see exactly what's going out.
Add on to that making the 5v-3v voltage conversion hardware, and just doing this using the hardware-assisted UART starts looking like the best option...
Scott,
DeleteThis is an interesting idea. You are correct that i2c doesn't support the cable length you are proposing (10' is about the limit without external drivers). However, neither does RS232 (limited to about 50') without special handling. Even with that, I don't know that the signals on the TX/RX pins (which are not RS-232 level) would even go that far.
As far as sacrificing pins, both the UART and the i2c use the same number of pins (2). The are different pins, TX/RX vs. SCL/SDA, but it is 2 pins in either case so that's not really an issue.
And each device doesn't necessarily need to be programmed with an ID, there are other ways to deal with that problem that don't require repeating the message over and over down the line.
You mention that this is fast enough for your application. That may be the case, but this method might not be fast enough in another setting.
I also question whether it is truly fast enough when you scale to the size your project proposes. Think about the traffic when you have, say, 100 units on the buss. Assuming your suggested protocol of 5 bytes per node, the host has to transmit 500 bytes. If you were on a shared bus like i2c that would be the end of it. With your setup, the first node then has to send 495 bytes, then the second node has to send 490 bytes, and so on. In the end, you must send 25250 bytes total.
Assuming you have 100% efficient reflection between nodes (no delay between receiving the packet and starting to retransmit it) at, say, 9600 baud, 8 data, 1 stop, no parity, that translates to about 26 seconds (25250 bytes / 960 bytes per second).
You could mitigate this by starting to retransmit the packet as soon as you have the first few bytes in (enough to calculate the new length). That way, you would only add 100 to 200 byte times to your total time because of all the parallel processing. Still, your 500 byte packet will take something like 3/4 of a second to work its way down the line.
And, you are still asking all your nodes to do a lot of extra work. On average, each node is transmitting 1/2 the data. In our example, that would be about 250 bytes. And, this is not evenly distributed since the first node always has to transmit the entire packet and the last node has to transmit nothing.
Understood. I plan on having no more than probably 6 nodes on the network. And on top of that, even if i get 50' per run, that's more than plenty, since I plan on needing no more than 50' total. Each node will act as a repeater, so really, no length will be longer than about 10' apart from either neighbor.
DeleteAlso, the I2C pins are not broken out on these devices, so I'd have to use software I2C, or just bit bang them...
DeleteWhy not use SoftwareSerial?
ReplyDeleteIn this way the debugging should be easier
Why not just use the hardware Serial at that point? I'm already tight on IO lines on this, and want to keep the software as tight as possible. -- trying to minimize unnecessary use of RAM and program space
DeletePersonally, I would have used a star topology ( connect all arduino TX's together and then to the FTDI RX , and all arduino RX's together and then to the FTDI TX).
ReplyDeleteAs for making these devices "configuration free", I would have an initialization script that runs on the server , which when it's run emits a "next available ID" packet, and when a device is powered up, it listens for this packet type, and responds to it with a "device ID taken", and then write the data into it's built-in EEPROM ( just one byte will do 255 devices)... the initialization script just increments the ID number it announces, each time it sees a "device id taken" packet. In this way, you initialize devices by the order that you turn them on, and they remember that order till you change it by running the server-side script again.
The upside of this arrangement is that there's no "retransmit" latencies, there's a simple ID based packet protocol ( ie , each note ignores packets with an ID that doesn't match ), and it still only needs two lines to communicate with the PC and each arduino.
You need to be a little bit careful that only one Arduino "talks" at a time, but if your protocol is made so that the "master" PC always communicates first, and the device it's communicating with has only a fixed time to respond afterwards, then thee contention is easy to manage.
Also, remember to use tri-state logic on your arduino TX pins when they are not actively TX-ing, otherwise you'll mess up TX from the other asrduino/s.
Buzz: By chance, have you ever successfully implemented a Serial Star Topology like you describe, or is it just theory. If the Star actually works, it would make a design I'm working on much much easier.
DeleteGo with RS485 at a normal 9600 baud rate: no daisy chain but parallel connected slave nodes on a single line is the way to go (DMX anyone?).
ReplyDeleteStill you would need to address them but c'mon it's a lot easier and faster to program "snif for your address" than "repeat the whole stream of data while you search for your part"
Burning the address on the eeprom is not that hard and doesn't require IO pins, just the usb programming in case of arduinos. Also that way you make sure that the remote controlled flame thrower doesn't shoot because you connected the terminal cables in the wrong order.
As a bonus everything works directly from +5V, can connect everything with the cheapest ugliest twisted pair you can find and still get range, also the RS485 converter chip (SN75176) cost something like ten cents each.
Actually, it's pretty easy to write the "forward everything that's not for me" technique, as I wrote it earlier this evening. I understand your points and what you're getting at. And in an ideal world where focusing on making this network robust and done the proper way, I would go with your suggestions, but for this project, just using the serial for the network is good enough, and in fact, as of today, it's done, and i can work on the portions of the project which are more fun for me. ;)
DeleteScott;
ReplyDeleteAs an old practitioner of the electronic arts I have a concern in that all signals are notorious for noise pick up causing the message to be distorted. In theory your solution sounds usable but have you run the system in a electrically high noise situation ? If so I'd like to hear about the results since most signal transmission schemes have an error verification /correction / retransmission feature built in. I have been burned in the past with noise and floating grounds
good luck and thanks for the solution
Good to know! Thanks for the cautionary notes! This will ultimately be running in our family room in our house, not much electrical noise around it. I will certainly post a follow up when I do more testing in there. :)
DeleteScott,
ReplyDeleteDoes this Ring configuration work when one of the Arduino boards is plugging into a computer via USB? (I am still learning the basics of serial comms)
I do not believe that it will. I have further to post about this. I have made each Arduino-node into its own module with FTDI header for input, and a FTDI header for output. For testing, they're just plugged into each other, with a "reflector" on the end to send the last node's info back to the host. It all works REALLY well. For accuracy, the above picture of the Arduino UNOs is probably not realistic. I will probably recreate the image for the followup post using Sparkfun's Arduino Pro, which has FTDI header on it, and does not have hardware sitting on D0 and D1, which I am not positive would influence the serial connection or not. It is a very good question though. I should probably experiment and get you a definitive answer.
Deleteref: http://arduino.cc/en/Main/ArduinoBoardPro
How well did it work out in your family room?
ReplyDeleteAnd if you were directly connecting the UARTs (without any electrical buffering or conversion), how far apart were the devices? Did you use twisted or shielded cables?
I have not done further tests on this yet. I've had a block in the path for another aspect of the project, so I have put it back on the shelf for the time being...
Deletethanks!
ReplyDeleteYou just made me save several $, as I was about to buy 50 MEGA 2560 arduinos (hosting 3 UART each) as I though I needed two serial ports on each device to acheive the daisy chain communication.
Hey there, thanks for the run down. I read a lot of comments here, and see that you were as recently as July still working with arduino boards, so I have to ask.
ReplyDelete...Did this work? Your proof of concept shows that it should, but did it do what you wanted?
I want to daisy chain 3 or 4 arduino nanos to record data on a Pi Zero. Each of the arduino will control a specific electronic function on an electric motorcycle, and I really want each to pass off their pertinent output (eg: the throttle controller should pass along strings of CSV of throttle input, throttle output, and throttle mode). Because I want to transmit ASCII strings, I'm transferring a good deal more data than you were. Each arduino can just append their data to the end of the string they received and pass it on, the Pi will append the full string into a CSV single file, each line a new data point with several values, then upload its database to a home server when trusted wifi is connected. Essentially the bike should output a spreadsheet of its own dynamometer!
If you've read this far, thanks again. I'll appreciate any thoughts you have. :)
-Andrew