Friday, December 9, 2016

Caveat: multimaster I2C on 32-bit Arduino's

For a new project I'm working on, it was necessary to allow for bidirectional communication on a shared I2C bus between a bunch of Arduino's (more specifically, a healthy mix of Arduino MKR1000's and Adafruit Feather M0's). After some quick researching, I found a few easy looking examples on how to use some mixture of master/slave settings in a "multimaster" mode with Arduino's Wire library (the TwoWire/I2C implementation). You can read them here, here and here, and they basically all do the following "trick" during initialization:
 Wire.begin(MY_ADDRESS);
 Wire.onReceive(receiveI2C);
and then for sending data, they use:
 Wire.beginTransmission(DEST_ADDRESS);
 Wire.write(0x30);
 Wire.endTransmission();
So what's happening here?

  • Basically, if you use Wire.begin(MY_ADDRESS) you are initializing the I2C bus in slave mode, and by registering the "onReceive" interrupt service handler, you define what needs to happen when the slave receives data. 
  • Next up, these examples all start sending data as a MASTER (despite being configured as a slave) by initializing the transmission, writing the data and then putting the data on the bus with the endTransmission() call. 
This works on 8-bit AVR Arduino's (and a bunch of other devices) because of how the Wire library is implemented on these devices. Just have a look at the following files (from your Arduino IDE installation folder):
  • hardware\arduino\avr\libraries\Wire\src\Wire.cpp
  • hardware\arduino\avr\libraries\Wire\src\utility\twi.c
You can see endTransmission() calls the twi_WriteTo() function from twi.c (yes, Wire is merely a wrapper around some C implementation of I2C). What does twi_WriteTo() do? It attempts to become the master on the I2C bus and then sends data.  That's actually a very good way of combining master & slave functionality on a single device on the I2C bus, and most definitely the way to go.

Now cue the Wire implementation on the 32-bit Atmel processors. These use the hardware SERCOM's of the SAMD21 processors, and directly use the corresponding I2C (well.. SERCOM) hardware registers to put the MCU in I2C master or slave mode... Unfortunately, there is no corresponding temporary promotion to a master going on in this implementation. Let's have a look at the SAMD21 implementation of Wire, which you can find in your %LOCALAPPDATA%\Arduino15\packages\arduino\hardware\samd\1.6.8\libraries\Wire folder:
  • The endTransmission() implementation directly calls SERCOM->startTransmissionWIRE(), which is from the the %LOCALAPPDATA%\Arduino15\packages\arduino\hardware\samd\1.6.8\cores\arduino\SERCOM.cpp file.
  • That startTransmissionWIRE() function first checks if the I2C bus is idle (multimaster collision protection) or whether it already owns the I2C bus using:

    while ( !isBusIdleWIRE() && !isBusOwnerWIRE() );
Unfortunately, both these functions assume the SERCOM was initialized as a master, and since the Wire.begin() was called with a slave address and hence the entire MCU was initialized as a slave. I haven't worked out the details yet but you'll see that the sketch remains stuck in this while loop because the conditions are never met.

This is one of the many differences between AVR & SAMD based Arduino's that you'll encounter when diving into the details. The solution is to do extend your code and do your own proper promotion towards I2C master when you need to send data, and depromote yourself to slave in case you don't need to send anything... basically mimicing the behaviour of the original twi.c that all started it.


Friday, September 16, 2016

MKR1000 support in the Arduino IDE

I started experimenting with the Arduino/Genuino MKR1000 board that I received today for another IoT project that we're working on at ThingTank. There are a few caveats when trying to access the board that you might run into:

  • First of all, be sure to use the Arduino.cc IDE and not the Arduino.org IDE. At the time of writing you can distinguish them in the version number; you'll need the 1.6.x releases and not the 1.7.x releases. It might seem like you're using an "older" version but that is not the case; they are just different branches of a very similar IDE.

    The biggest difference is that the Arduino.org IDE at the moment does not have the simple "Board Manager" user interface to enable other boards than the standard Atmel AVR boards.
  • Then, use the "Board Manager" to install the "Arduino SAMD" toolchain. When starting the board manager, I received a "package_index.json file signature verification failed" error message.

    Not sure what caused this (remnant of my previous 1.7.x Arduino IDE installation?), but I had a remnant package_index.json.sig file in the Arduino AppData folder (%APPDATA%/Local/Arduino15/). After deleting that file, I was able to search and install in the Board Manager correctly.
After installation of the Arduino SAMD Boards support, you can now select the MKR1000 as one of the boards in the IDE and start fiddling around! Hurray!