Overview

The MCP23008 and MCP23017 family of chips provide an easy way to add extra digital inputs and outputs to your development board.  These chips are controlled with an I2C connection and add 8 or 16 extra digital pins that can act as outputs or inputs (even with optional pull-up resistors).  This is great for connecting more digital devices to your board when you run out of native digital I/O!

This guide explores how to use the MCP23008 and MCP23017 with CircuitPython.  You'll learn how to connect the chip to a CircuitPython board, load an Adafruit MCP230xx module, and control the I/O pins of the chip from Python code!

Hardware

Hardware

To follow this guide you'll need the following parts:

Wiring

Connect your MCP230xx to your development board using a standard I2C connection.  Here's an example of wiring a MCP23017 to a Feather M0 board:

Remember you need to explicitly add pull-up resistors to the I2C SCL and SDA connections as shown above!

  • Board 3.3V output to MCP23017 Vdd
  • Board ground/GND to MCP23017 Vss
  • Board SCL to MCP23017 SCL
  • Board SDA to MCP23017 SDA
  • MCP23017 SCL to 4.7 kilo-ohm resistor connected to 3.3V
  • MCP23017 SDA to 4.7 kilo-ohm resistor connected to 3.3V
  • MCP23017 A0 to ground
  • MCP23017 A1 to ground
  • MCP23017 A2 to ground
  • MCP23017 reset to 3.3V

When in doubt consult the MCP23017 datasheet for exact details on its pins and wiring.

Here's an example of wiring a MCP23008 to a Feather M0 board:

Remember you need to explicitly add pull-up resistors to the I2C SCL and SDA connections as shown above!

  • Board 3.3V output to MCP23008 Vdd
  • Board ground/GND to MCP23008 Vss
  • Board SCL to MCP23008 SCL
  • Board SDA to MCP23008 SDA
  • MCP23008 SCL to 4.7 kilo-ohm resistor connected to 3.3V
  • MCP23008 SDA to 4.7 kilo-ohm resistor connected to 3.3V
  • MCP23008 A0 to ground
  • MCP23008 A1 to ground
  • MCP23008 A2 to ground
  • MCP23008 reset to 3.3V

When in doubt consult the MCP23008 datasheet for exact details on its pins and wiring.

Software

Installing Library

To use the MCP230xx you'll need to install the Adafruit CircuitPython MCP230xx library on your CircuitPython board.

First make sure you are running the latest version of Adafruit CircuitPython for your board.

Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle.  Our introduction guide has a great page on how to install the library bundle for both express and non-express boards.

Remember for non-express boards like the, you'll need to manually install the necessary libraries from the bundle:

  • adafruit_mcp230xx.mpy
  • adafruit_bus_device

You can also download the adafruit_mcp230xx.mpy from its releases page on Github.

Before continuing make sure your board's lib folder or root filesystem has the adafruit_mcp230xx.mpy, and adafruit_bus_device files and folders copied over.

Next connect to the board's serial REPL so you are at the CircuitPython >>> prompt.

Usage

To demonstrate the usage of the device we'll initialize it and control the chip's digital I/O lines from the Python REPL.

Run the following code to import the necessary modules and initialize the I2C connection:

import board
import busio
import adafruit_mcp230xx
i2c = busio.I2C(board.SCL, board.SDA)

Remember if you're using a board that doesn't support hardware I2C (like the ESP8266) you need to use the bitbangio module instead:

import board
import bitbangio
import adafruit_mcp230xx
i2c = bitbangio.I2C(board.SCL, board.SDA)

Then create an instance of either the MCP23017 or MCP23008 class depending on which chip you're using.  For example the MCP23017:

mcp = adafruit_mcp230xx.MCP23017(i2c)

Or the MCP23008:

mcp = adafruit_mcp230xx.MCP23008(i2c)

By default the chip's 0x20 I2C address will be assumed (with A0, A1, A2 all grounded). But either class initializer can also be passed an optional address keyword argument to override the I2C address, if you set any of the A0, A1, or A2 pins as described in the datasheet.   For example:

mcp = adafruit_mcp230xx.MCP23008(i2c, address=0x24)

Next you're ready to control the GPIO ports of the chip.  This is as easy as using a DigitalInOut instance in CircuitPython, the MCP230xx library makes each chip pin look like CircuitPython DigitalInOut class.  You just need to call the get_pin function to retrieve an instance of the chip's DigitalInOut class. 

For example to create GPIO0 (or GPIOA0 on the MCP23017) as a digital output:

pin0 = mcp.get_pin(0)
pin0.switch_to_output()

Then toggle the pin high or low by controlling its value property, just like using the DigitalInOut class.  Try connecting the cathode or anode of a LED to the output to see it turn on or off (or use a multimeter to measure the output voltage).

pin0.value = True  # GPIO0 / GPIOA0 to high logic level
pin0.value = False # GPIO0 / GPIOA0 to low logic level

You can also set a pin as an input, again using the same functions and properties as the DigitalInOut class.  For example here's how to create GPIO1 / GPIOA1 as a digital input with a pull-up resistor enabled:

import digitalio
pin1 = mcp.get_pin(1)
pin1.direction = digitalio.Direction.INPUT
pin1.pull = digitalio.Pull.UP

Note that pull-down resistors are not supported by the chip.  If you need them you'll have to add external resistors yourself!

Again read the state of the input with the value property like a DigitalInOut instance.  Notice since the pull-up resistor is enabled if the pin is not connected it will read True / high logic level.  Connect the pin to ground and you'll see it read False / low logic level.

pin1.value
pin1.value

For the MCP23008 you can get a pin instance for any pin numbered 0 to 7.  These correspond to the GPIO0 to GPIO7 pins of the chip.

For the MCP23017 you can get a pin instance for any pin numbered 0 to 15.  These correspond to the GPIOA0 to GPIOA7, then GPIOB0 to GPIOB7 pins.  For example pin 12 corresponds to GPIOB4 on the MCP23017.

That's all there is to using the MCP230xx I2C I/O extender with CircuitPython!

Below is a complete example that will blink pin 0 and read the state of pin 1 with a pull-up resistor enabled.  Save this as main.py on your board (being careful to switch to bitbangio if required for your board), then open the serial REPL to see the output:

# Simple demo of reading and writing the digital I/O of the MCP2300xx as if
# they were native CircuitPython digital inputs/outputs.
# Author: Tony DiCola
import time

import board
import busio
import digitalio

import adafruit_mcp230xx


# Initialize the I2C bus:
i2c = busio.I2C(board.SCL, board.SDA)

# Create an instance of either the MCP23008 or MCP23017 class depending on
# which chip you're using:
mcp = adafruit_mcp230xx.MCP23008(i2c)  # MCP23008
#mcp = adafruit_mcp230xx.MCP23017(i2c)  # MCP23017

# Optionally change the address of the device if you set any of the A0, A1, A2
# pins.  Specify the new address with a keyword parameter:
#mcp = adafruit_mcp230xx.MCP23017(i2c, address=0x21)  # MCP23017 w/ A0 set

# Now call the get_pin function to get an instance of a pin on the chip.
# This instance will act just like a digitalio.DigitalInOut class instance
# and has all the same properties and methods (except you can't set pull-down
# resistors, only pull-up!).  For the MCP23008 you specify a pin number from 0
# to 7 for the GP0...GP7 pins.  For the MCP23017 you specify a pin number from
# 0 to 15 for the GPIOA0...GPIOA7, GPIOB0...GPIOB7 pins (i.e. pin 12 is GPIOB4).
pin0 = mcp.get_pin(0)
pin1 = mcp.get_pin(1)

# Setup pin0 as an output that's at a high logic level.
pin0.switch_to_output(value=True)

# Setup pin1 as an input with a pull-up resistor enabled.  Notice you can also
# use properties to change this state.
pin1.direction = digitalio.Direction.INPUT
pin1.pull = digitalio.Pull.UP

# Now loop blinking the pin 0 output and reading the state of pin 1 input.
while True:
    # Blink pin 0 on and then off.
    pin0.value = True
    time.sleep(0.5)
    pin0.value = False
    time.sleep(0.5)
    # Read pin 1 and print its state.
    print('Pin 1 is at a high level: {0}'.format(pin1.value))
This guide was first published on Mar 08, 2018. It was last updated on Sep 21, 2018.