Connecting two Arduinos (unos) with I2C. My findings.

A few weeks back I was asking about using I2C to send commands/data between two Arduinos, specifically using the "Wire.onRequest" method. HERE.. I was a bit worried about exactly when and how it "grabbed" my data if it was called asynchronously to the main loop.

Anyway I ended up not using onRequest(), but instead just used onReceive() at both ends. Since I2C is multimaster this works fine and I found it a little more consistent to "go the same way" at either end.

I'm just posting here to share some of the things I found from testing the details. There is kind of some questions buried in here, as in some things that didn't make total sense to me, but it's mostly about sharing my findings.

Since there is a fair bit of information here I'll split it over a couple of posts.

1 .Testing Wire.onReceive()

The calls to the onReceive are definitely asynchronous to the main loop. The data being received is volatile in the main loop (and all other user functions). So you do have to be careful if your code is sensitive to the received data changing unexpectedly mid algorithm.

I tested this by making my main loop rather slow, and then testing the value of the (volatile) global variable that was taking my received data at the beginning and again at the end of the loop to see if it had changed (without my loop coding modifying it). It changed mid loop even when the main loop itself made no system calls, for example even if the loop did nothing but a tight for-loop.

BTW. When I didn't declare that global variable as volatile, it didn't actually change in the main loop, but I wouldn't necessarily want to rely on that behaviour.

All bytes (sent in the one Wire.beginTransmission and Wire.endTransmission operation on the master) are received in the one call to "onReceive". So I didn't have worried about things like integers getting split and received at different times.

  1. Polling Wire.available for data.

I tried polling for data in the same way as we often do with Serial.available (instead of using the asynchronous onReceive), but it didn't work with Wire.available. Despite data being sent to the slave devices, Wire.available never became true in the main loop.

So I tried including an onReceive() function in the normal way, but leaving it completely empty, and suddenly Wire.available did start to work. I could now do things like "if (Wire.available) myData=Wire.read()". There is a catch though...

If however, instead of polling as above, I tried blocking code like "while(!Wire.available());" then it was a complete fail. Once again it never became available.

To be honest I don't know why this happens. At first I thought yeah, that means that the received data IS synchronized to the main loop somehow (and hence my blocking code was preventing Wire.available happening). But in other tests I did the data was DEFINITELY received asynchronously (received data values would change in the main loop without my loop code changing them.)

So I'm kind of stumped as to why "if(Wire.available)" works but waiting with "while(!Wire.available)" doesn't.

  1. An alternate method of polling that does work.

I then tried one final method of polling for data (instead of letting onReceive change it asynchronously), and this method did work with both "if(available)" and "while(!available)" type of code.

This time, instead of making my onReceive routine completely empty, it did nothing except set a global (volatile) boolean variable to let the main loop know that data was available.

boolean volatile dataReadyFlag = false;
...
Wire.onReceive(myReceive);
...
void myReceive(int n) {
  dataReadyFlag = true;
}

No idea why this worked when Wire.available() didn't. But now using this new boolean variable I could do either of the above types of "polling" for data in the main loop and this works perfectly.

Anyway, I just thought this could be a valid alternative if you're worried about onReceive changing things mid algorithm.