Sunday, March 21, 2010

Extremely simple I/O port expansion using two arduinos and I2C

Here's a simple way to expand your I/O ports using two Atmega microcontrollers.

I like this approach better than going for an Arduino Mega because you get to use all your smaller shields at the time it's a more affordable solution. It works flawlessly in my projects, even with other I2C devices hooked up at the same time. This may not be as cheap as using a multiplexer, shift registers, a MuxShield or so many other ways of doing this but hey, it's dead simple and will definitely save you if in need of more ports and you have more than one Atmega328 lying around.

The connection diagram: two Atmel microcontrollers will be less expensive than two actual Arduino boards, just like the one in the video.

The connection is the same as what you would do with a standard I2C between two arduinos. I added pull-up resistors to each wire.

Basically what this does is to allow the master Arduino to access the remote's I/O pins, thus, expanding its original I/O capacity. You can hook up many Arduinos in parallel expanding to more I/O pins as needed.

Needless to say, this also duplicates you processing capabilities at the time it allows you to add software watchdogs for a crash-self-recover system.

You don't really need to use two Arduinos: You can also use a barebones ATmegas just like I did in the video tutorial if you want to keep your costs low..



For the code, I started with "Wire Slave Receiver" by Nicholas Zambetti and modified it to this purpose:


The code at the Master Arduino:


#include <Wire.h>

#define LOCAL_PIN_TWO 2      // Pin 2 on local
#define REMOTE_PIN_TWO 102          // Pin 2 on remote
#define TEMPERATURE_PROBE_PIN 103   // Pin 3 on remote
#define SPEAKER_PIN 104         // Pin 4 on remote


void setup()
{
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop()
{
// Whenever you want to write to the other Arduino just go with something such as expansionWrite(102,HIGH); 
// That will -for example- set the pin 2 on the second arduino to high. I used the 102 nomenclature only to differentiate the two boards. It avoids confusion. You will see how the exceeding 100 is substracted when the time comes.
}

void expansionWrite(int pin, int value)
{
  pin = pin-100;  // substracts 100 so it maps to the real ports on the expansion arduino
  Wire.beginTransmission(2); // transmit to device #2
  Wire.send(pin);              // sends one byte stating the pin to be addressed
  Wire.send(value);          // sends the value to be transmitted to the pin selected
  Wire.endTransmission();    // stop transmitting
}


The code at the Slave Arduino:

#include <Wire.h>

void setup()
{
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onReceive(receiveEvent); // register event
}

void loop()
{
    // Whatever. maybe nothing.
}


void receiveEvent(int howMany)
{
  int port = Wire.receive();    // receive byte as an integer
  int value = Wire.receive();   // receives the byte with the value
  digitalWrite(port,value);         // sets the pin to the desired value
}


Final Note: You can even use the second arduino for READING at the same time even it's a slave machine. The only thing you need to do is add some extra code as follows:

Master requests the value on a certain pin located at the slave Arduino:


  Wire.beginTransmission(2); // transmit to device #2
  Wire.requestFrom(2, 1); // Requests 1 byte from device #2
  remote_pin_status = Wire.receive();
  Wire.endTransmission();    // stop transmitting

Then the slave needs something like this:

void setup()
{
    Wire.onRequest(requestEvent); // register event
}

and somewhere else in your code something like this:

void requestEvent()
{
  byte variable_name = digitalRead(pin_of_your_choice);
  Wire.send(variable_name); // respond with message of 1 byte as expected from Master
}


9 comments:

  1. This is exactly what I was looking for, Thanks for sharing!

    I will post a link to my project once done.

    ReplyDelete
  2. Hey shift registers are not that simple to use. I ended up spending hours to make them work. This approach worked immediately for me THANK YOU THANK YOU!!!

    ReplyDelete
  3. I did this with a single AVR chip and a resonator. It worked like a charm and costed me 5 dollars. Great post!

    ReplyDelete
  4. Hey this is so cool thanks. It takes a second for them to sync but then it's super fast. Way easier than using shift registers with arduino that's a pain compared to your method.

    ReplyDelete
  5. What are the resistor pullup values?

    Thank you.

    ReplyDelete
    Replies
    1. 1.5k Ohm should work. Instead of these you can activate the internal pullup resistors by adding: digitalWrite(A4, HIGH); and digitalWrite(A5, HIGH); although I believe the TWI library activates the internal pullup resistors for you.

      Delete
  6. You can provide the layout for the second arduino?

    ReplyDelete
    Replies
    1. Here is a simple schematic on how to hook up an ATMega 328: http://fritzing.org/projects/barebones-arduino/

      Delete