MCP23017 Port Expanders

Date: 2019-08-19 01:15:53

<<Previous | TOC | Next>>

ChessLR progress report 4. I got two MCP23017 port expanders working on my Raspberry Pi. This took while, because it refused to work until I eventually figured out that the negative rail on my breadboard wasn't connected. The frustration wasn't bad though, since it while searching for a reason it wasn't working lead me to another site that had examples of how to use the command line to test the I2C bus for connected devices, as well as be able to send data to it. Using the command line allows you to do quick tests rather than writing an entire program for it. In my test below I hooked up two MCP23017's and had them control some LEDs and detect when I pressed a button.

I found the site that talked about the I2C command line utils, but then went on to talk about writing code in Python. So it's usefulness to me was limited, as I am using Java and Pi4J. It also didn't talk about where the author found out that register 0x14 was the output register for the MCP23017. I later found it on the datasheet.

So let's dive into it. Hooking up two MCP23017 chips to a Raspberry Pi 3B, a few LEDs, and a button for input. I wanted to test having two devices, as well as controlling the input and output of each port of at least one of the chips. This should give me a good feel for how to work the chip.


I am driving one MCP23017 on address 0x20 and the other on 0x21. You change the address the MCP23017 operates on by hooking A0-A2 (pins 15,16,17) to ground or Vdd (3.3v from the RPi). So on one all pins are grounded to use the default address of 0x20. On the other I hooked A0 to Vdd and the others to ground for address 0x21. You basically use binary to make the addresses.

1 is Vdd and 0 is Vss

A2 A1 A0 Address
0 0 0 0x20
0 0 1 0x21
0 1 0 0x22
0 1 1 0x23
1 0 0 0x24
1 0 1 0x25
1 1 0 0x26
1 1 1 0x27

Wiring things up

First off, if you have more then one I2C devices on your circuit, which we do in this example, you will need to use two 4.7K ohm pull-up resistors to the SDA and SCL pins. When using just one device you don't need to. And don't forget to put a 220 or 330 ohm resistor in series with your LEDs. Use a 10K ohm resistor on the switch.

You can also download the Fritzing diagram proto01 on GitHub. Note that I have LED1 on GPA0 and LED2 on GPB7 on the MCP23017 at address 0x20, and LED3 on GPA1 on 0x21. This allows me to test outputting two ports.

Test that devices are connected

First once you have things wired up, it's good to check if the RPi is seeing the I2C device, and confirm the address it is on. You use the i2cdetect command and supply the bus number you wish to check. In this example you can see I have two MCP23017's connected, one on port 0x20 and the other on port 0x21.

You should replace the -y 1 with -y 0 if you are using a rev 1 board.

$ sudo i2cdetect -y 1

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: 20 21 -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         

Data Registers

Data registers are how you talk with the MCP23017. You will write data to registers, or read data from them. We use bank 0, so you can ignore the bank 1 column. You also have two IO ports named A and B. They represent GPA0-7 (pins 21-28) and GPB0-7 (pins 1-8).

Address IOCON.BANK = 1 Address IOCON.BANK = 0 Access to
00h 00h IODIRA
10h 01h IODIRB
01h 02h IPOLA
11h 03h IPOLB
02h 04h GPINTENA
12h 05h GPINTENB
03h 06h DEFVALA
13h 07h DEFVALB
04h 08h INTCONA
14h 09h INTCONB
05h 0Ah IOCON
15h 0Bh IOCON
06h 0Ch GPPUA
16h 0Dh GPPUB
07h 0Eh INTFA
17h 0Fh INTFB
08h 10h INTCAPA
18h 11h INTCAPB
09h 12h GPIOA
19h 13h GPIOB
0Ah 14h OLATA
1Ah 15h OLATB

Let's turn on those LEDs. The command you will use is i2cset, the bus is 1, the addresses we found in the previous step was 0x20 and 0x21. The data output register for port A is at 0x14 (OLATA) and for port B it's 0x15 (OLATB). You first need to tell the MCP23017 what pins will be used for output and what are input. 0 is for output and 1 is for input. Port

$ sudo i2cset -y 1 0x20 0x00 0x01 <- GPA0-6 
$ sudo i2cset -y 1 0x20 0x01 0x00 <- GPB0-6 
$ sudo i2cset -y 1 0x21 0x00 0x00 <- GPA0-6 
$ sudo i2cset -y 1 0x20 0x14 0x01 <- turn on GPA0
$ sudo i2cset -y 1 0x20 0x14 0x00 <- turn off GPA0
$ sudo i2cset -y 1 0x20 0x15 0x80 <- turn on GPB7
$ sudo i2cset -y 1 0x20 0x15 0x00 <- turn off GPB7
$ sudo i2cset -y 1 0x21 0x14 0x02 <- turn on GPA1
$ sudo i2cset -y 1 0x21 0x14 0x00 <- turn off GPA1 

Source code building and running

I am using Pi4J to control the Raspberry Pi GPIO port. You can find the MCPExample at GitHub.

import com.pi4j.gpio.extension.mcp.MCP23017GpioProvider;
import com.pi4j.gpio.extension.mcp.MCP23017Pin;


public class MCPExample {
    public static void main(String[] args) throws InterruptedException, IOException, I2CFactory.UnsupportedBusNumberException {
        int address = 0x20;
        System.out.format("<--Pi4J--> MCP23017 GPIO Example ... started using address %02X.\n",address);
        System.out.format("<--Pi4J--> MCP23017 GPIO Example ... started using address %02X.\n",address+1);

        // create gpio controller
        final GpioController gpio = GpioFactory.getInstance();

        // create custom MCP23017 GPIO provider
        final MCP23017GpioProvider pro0 = new MCP23017GpioProvider(I2CBus.BUS_1, address);
        final MCP23017GpioProvider pro1 = new MCP23017GpioProvider(I2CBus.BUS_1, address+1);

        // provision gpio input pins from MCP23017
        GpioPinDigitalInput in0[] = {
                gpio.provisionDigitalInputPin(pro0, MCP23017Pin.GPIO_B0, "in0-B0", PinPullResistance.PULL_UP),

        // create and register gpio pin listener
        gpio.addListener(new GpioPinListenerDigital() {
            //            @Override
            public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
                // display pin state on console
                System.out.println(" --> GPIO PIN STATE CHANGE: " + event.getPin() + " = " + event.getState());
        }, in0);

        // provision gpio output pins and make sure they are all LOW at startup
        GpioPinDigitalOutput out0[] = {
                gpio.provisionDigitalOutputPin(pro0, MCP23017Pin.GPIO_A0, "out0-A0", PinState.LOW),
                gpio.provisionDigitalOutputPin(pro0, MCP23017Pin.GPIO_B7, "out0-B7", PinState.LOW),
        GpioPinDigitalOutput out1[] = {
        // keep program running for 20 seconds
        for (int count = 0; count < 10; count++) {
            gpio.setState(true, out0);
            gpio.setState(true, out1);
            gpio.setState(false, out0);
            gpio.setState(false, out1);

        // stop all GPIO activity/threads by shutting down the GPIO controller
        // (this method will forcefully shutdown all GPIO monitoring threads and scheduled tasks)

        System.out.println("Exiting MCP23017GpioExample");

You should be able to build by cloning the chesslr repository then build with mvn package. One compiled, you can run the example with sudo java -classpath chesslr-A.1.jar:../lib/'*':chesslr-A.1.jar MCPExample. sudo is required because PI4J needs access to gpio, and that is restricted to root.

Note I had my program get an I/O error when had 5 LEDs on the system. I believe it was because I was drawing too much power. Since reducing to 3 LEDs it seems stable.

What's Next

The RPi can not power all 64 LEDs, all the MCP23017's and LED displays I will be using on my chess board. I will need to design the next circuit so I can draw power from a different source other then right from the RPi.

Copyright © 2020, Lee Patterson