IO Port Expander (MCP23017 and MCP23008)

Submitted by Evan Boldt on

The MCP23017 and MCP23008 integrated circuits are a great way to add more I/O pins to a microcontroller. They use the i2c standard, so they can share the same serial line with 254 other sensors and even up to 8 other chips of the same exact type.  They are particularly good for a Raspberry Pi because they have higher current capabilities than the Raspberry Pi's GPIO pins. The MCP can supply 25mA per pin and the Raspberry Pi can only do less than 16mA per pin. 20mA is enough to fully power a strong LED, so 16mA may not be enough in some cases. More importantly, through the use of a relatively cheap logic level shifter, many 5V I/O pins can be added to a single serial channel despite the fact that the Raspbery Pi uses 3.3V GPIO and serial.

MCP23017 (16 I/O pin)
GPB0• 128GPA7
GPB12The MCP23017 I/O port expander adds input and output pins over i2c serial.27GPA6
GPB2326GPA5
GPB3425GPA4
GPB4524GPA3
GPB5623GPA2
GPB6722GPA1
GPB7821GPA0
VIN920INTA
GND1019INTB
  1118RESET
SCL1217ADDR2
SDA1316ADDR1
  1415ADDR0

  

 

MCP23008 (8 I/O pin)
SCL• 118VIN
SDA2             17GP7
ADDR2316GP6
ADDR1415GP5
ADDR0514GP4
RESET613GP3
  712GP2
INT811GP1
GND910GP0

*The bold pins are required. In particular, it is important to set the RESET pin to high.

GPA#, GPB#, GP# are the GPIO pins. On the 16 pin version, there are two ports denoted by the A and B (PORTA, PORTB).

INTA, INTB, INT are the interrupt outputs that can be monitored by the microcontroller to notify it when inputs change. On the 16 pin variant, there is one for each GPIO port.

i2c Address
0100ADDR2ADDR1ADDR0Hex Addr.Dec. Addr.
01000000x2032
01000010x2133
01000100x2234
01000110x2335
01001000x2436
01001010x2537
01001100x2638
01001110x2739

The ADDR# pins can be set to high or low to change the address by 1 or 0 in the style in the address table allowing for a possible total of 8 different addresses, and therefore allowing up to 8 chips total. Both the 16 GPIO and 8 GPIO pin variants start at 0x20, so mixing the type will not help get more. If you are using more than 128 pins, you might want to consider alternatives like shift registers.

The RESET pin can be used to clear the registers and set it back to its power-on state, but really that isn't very useful since you can always use serail commands. So, usually you will just wire it directly to HIGH (3.3v or 5v) so that it always stay on.

 Arduino

There is an Arduino library made available by adafruit on GitHub. Usage is very simple:

#include <Wire.h>
#include "Adafruit_MCP23017.h"

Adafruit_MCP23017 mcp;

# define BLINKPIN 0
# define INPUTPIN 1
# define ECHOPIN 13

// blinks MCP pin 0 on and off
// turns MCP pin 13 on when MCP pin 1 is high or unplugged

void setup() {  
  mcp.begin();      // use default address 0x20 (0,0,0)

  mcp.pinMode(BLINKPIN, OUTPUT);

  mcp.pinMode(INPUTPIN, INPUT);
  mcp.pullUp(INPUTPIN, HIGH);  // turn on a 100K pullup internally
}

void loop() {
  delay(100);
  mcp.digitalWrite(BLINKPIN, HIGH);
  delay(100);
  mcp.digitalWrite(BLINKPIN, LOW);

  // ECHO only updates every 200 ms
  digitalWrite(ECHOPIN, mcp.digitalRead(INPUTPIN));
}

The major problem with the above code is that the ECHO is not updated until the delays are done. To get by that limitation, something like Metro or Kernel could be used, or simply checking the time yourself. This example was built just to show the bare minimum of how to interface with the MCP, not to show a practical application though.

Raspberry Pi

Adafruit also provies a Python library for the Raspberry Pi, which is also easy to use. 

from Adafruit_MCP230XX import *

# Use busnum = 0 for older Raspberry Pi's (256MB)
# Use busnum = 1 for new Raspberry Pi's (512MB with mounting holes)
mcp = Adafruit_MCP230XX(busnum = 1, address = 0x20, num_gpios = 16)

# Set pins 0, 1 and 2 to output (you can set pins 0..15 this way)
mcp.config(0, mcp.OUTPUT)
mcp.config(1, mcp.OUTPUT)
mcp.config(2, mcp.OUTPUT)
 
# Set pin 3 to input with the pullup resistor enabled
mcp.pullup(3, 1)
# Read pin 3 and display the results
#bitshift output, third bit is the output
print "%d: %x" % (3, mcp.input(3) >> 3) 
 
# Python speed test on output 0 toggling at max speed
while (True):
  mcp.output(0, 1)  # Pin 0 High
  mcp.output(0, 0)  # Pin 1 Low