Acting as both master and a slave

I've seen some people post code where an Arduino could act as both master and slave.

Basically the Wire library initializes itself as a slave and registers to receive onReceive events but it also calls Wire.beginTransmission/endTransmission are called from within the loop function.

I know that there are times when an I2C bus can have multiple masters (which this would be the case I guess) but has anyone tried this with an Arduino? Does the Wire library properly check the status of the bus to ensure it is not busy?

Is there anything else I should consider?

The Wire library does not work in a multi-master environment. If you call begin() with an address then you are a slave.

Even if you were able to call begin() again and drop back to master mode, you lose sync on the bus and you end up with no idea of what's going on on the bus. It's like asking the blind guy to read the menu and have the deaf guy listen to the waiter.

The AVR chip itself is quite capable of doing proper multi-master I2C. It's just the simple Arduino library that doesn't allow that option.

MorganS:
The Wire library does not work in a multi-master environment. If you call begin() with an address then you are a slave.

Even if you were able to call begin() again and drop back to master mode, you lose sync on the bus and you end up with no idea of what's going on on the bus. It's like asking the blind guy to read the menu and have the deaf guy listen to the waiter.

The AVR chip itself is quite capable of doing proper multi-master I2C. It's just the simple Arduino library that doesn't allow that option.

MorganS,
I have not had any problem setting up two UNO's as both master and slave at the same time. I setup the OnReceive,OnRequest in both. I just have to handle collision, and NAK errors returned by the endTransmission(), and requestFrom() calls.
I only call Wire.begin() once to assign the slave address on each UNO.

I did modify the Wire library to allow multiple write() calls in OnRequest(), and I added Nick Gammon's WriteAnything() templates. I also implement a Wire.lastError() to actually identify the error because requestFrom() doesn't normally return any error codes, just 0 or the count of bytes received.

I think I heard the latest build includes the fix for multiple write() calls in onRequest(). So other than the lastError() additions, the standard Wire library worked just fine for Master/slave mixed mode.

What has been your experience? How has using mixed mode failed for you?

Chuck.

MorganS:
Even if you were able to call begin() again and drop back to master mode, you lose sync on the bus and you end up with no idea of what's going on on the bus.

Chucktodd thanks for the feedback. I am happy to hear you confirm it works well between two Arduinos. What I am planning on however is to act as master/slave on a bus connecting to a different device altogether (a Vex IQ).

Morgan I never said to call begin() again and drop back to master mode. I simply said call begin with your slave address but still call beginTransmission/endtransmission to act as a master within the loop function.

Below is the code I was talking about it was copied from https://www.microchip.com/forums/m765549.aspx

I am still curious if this completely acceptable or is this a bad thing to do. I've done a bit with I2C and Arduino/NXT but I don't know if there are good reasons to avoid doing this. I wanted to know before I went down this road.

#include <Wire.h>
#define RXLED 17
#define BUTTON 10
#define THIS_ADDRESS 0x9
#define OTHER_ADDRESS 0x8
boolean last_state = HIGH;

void setup() {
    pinMode(RXLED, OUTPUT);
    digitalWrite(RXLED, LOW); //RXLED Off
    pinMode(BUTTON, INPUT);
    Wire.begin(THIS_ADDRESS); //This modules I2C Slave address
    Wire.onReceive(receiveEvent); //Setup a interupt when I2C data arrives
}
void loop() {
    if (digitalRead(BUTTON) != last_state){    //Only when a change in BUTTON occurs
        last_state = digitalRead(BUTTON); //Read and store the BUTTON state
        TXLED1; 'TXLED On Macro
        delay(1000); ' for one second to signal that a button change occurred and a transmission to OTHER_ADRESS begins
        TXLED0; 'TXLED Off Macro
        Wire.beginTransmission(OTHER_ADDRESS); //Setup I2C transmission to OTHER ADDRESS (Be a MASTER?)
        Wire.write(last_state); //Send the byte of data
        Wire.endTransmission(); //End the transmission
    }
}
 
void receiveEvent(int howMany) {  //Here an I2C message arrives. howMany holds the number of bytes
    while (Wire.available() > 0){
        //While bytes are available
        boolean b = Wire.read(); //Read a byte. In this case we know it is a boolean value
        Serial.print(b, DEC); //Send it to the debug port
        digitalWrite(RXLED, !b); //Change the LED to On or Off according to the boolean value.
    }
    Serial.println(); //Send linefeed to debug
    TXLED1; // TXLed on Macro
    delay(100); //for 100ms to signal a message was received.
    TXLED0; //TXLed Off MAcro
}

Denbo:
Chucktodd thanks for the feedback. I am happy to hear you confirm it works well between two Arduinos. What I am planning on however is to act as master/slave on a bus connecting to a different device altogether (a Vex IQ).

Morgan I never said to call begin() again and drop back to master mode. I simply said call begin with your slave address but still call beginTransmission/endtransmission to act as a master within the loop function.

Below is the code I was talking about it was copied from https://www.microchip.com/forums/m765549.aspx

I am still curious if this completely acceptable or is this a bad thing to do. I've done a bit with I2C and Arduino/NXT but I don't know if there are good reasons to avoid doing this. I wanted to know before I went down this road.

#include <Wire.h>

#define RXLED 17
#define BUTTON 10
#define THIS_ADDRESS 0x9
#define OTHER_ADDRESS 0x8
boolean last_state = HIGH;

void setup() {
   pinMode(RXLED, OUTPUT);
   digitalWrite(RXLED, LOW); //RXLED Off
   pinMode(BUTTON, INPUT);
   Wire.begin(THIS_ADDRESS); //This modules I2C Slave address
   Wire.onReceive(receiveEvent); //Setup a interupt when I2C data arrives
}
void loop() {
   if (digitalRead(BUTTON) != last_state){    //Only when a change in BUTTON occurs
       last_state = digitalRead(BUTTON); //Read and store the BUTTON state
       TXLED1; 'TXLED On Macro
       delay(1000); ' for one second to signal that a button change occurred and a transmission to OTHER_ADRESS begins
       TXLED0; 'TXLED Off Macro
       Wire.beginTransmission(OTHER_ADDRESS); //Setup I2C transmission to OTHER ADDRESS (Be a MASTER?)
       Wire.write(last_state); //Send the byte of data
       Wire.endTransmission(); //End the transmission
   }
}

void receiveEvent(int howMany) {  //Here an I2C message arrives. howMany holds the number of bytes
   while (Wire.available() > 0){
       //While bytes are available
       boolean b = Wire.read(); //Read a byte. In this case we know it is a boolean value
       Serial.print(b, DEC); //Send it to the debug port
       digitalWrite(RXLED, !b); //Change the LED to On or Off according to the boolean value.
   }
   Serial.println(); //Send linefeed to debug
   TXLED1; // TXLed on Macro
   delay(100); //for 100ms to signal a message was received.
   TXLED0; //TXLed Off MAcro
}

In the onReceive() function, DO NOT Serial print, or DELAY or anything other than access private variables.

The call to Delay would hang because the delay() function relies on the timer interrupt, Since we are already in an interrupt, NO Other interrupt can happen until this one completes.

The OnReceive() function is an interrupt service routine. It will interrupt any executing code. Think about what would happen if the foreground process was in the middle of a Serial.print() and you tried to do inject another Serial.print() into the middle? Bad Things! None of the Arduino library routines are interrupt safe. So, the only things you can safely do in the onReceive() function is change private variables, atomically access isolated hardware.

Here is how I would rewrite your onReceive() function:

//  The volatile predicate, indicates to the compiler that this value can change in an interrupt.
//  The compiler tries to optimize how it stores and accesses data, sometimes it keeps the current value 
// of a variable in a CPU register, and only updates the RAM location when it needs to reuse the CPU 
// register for something else,  This works great, unless the value is modified by an ISR, if the ISR 
// updates the RAM storage location, but the compiler thinks its register value is 'current' it will overwrite 
// the RAM value, loosing any changes made by the ISR.  

volatile bool receivedData =false; // flag to indicate new data has arrived
volatile bool overRun =false; // flag to indicate the received data was not process before the next
  // next block came in.

volatile byte buffer[32]; // i2c transmissions are limited to a max of 32 byte by the Wire.h library
volatile byte bufLen=0; 

void receiveEvent(int howMany) {  //Here an I2C message arrives. howMany holds the number of bytes
if(!receivedData) { //buffer is clear to use
  bufLen = 0; // start at beginning of buffer
  while (Wire.available() > 0){ 
    buffer[bufLen++]=Wire.read(); // receive data from I2C, inc bufLen to next
    }
  receivedData = true; // mark the buffer as active;
  }
else { // New data came in, but there was already data in the buffer!, an overrun
  overRun = True;
  }
}

void loop(){
  if(receivedData){ // ISR got a block from the other I2C master
    uint8_t index = 0;
    while(index<bufLen){ // walk through buffer
      Serial.print(buf[index], DEC); //Send it to the debug port
      digitalWrite(RXLED, !buf[index++]); //Change the LED to On or Off according to the boolean value.
      }
    Serial.println(); //Send linefeed to debug
    TXLED1; // TXLed on Macro
    delay(100); //for 100ms to signal a message was received.
    TXLED0; //TXLed Off MAcro
    receivedData = false; // buffer can now be reused!
    }
  if(overRun){ // did not service the buffer fast enough!
   Serial.println("Buffer OverRun!");
   overRun = false;
   }

 if (digitalRead(BUTTON) != last_state){    //Only when a change in BUTTON occurs
    last_state = digitalRead(BUTTON); //Read and store the BUTTON state
    TXLED1; //TXLED On Macro
     delay(1000); //for one second to signal that a button change occurred and a transmission to OTHER_ADRESS begins
    TXLED0; //TXLED Off Macro
     Wire.beginTransmission(OTHER_ADDRESS); //Setup I2C transmission to OTHER ADDRESS (Be a MASTER?)
     Wire.write(last_state); //Send the byte of data
     uint8_t err =Wire.endTransmission(); //End the transmission
      /*

    0:success
    1:data too long to fit in transmit buffer
    2:received NACK on transmit of address
    3:received NACK on transmit of data
    4:other error 
*/
     If(err){ // not Zero
      if(err==1) Serial.println(" Databuffer too Long!"); //kinda impossible because you are only sending one byte?
      if(err==2) Serial.println(" Slave did not Answer!, Address NACK");
      if(err==3) Serial.println(" Slave did not accept data? busy? ");
      if(err==4) Serial.println(" I2C net was busy, Other master has not released bus");
      }

    }
}
 
}

I did not compile this code, there may be syntax errors or open {}, But, you should get the gist.

Chuck.

Chuck,

Just FYI that isn't my code. I pasted it from the link (a microchip forum post).

I know full well not to call Serial.println or anything else that gens an interrupt within an ISR.

Denbo:
Chuck,

Just FYI that isn't my code. I pasted it from the link (a microchip forum post).

I know full well not to call Serial.println or anything else that gens an interrupt within an ISR.

Sorry for the abuse.

Based on your decision to post it, I assumed you believed it was an acceptable example of how to code an ISR.

My action was to disabuse you of this belief.

If you have any other question, post them.

Chuck.