Robotic Controls — printer-friendly version of IO Port Expander (MCP23017 and MCP23008)

IO Port Expander (MCP23017 and MCP23008)

Evan Boldt, August 16, 2013

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↔• 1◡28↔GPA7
GPB1↔2The MCP23017 I/O port expander adds input and output pins over i2c serial.27↔GPA6
GPB2↔326↔GPA5
GPB3↔425↔GPA4
GPB4↔524↔GPA3
GPB5↔623↔GPA2
GPB6↔722↔GPA1
GPB7↔821↔GPA0
VIN→920→INTA
GND→1019→INTB
  1118←RESET
SCL→1217←ADDR2
SDA↔1316←ADDR1
  1415←ADDR0

  

 

MCP23008 (8 I/O pin)
SCL↔• 1◡18←VIN
SDA↔2             17↔GP7
ADDR2→316↔GP6
ADDR1→415↔GP5
ADDR0→514↔GP4
RESET→613↔GP3
  712↔GP2
INT←811↔GP1
GND→910↔GP0

*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