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.

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, this approach may save you if in need of more ports and you have more than one Arduino in your parts bin.

The connection diagram. The pull-up resistors shown are optional.

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.
 

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
}


15 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
  7. ... and can you use the standard Keypad Library for remotely attached keypad with remote pin numbers (like 102,103,104,.. 109 for example)?

    ReplyDelete
  8. I made a quick and dirty "Keypad Server" using the I2C communication. Keypad is "local" to slave machine, hence uses the "normal" Keypad library. It continuously scans the keypad and buffers the keystrokes, if any. When the Master asks for keypad data, it writes the buffer to master. I understand I2C version of Keypad library is also available, but this looked simple enough to implement it, and it nicely worked.

    ReplyDelete
  9. Thanks for this. Its exactly what I needed to get a couple more relay control pins into my project. You can buy a RobotDyn Pro Mini Arduino for $1.25 online meanwhile the I2C io expander modules are $2+ (PCF8574) and have less capability. Plus, I have arduinos on-hand rather than waiting for shipping.

    ReplyDelete
  10. Hi Thanks for this great job.I used two atmega32 to link together but no chance.Could you help me in source code in c please .Because these codes is not recognized in code vision.Is the program for slave and master different? and both must be programmed?

    ReplyDelete
    Replies
    1. Yes, the code for master and slave are different and both MUST be programmed so they can speak the same language and communicate between each other.

      Delete