I have been building "connected sensors" for a couple years but focused initially on the "sensor" part and only recently moved to "connected". Each time I explore a new technology for connectivity - WiFi, Cellular, Bluetooth, LoRa, etc. - I run into some of the limitations of Arduino particularly its limited working memory. These issues stem from two sources when I start to connect my sensors: 1) Working with long variables or keys and 2) String manipulation such as parsing a 64-bit GSMloc string to extract time or location from a nearby cell tower.
Before arriving at this multi-master solution, I had tried all the usual suspects: using non-Atmel varieties such as the Teensy, moving static variables into program memory and using the F( ) argument in print statements. I have also started putting TI's FRAM chips on my boards as I don't like the limited life of Arduino's EEPROM (100,000 writes).
This article describes another approach, using more than one microcontroller to manage the functions of my board. These microcontrollers would share access to and communicate over a common I2C bus. I wanted to use the standard Arduino Wire Library, and I was encouraged to see this library claimed multi-master support. However, there is a big catch as described by wayneft in this thread on Arduino.cc: Arduino's multi-master support can arbitrate between masters vying to take initial control or the bus, but it can mistakenly release the bus before a master is done with it. This can lead to the bus and your programs locking up.Avoiding Collisions and Race Conditions
I developed a simple solution to this problem that, along with the multi-master support that Wire does provide, allows for robust shared control of the I2C bus. I added lines to connect two sets of I/O pins on my microcontrollers: Busy and Clock. The Clock line is the 32kbs square wave that is output by my real time clock. Both the Busy and Clock lines are pulled high using 4.7k ohm pull-up resistors. This allows us to arbitrate control of the bus as each microcontroller needs to "claim" the bus using the Busy line and the Clock line ensures that they don't both claim the bus at the same moment.
Here is how it works with sample code snippets provided below:
- If the Arduino wants to read or write to the I2C bus it first needs to check if the Busy line is High (this indicates that the bus is currently free). Then, it needs to check the Clock line and can only pull the Busy line Low when the Clock line is Low.
- If the Simblee wants to read or write to the I2C bus it first needs to check if the Busy line is High (this indicates that the bus is currently free). Then, it needs to check the Clock line and can only pull the Busy line Low when the Clock line is High.
- If the Simblee or Arduino want access to the bus and finds it Low, they need to wait.
- I implemented this in code by creating two functions:
GiveUpTheBus();. Each returns a boolean so you can write your code to wait for a successful attempt before proceeding.
This type of communications, where a pull-up resistor holds the line high and the only time a microcontroller "writes" to the bus is to pull it low, should prevent situations which would damage either of the processors.Compared to SPI
You may be saying - "I2C is also known as the Two Wire Interface. This approach uses four, that is no better than SPI!". True, but consider these points:
- I2C sensors are generally more common and cheaper
- the four wire requirement is only between the two masters
- I2C sensors can communicate over a longer distance from your board
With this approach, you can effectively split the work between the two microcontrollers. I had the Arduino focused on servicing interrupts from the sensors and logging data to the FRAM. The Simblee was able to read the data from that FRAM and serve it to the application on the phone using Bluetooth LE. Each sketch became shorter and easier to debug and the pressures on the Arduino's memory were reduced. I plan to document the board design in a subsequent post.
I hope you found this article helpful.