I2C Port expanders - 8574, 8574A, 8575, 9555 etc

If you just don't have enough digital I/O pins on your Arduino to interface with all your sensors and controls, you might want to look at using the I2C bus to connect a few port expander chips:

  • The 8574 and 8574A (TI and NXP) have 8 digital I/O bits and can be set to addresses 0x20-0x27 and 0x38-0x3F respectively. Cost is about $1.00
  • The 8575 (TI and NXP) has 16 digital I/O bits at addresses 0x20-0x27. Cost is about $1.10
  • The MCP23016 (Microchip) and PCA9555 (NXP) have 16 bits of digital I/O at adresses 0x20-0x27 (inexpensive at $1.30, but are more complicated to use)
  • The PCA9698 from Phillips has 40 bits of digital I/O, can be addressed at 64 different addresses, is extremely powerful, but is a $4.00 56-pin surface-mount part.

Common among these chips is the feature that any of their digital I/O ports can be configured as an input or output, and that many (usually up to 8 of each type) can be used at the same time on the same bus. Using both 8574 and 8574A parts, you could have 16 independently addressable devices with 8 bits of I/O each - giving you a total of 128 additional I/O lines.

The I2C bus on the Arduino uses Analog pins 4 and 5, a couple of pull up resistors and the Wire library. The I2C bus is widely documented; I find http://www.robot-electronics.co.uk/htm/using_the_i2c_bus.htm to be a typically useful writeup. If you want to have more fun with I2C, check out the BlinkM (http://blinkm.thingm.com/) - a 3-color LED controlled via I2C.

Since the Wire library does almost all the work for you, there is very little to do in this example that uses one 8574A as an 8-input device and another as an 8-output. The code sample includes versions of the routines (unused here) to do the same for 16 bit PCA9555's.

sample8574A.pde

/*
 * A4 is SDA
 * A5 is SCL
 * Both need pull-up resistors.
 * The value of the resistors is not critical. 
 * Anything from 1k8 (1800 ohms) to 47k (47000 ohms)
 * should work; 1k8, 4k7 and 10k are common values.  Start
 * with 1k8 as this gives you the best performance. If
 * the resistors are missing, the SCL and SDA lines will
 * always be low - nearly 0 volts - and the I2C bus will
 * not work. 
 *
 * Connect a LED between Arduino pin D13 and Gnd so we can
 * show activity, and connect pin 13 (/INT) of the 8574 to
 * pin D12 on the Arduino.  We use the /INT signal to tell
 * us when to read the data from the 8574.
 * (In a real application, you would probably want to
 * connect /INT to an Arduino interrupt (and associated
 * ISR routine) to avoid the polling loop...)
 */

#include <Wire.h>
// 8574  Address range is 0x20-0x27
// 8574A Address range is 0x38-0x3F
// 9555  Address range is 0x20-0x27 (same as 8574, bummer)

#define INaddr 0x38  // 8574A addr 000
#define OUTaddr 0x39 // 8574A addr 001

#define NXP_INPUT      (0)  // For NXP9555
#define NXP_OUTPUT     (2)  // See data sheet
#define NXP_INVERT     (4)  // for details...
#define NXP_CONFIG     (6)

void setup()
{
  pinMode(12, INPUT);  // to read /INT
  pinMode(13, OUTPUT); // to show we are working
  Wire.begin();      
  expanderSetInput(INaddr, 0xFF);
} 

// I2C routines to talk to 8574 and 8574A
void expanderSetInput(int i2caddr, byte dir) {
  Wire.beginTransmission(INaddr);
  Wire.send(dir);  // outputs high for input
  Wire.endTransmission();    
}

byte expanderRead(int i2caddr) {
  int _data = -1;
  Wire.requestFrom(i2caddr, 1);
  if(Wire.available()) {
    _data = Wire.receive();
  }
  return _data;
}

void expanderWrite(int i2caddr, byte data)
{
  Wire.beginTransmission(i2caddr);
  Wire.send(data);
  Wire.endTransmission();   
}

// I2C routines to talk to a PCA9555
void expanderSetInput16(int i2caddr, int dir) {
  Wire.beginTransmission(i2caddr);
  Wire.send(NXP_CONFIG);
  Wire.send(0xff & dir);  // low byte
  Wire.send(dir >> 8);    // high byte
  Wire.endTransmission();  
}

void expanderWrite16(int i2caddr, int data) {
  Wire.beginTransmission(i2caddr);
  Wire.send(NXP_OUTPUT);
  Wire.send(0xff & data);  //  low byte
  Wire.send(data >> 8);    //  high byte
  Wire.endTransmission();  
}

int expanderRead16(int i2caddr) {
  int _data = 0;
  Wire.beginTransmission(i2caddr);
  Wire.send(NXP_INPUT);
  Wire.endTransmission();  
  Wire.requestFrom(i2caddr, 2);
  if(Wire.available()) {
    _data = Wire.receive();
  }
  if(Wire.available()) {
    _data |= (Wire.receive() << 8);
  }
  return _data;
}


void loop()
{
  while (digitalRead(12) == 1) { /* wait for /INT to go low */ }
  digitalWrite(13, 1);
  delay(50);  // Flash my bling
  digitalWrite(13, 0);

  int data = expanderRead(INaddr);
  if (data != -1) {
      expanderWrite(OUTaddr, (byte)data);
  } else {
      for (int x = 0; x <= 8; x++) { // flash error pattern
        digitalWrite(13, 1); delay(100); 
        digitalWrite(13, 0); delay(400); 
        digitalWrite(13, 1); delay(400); 
        digitalWrite(13, 0); delay(100); 
      }
  }
}

Share