5

I'm looking to communicate between ~5-10 Arduinos to send commands and data between them. By "Arduino" I mean the ATmega328 programmed using the Arduino programming framework.

Each of the devices would be on the same PCB.

I know I2C and SPI can communicate to some degree, but that they're really meant for peripherals devices connected to a micro controller rather than connecting multiple micro controllers to each other, and that they usually only send a few bytes of data at a time. On the other hand I know LCD displays can be driven using them, and I'd imagine there's a lot of data being transferred in those cases.

Is there a something like I2C (multi-master, multi-slave, single-ended, serial computer bus) for communicating between micro controllers? Allowing each device to send data to each other device unsolicited. For example, if a switch was pressed on device A, it would tell device B, without having device B ask A all the time if a switch was pressed. and vice versa.

Edit: I found some libraries that may do what I want with RS-485. Is this the only viable option for ~1k bps transmission between 5-10 devices? I would imagine TCP/IP over Ethernet would work as well, but that seems like too much. Are there any I2C libraries that allow sending that amount of data between only master devices?

  • Have you looked at serial connection? – Avamander Mar 17 '16 at 20:47
  • do you mean RS-485? – waspinator Mar 17 '16 at 21:00
  • 2
    There is a very similar question here: How to interconnect multiple Arduinos with a Rpi to control home-lights/switches - ignoring the Rpi part (it wasn't really relevant to the question) I count that as virtually identical to what you want to do. I made a lengthy response there about doing a "rolling master" system. Please read the other question/answer and see what you think. – Nick Gammon Mar 17 '16 at 21:33
  • You need a physical layer (cabling or board), electronical "standard" like RS-485 but 5V TTL is also good. Then you'll have to choose what type of bus arbitration (round-robin, master-slave or broadcast messages) and probably implement some addressing or error checking. – Paul Mar 17 '16 at 21:48
  • On a breadboard, I wouldn't use RS485, since it needs transceivers. Just 5v for a logic 1 and 0v for a logic 0. But from that point, possibilities are endless. It will also depend on speed requirements etc. – Paul Mar 17 '16 at 21:51
2

There are a number of concepts that can help you realize your dream. I can't, in the space of this answer, tell you exactly how to implement what you want, but I can show you the concepts that will help you to implement it.

First off there's the concept of the bus master. This isn't necessarily the device that instantiates communication - instead this is the device that owns and controls the bus.

When a device that isn't the bus master wants to communicate on the bus it first asks permission from the bus master. The old Z80 (well, I say "old", but they are still in use in many forms today) used this concept to allow other chips to use the data and address buses. It consists of two signals - BUSRQ and BUSACK. A device first looks to see if either of BUSRQ or BUSACK are active, and if neither are it then activates BUSRQ. If the bus master is willing to give up the bus to the other device (it's not using it at that moment) it activates BUSACK and the other device then knows that it can use the bus. Nothing else can use it until BUSRQ and BUSACK have both been released. Nice and simple and elegant.

But not perfect. If two devices both decide to ask for the bus at the very same instant you get a collision. This is a common problem amongst shared bus systems like this, and causes untold problems unless you know how to handle it properly.

Enter the concept of listen-while-you-talk. This involves the device that is sending on the bus also listening to what is being sent on the bus through a separate receiver. It then can know if what it sent on the bus is what actually ended up on the bus. For instance if two devices talk at the same time and one sends 10011001 and the other sends 11001100 the result that appears on the bus might actually end up as something else, such as 11011101 or maybe 10001000 depending on how the bus signals are created. So if you know what you sent got corrupted you can now do something about it.

Next concept: backing off. This is where both the senders wait for a short period and try and send again. As long as they both delay for a different amount of time the first one to try will get the bus and be able to communicate. But how do you guarantee that they will both delay for different times? You may think the answer is simple: use a random number, like rand() or random(). But that is also problematic:

Another concept: The pseudo random number generator

The Arduino doesn't generate random numbers. It just uses a complex mathematical formula to create a sequence of numbers that, to us, look random. They aren't though. Write a small program to print 10 random numbers through serial and run it multiple times (press the reset button). You will find the same "random" numbers in the same order every time. Try it on another Arduino and you get the same numbers again. Always the same.

So what to do? The answer is called seeding the random number generator. The next number generated by rand() et al depend on the number that had been generated last. So change the first number and all the rest of the numbers will change. However, you have a catch-22 situation. You need a random number to seed the random number generator to make it random to be able to generate a random number to seed the random number generator... ad infinitum. You see where that's going? You can't seed from rand() since rand() isn't random until you have seeded from a random source. So you need to find a random source.

And that's not an easy task. The best source of entropy as it's known is white noise. This can be generated in a number of ways with a number of different circuits - from the breakdown of a diode junction to very high gain amplification of the thermal fluctuations in a resistor.

All are quite complex for what you want really, but there is a simpler, if slightly less random, method - read an analog input that isn't connected to anything. It won't have as much of a range as a proper entropy generator, but it should provide enough randomness to give a reasonable chance of each device getting a different seed.

Another useful concept is the interrupt.

This is good in a situation where you don't want the complexity of a multi-master bus with all the collisions etc. You have a single master that does all the work on the bus, and when a slave device has something important to say it nudges the master with an interrupt. The master then goes "Yes? What do you want?" to which the slave replies "Someone pressed my button".

That way the master isn't constantly polling the slave to see if the button has been pressed. It is often used in bus arrangements like SPI and there are many chips, such as IO expander chips, that can assert an interrupt when one of their input pins changes state.

But if you have 20 devices does that mean you have 20 interrupt pins? Not necessarily. New concept: wired OR.

It's perfectly possible to have multiple different slaves all using the same interrupt pin. The pin is normally held HIGH with a resistor (it could be an internal pullup resistor) and each slave has an open drain output connected to that pin. An open drain output, when "off", is not connected to anything - it's like the pin is in input mode (in fact it can be emulated on chips that don't have open drain by switching between input and output mode). When the output is "on" it connects the pin to ground, pulling down the IO pin, just like a button would.

It's then up to the master to make its way round the slaves that it knows are attached to that interrupt to see who needs attention. You can of course implement a number of different interrupt pins with different groups of slaves on each one - maybe a high priority one with just one device, and lower priority ones with multiple devices on each, for example.

The same concept of wired OR and open drain can be used for allowing multiple devices to all share the same physical wires. That is exactly how I2C works - the two bus lines are pulled up by resistors and the devices on it use open drain outputs to pull the line low to release it back to high to create the different logic levels. If two devices both pull it low together it will just be low. Without the open drain method if you had one device outputting a 1 and another outputting a 0 you would basically get a short circuit between the two and you would end up damaging chips.

And then of course you have the concept of synchronous versus asynchronous communication, but that is an entirely different kettle of fish. Simply said though, protocols with a clock, like SPI and I2C, which have a master generating that clock, are synchronous. Protocols like the UART and RS-232, RS-485, etc are asynchronous - they rely on both ends agreeing on how fast data is being sent (baud rate) so they know how to interpret the signals as they arrive.

  • 1
    I would just want to add that I2C is theoretically multi-master capable, and has an arbitration scheme that does not need random numbers. However, there seems to be an issue with Atmel's implementation. C.f. for example TWI module seems buggy in multi-master communications and Multiple Master Problem with Atmel AVR Microcontrollers. Single master + interrupts seems to me like the easiest solution. – Edgar Bonet Mar 18 '16 at 9:24
  • @EdgarBonet Pretty much all protocols can be made multi-master if some form of bus arbitration system is implemented. Some are more suited to this than others, and some, such as I2C, have "official" ways of doing it, some of which work better than others. The most reliable way, of course, is to not need a multi-master system at all :) – Majenko Mar 18 '16 at 11:21
  • @EdgarBonet do you know if the wire library supports multi-master? Is it as easy as this michael.bouvy.net/blog/en/2013/05/25/… – waspinator Mar 18 '16 at 19:10
  • @waspinator: I cannot tell, as I have never tried. It would seem the Wire library handles bus arbitration, but I don't know whether it can cope with the issues I pointed to before. – Edgar Bonet Mar 18 '16 at 20:15
1

Check out PJON - https://github.com/gioblu/PJON. It's a one-wire alternative.

  • 1
    Please can you elaborate on your answer possibly an example of usage or pros and cons,, as this is really a link only answer at the moment. – RSM Apr 15 '16 at 6:14
  • 1
    Please add some information on why it's better than the alternatives/what makes it stand out. Thanks! – Anonymous Penguin Apr 16 '16 at 14:34
  • PJON has been made available for multiple platforms, not just Arduino. It's simpler than I2C/one-wire. It handles noise better. It has multi-master support. Read the wiki from the link. – EllisGL Sep 18 '16 at 7:05
0

In addition to @Majenko's excellent answer, you need to have a think about what you are communicating.

It sounds like you want to communicate a single bit - perhaps you can get away with individual pins? If you have a master, and up to 13 slaves, you could have one pin on the master connected to one pin on each of the slaves (via a resistor). If slave A wanted to signal it's signal, it would raise the pin; the master would then be able to poll each pin in turn, to work out who said what.

  • sorry, that last bit was just an example of how I'd like devices to be able to "push" data to each other instead of polling for it. I, in fact, do want to send more data. ~100 bytes 10 times a second. – waspinator Mar 18 '16 at 18:32
0

Another option is SPI.

SPI can be used with any number of devices, in a ring. It was explained to me as follows: any participant can toggle the clock line 8 times, to move the byte in it's own buffer, into the next device's buffer, in a ring; the device gets a byte from the previous device. If someone else toggles the clock 8 times, you get notified that the new byte is ready (this is done in hardware).

You need to think carefully about a protocol for this - an "address", "length", "command", and then any number of parameters. The sender sends the "address" byte, sets the "length" byte in the buffer, and holds the rest ready. The next device has received the "address" byte, and sees if it is the intended recipient. If it is not, it notes this down, and pushes the chain round, notes the length, and waits until someone ELSE has pushed that many bytes around. If it IS yours, read the rest of the data, pushing the data around, and blanking it as you go (to stop it coming back). A blank address (e.g. 0) would be reserved, meaning "don't do anything with this, wait for someone else to push it around, and read the next byte as the next address". All devices would need to initialize to 0 on startup.

If you need to set addresses dynamically, set a special byte "all stations", with a command which means "your device number is X, and tell the next device it's device number is X+1". Someone would need to initiate this, of course.

You also run the risk of collisions, if two devices start sending at the same time, or if something doesn't relay what it's supposed to (e.g. a chip is powered down). You need to think about how you're going to detect/recover from this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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