More learning on the Raspberry Pi ... this time I am adding a MCP23017 GPIO Expander. My earliest Pi is the original Model B with only 17-ish GPIO pins, so a GPIO expander is very handy.

I chose the MCP23017 (http://ww1.microchip.com/downloads/en/devicedoc/21952b.pdf) GPIO expander as it:

  • adds many (16) I/O channels that can be used as digital inputs or digital outputs
  • easy to interface to as it uses i2c (up to 8 of them can be daisy chained bringing an additional 128 GPIO ports)
  • it is relatively cheap (MYR5.22)
  • easy to source locally (http://goo.gl/AV3ivf)
  • available as in a DIP-style package, which allows it to be prototyped on a breadboard
  • is supported by an extension in the WiringPi library (http://wiringpi.com/)
  • supports triggering an interrupt when there is a change in the IO pins

Hardware

The pinouts are as follows:



I set it up on a breadboard as follows:

(Check out Fritzing at http://fritzing.org/home/).

The MCP23017 can be run over 3.3V or (preferably) 5V, which needs to be connected to the VDD and VSS pins. The RESET signal works in inverse polarity, so for normal operation requires 5V/HIGH connected to it.

It requires a 2-wire connection to the Pi's i2C pins, SCL and SDA. While the Pi's pins I/O can only handle up to 3.3V and should never be connected to a 5V device, for the i2c pins this is apparently allowed. The reason being that on an i2c bus, "At the physical layer, both SCL and SDA lines are of open-drain design, thus, pull-up resistors are needed. Pulling the line to ground is considered a logical zero while letting the line float is a logical one" (https://goo.gl/DN5G1M). The Pi end has built-in 1.8K ohm resistors, bringing the line up to 3.3V. Since Slave devices respond to commands by pulling the line low (0V), it doesn't matter if the rest of the slave runs on 3.3V or 5V, it never feeds current back to the i2C slave. The only important thing is that the slave is capable of interpreting 3.3V as a high. Let me know if I've got this wrong.

It defaults to an i2c address of 0x20, but this can be adjusted to 0x20-0x27 by setting a combination of the A0, A1 and A2 pins to 5V/HIGH. Otherwise they should be connected to LOW/GND as above.

Internally, the MCP23017 consists of two banks of 8 I/O lines, labelled bank A and bank B.

To test the I/O, I connected:
  • a LED on GPA6, to test output. A 1K Ohm resistor is used to reduce the current so as not to burn out the LED.
  • a switch on GPA7, to test input. A 1K Ohm resistor is used as a failsafe to reduce the current, in case the pin is accidentally set to output and set to HIGH and the switch pressed, which would channel 5V directly to GND.
When powered on, the MCP23017 appears on the i2c bus:

pi@raspberrypi:~ $ sudo i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Software

With WiringPi's built-in MCP23017 extension, it is quite trivial to access the I/O. I wrote a simple test program as follows:


#include
#include
#include
#include

#define TESTIN 6
#define TESTOUT 7

using namespace std;

int main () {

// initialize wiringPi
wiringPiSetup();

// init i2c for mcp23017
mcp23017Setup(120, 0x20);

// setup mcp23017 GPA5 as input
pinMode (120+5, INPUT);
pullUpDnControl (120+TESTIN, PUD_UP); // Note: MCP23017 only supports Pull-Up, so all switches connect to GND.

// setup mcp23017 GPA7 as output
pinMode (120+TESTOUT, OUTPUT);

int lastState = digitalRead (120+TESTIN);

while (1) {

// toggle LED, off on even seconds, on odd seconds
if (time(NULL) % 2 == 0) {
digitalWrite (120+7, LOW);
} else {
digitalWrite (120+7, HIGH);
}

int state = digitalRead (120+TESTIN);
if (state != lastState) {
cout << "Switch state is " << state << endl;
lastState = state;
}

delay (20); // let CPU sleep 20 ms (also helps with debounce on button press)
}
}

When executed on the Pi, the LED blinks on and off every second. When the button is pressed, the state of the switch is output on the terminal:

pi@raspberrypi:~ $ sudo ./PiTest
Switch state is 0
Switch state is 1
Switch state is 0
Switch state is 1
Switch state is 0
Switch state is 1
Switch state is 0

It's a quite straightforward test, and made simpler with the abstraction layer from WiringPi.

The above program polls the device continuously to know the current status of the I/O ports. In my next posting on this topic, I will explore getting the MCP23017 to trigger an interrupt on the Pi whenever the I/O state changes.

Bug: in the breadboard diagram above, the lower resistor should be shifted 1 column to the left.