Go Down

Topic: Arduino I2C Multi Master design (Read 29738 times) previous topic - next topic

wyatt

Hello all,

I am trying to set up a couple Arduinos linked in a multi master I2C design.  Each chip is both a message producer and consumer; the proof of concept code I am using will listen for a button press on one board, and send a message to the other to toggle an LED.  Sounds simple?

Well, I have some code which seems to work intermittently, but I can't figure out what is causing the failure.  Basically the first time or two you hit one of the buttons, the other light will flash, but after a few presses it stops responding.  After that it will respond very infrequently.

Code is included below:

Code: [Select]

/**
*
* Sample Multi Master I2C implementation.  Sends a button state over I2C to another
* Arduino, which flashes an LED correspinding to button state.
*
* Connections: Arduino analog pins 4 and 5 are connected between the two Arduinos,
* with a 1k pullup resistor connected to each line.  Connect a push button between
* digital pin 10 and ground, and an LED (with a resistor) to digital pin 9.
*
*/

#include <Wire.h>

#define LED 9
#define BUTTON 10

boolean last_state = HIGH;

void setup() {
 pinMode(LED, OUTPUT);
 digitalWrite(LED, LOW);
 
 pinMode(BUTTON, INPUT);
 digitalWrite(BUTTON, HIGH);
 
 //Initially join the bus as slave device with address 0x1
 Wire.begin(0x1);
 Wire.onReceive(receiveEvent);
}

void loop() {
 if (digitalRead(BUTTON) != last_state){
   last_state = digitalRead(BUTTON);
   //When we need to send a message, re-join bus as master, and send.
   Wire.begin();
   Wire.beginTransmission(0x2);
   Wire.send(last_state);
   Wire.endTransmission();
   //Once the transmission is complete, re-join as slave address 0x1, and register the receiveEvent function pointer again.
   Wire.begin(0x1);
   Wire.onReceive(receiveEvent);
 }
}

void receiveEvent(int howMany){
 while (Wire.available() > 0){
   boolean b = Wire.receive();
   Serial.print(b, DEC);
   digitalWrite(LED, !b);
 }
 Serial.println();
}


Note that this code is for Device 1; the addresses are switched on Device 2: it joins the bus as address 0x2, and sends a message to 0x1.

Note that I am not sure if the multiple Wire.begin() statements are correct - do I need to re-join the bus as a master before sending?  Or can I just beginTransmission / send / endTransmission even if I am in slave mode already?

Thanks for any pointers.

Cheers

savitch

It's my understanding that there is supposed to be a master that controls the clock. Without a master, the clock might be stopping the 1st time the receiver slave holds the clock line as an interrupt. Without the master it probably doesn't know to start it back up.

So maybe leave the address out on one of them so it's set as master. It might also be useful to have a 2nd led to just blink and let you know the program is still running.

wyatt

Thanks for the reply.  Perhaps I am misunderstanding how I2C works.  Instead of stating what I have tried, I will instead say what my objective is; perhaps there is a better way of accomplishing this.

The goal is to have a network of AVR chips which report key presses back to a central AVR as events occur; as well, the central AVR will need to send data to each of the other AVRs on the network to light up LEDs, etc.  Since the key press events are going to be sporadic, and there are potentially many 'slave' AVRs in the network, I don't want to poll for events.

The most obvious way that I thought to implement this was to make the central AVR the I2C master, and the rest be slaves.  However, this seems to lead to two problems:
1) From what I can tell, I2C Slave devices cannot initiate communication; to send data, they must first receive a 'request' event from a master (i.e., be polled).  One of my requirements is to avoid polling, so this won't work.
2) Even if they *could* initiate the sending of data, when a master device joins the bus, it joins without an address; this (to my knowledge) means that you cannot address it directly by using Wire.beginTransmission(address)

It is because of these limitations that I was trying to do the slave-switching-to-master approach as illustrated in the above code.  If anyone can think of a different approach which would still allow bi-directional communication without the need for polling, please let me know!

Cheers

kg4wsv

#3
Jul 22, 2009, 06:23 pm Last Edit: Jul 22, 2009, 06:23 pm by kg4wsv Reason: 1
Quote
If anyone can think of a different approach which would still allow bi-directional communication without the need for polling, please let me know!

Use "out of band" (for I2C, anyway) data to indicate that an event has occurred. For example, use a single interrupt on the master that is shared by all the slaves.  When the interrupt is triggered, the master would poll all slaves via I2C to find out what happened.

If you have few enough slaves, you could do something similar with a pin change interrupt and know which slave triggered the interrupt.

-j

EmilyJane

I just want to comment that I2C was designed to support the concept of multiple masters so there is no reason why your application can't be designed that way.

Also, addresses 0x1 and 0x2 are reserved addresses in the spec and I don't know if that is causing you any problems or not. Addresses between 0x8 and 0x78 are free and you could pic some of those for your tests.

wyatt

Well, this is sweet!  To make a long story short, the Wire library supports multi master I2C buses without a problem; the only thing which needs to be improved on is the documentation on the Arduino site (side note: how do we request a change in the docs? I would be willing to write up correct documentation if someone else can post it on the site).

I broke down and looked at the code for the Wire library, and it turns out that the twi.c function 'twi_writeto' is called from Wire.endTransmission().  In twi_writeto, the comments state that it first attempts to become bus master, then sends the message.   From this, I was able to gather that the Wire library documentation is misleading; while you *can* join the bus as Master without an address, you can also join as a slave.  The reason it says Master can join w/o an address is that it *makes no sense* for a device which could ever be used as a slave to join without an address.

Anyway, with this new datum, I modified the sketch, calling Wire.begin(address) only once for each chip, and using Wire.beginTransmission / Wire.send / Wire.endTransmission whenever an event needed to be processed.  Lo and behold, it works perfectly!

Below is my code, in case someone is interested in doing I2C / TWI multi master with arduinos (use the same code for both chips, but swap THIS_ADDRESS and OTHER_ADDRESS for the second chip):

Code: [Select]

/**
*
* Sample Multi Master I2C implementation.  Sends a button state over I2C to another
* Arduino, which flashes an LED correspinding to button state.
*
* Connections: Arduino analog pins 4 and 5 are connected between the two Arduinos,
* with a 1k pullup resistor connected to each line.  Connect a push button between
* digital pin 10 and ground, and an LED (with a resistor) to digital pin 9.
*
*/

#include <Wire.h>

#define LED 9
#define BUTTON 10

#define THIS_ADDRESS 0x8
#define OTHER_ADDRESS 0x9

boolean last_state = HIGH;

void setup() {
 pinMode(LED, OUTPUT);
 digitalWrite(LED, LOW);
 
 pinMode(BUTTON, INPUT);
 digitalWrite(BUTTON, HIGH);
 
 Wire.begin(THIS_ADDRESS);
 Wire.onReceive(receiveEvent);
}

void loop() {
 if (digitalRead(BUTTON) != last_state){
   last_state = digitalRead(BUTTON);
   Wire.beginTransmission(OTHER_ADDRESS);
   Wire.send(last_state);
   Wire.endTransmission();
 }
}

void receiveEvent(int howMany){
 while (Wire.available() > 0){
   boolean b = Wire.receive();
   Serial.print(b, DEC);
   digitalWrite(LED, !b);
 }
 Serial.println();
}


Thanks to all who replied for helping me walk through this one.

Cheers

EmilyJane

Oh, cool! We were just talking about that over in this thread:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1248311595

I'm glad you figured that out as I was just about to hook up a couple of boards to see if receiveEvent () works for masters too.

Great, thanks for persevering.

mem

Quote
side note: how do we request a change in the docs?

You can post in the bugs and suggestions forum: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?board=swbugs

Go Up