Raspberry Pi and the MCP23017 I2C I/O Expander

I had a quick play with the I2C drivers that are currently being developed for the Raspberry Pi this afternoon and managed to get a MCP23017 16-bit I/O Expander working with it without any fuss.

Here is a highly exciting video of it blinking an LED:

The MCP23017 is a handy 28 pin chip that gives you 16 pins that can be used as either inputs or outputs (max 25mA from each pin) and up to 8 of the MCP23017 can be used on one I2C bus so it can give you a whole lot more I/O than the Pi has built in as well as reducing the risk of frying the Pi and also has the added advantage that the expander can be located away from the Pi linked with only four wires. There is also the smaller MCP23008 which is an 8 I/O version that can be used in the same way. They are both available in DIP form making it easy for building your own boards with and experimenting on breadboard. Here is the datasheet for the MCP23017.

Read on for some info on what is required to get this working.

Connecting it up

For a quick demo the following needs to be connected on the expander:

Pin 9 to Vcc (eg. 5V on the Pi or external source up to 5.5V)
Pin 10 to Ground
Pin 12 to SCL0 on the Pi
Pin 13 to SDA0 on the Pi
Pins 15,16,17 to ground (this selects the I2C address as 0x20, other combinations can set different addresses)
Pin 18 to Vcc (this turns the expander on)

For the location of the I2C and power pins on the Pi see the diagram here.  Note that the maximum you can draw from the 5V pin on the Pi will be the USB input current, typically 1A less the draw of the Pi itself, around 700mA for the Model B so that leaves us with around 300mA max. the maximum you can draw from the 5v pin on the Pi is in the region of 150-250mA, maybe less depending on the devices plugged into the Pi, more here.

For this test I also connected an LED and resistor between GPA0 on the MCP23017 (pin 21) and ground.

Drivers and i2c-tools

See this thread on the Raspberry Pi forums for the current work that is being done on i2c drivers for the Raspberry Pi, it’s still very much in the early development stages. There is the original bitbanging one (slower) and the new faster hardware one, to take a short cut for now you can just download the pre-compiled kernel with the I2C bitbanging driver built in here and simply swap it with the one in /boot on the Debian Squeeze distro and reboot.

Then you will want to install i2c-tools package via apt-get, this gives us some command line tools for scanning the I2C bus and sending values to I2C addresses and registers.

Now we can check that the Pi is communicating with the expander by doing: i2cdetect -y 0
If it is working you should see an ASCII representation of a table with 20 in the first column on the row marked 20. This signifies there is something there with an I2C address of 0x20 as expected.

Controlling the expander

The I/O pins on the MCP23017 are in two banks, A and B and each bank is controlled together. To set whether each pin is an input or an output we need to send a hex value to the correct register (see Table 1.4 in the data sheet). IODIRA (0x00) sets the input/output state for bank A and IODIRB (0x01) for bank B, set each of the 8 bits as 1 for input (the default) and 0 for output.  eg. to set pins 0, 1, 7 as input and the rest as outputs it would be 10000011 in binary or 0x83 in hex, to set a whole bank as outputs would be 0x00.

Then to turn each pin on or off we send a hex value to the register for the relevant bank, 0x12 for bank A, 0x13 for bank B. We need to send a 1 to each bit we want on and a 0 for each we want to turn off in the same manner as above, so to turn pin 0 on it’s 00000001 in binary or 0x01 in hex.

For a quick demo we can use the i2cset command that comes with i2c-tools, its format is:
i2cset i2-cbus i2c-address i2c-register value

Set all of bank A to be outputs: i2cset -y 0 0x20 0x00 0x00
Set GPA0 as on: i2cset -y 0 0x20 0x12 0x01
Set GPA0 as off: i2cset -y 0 0x20 0x12 0x00

The -y switch just turns interactive mode off so it doesn’t ask for confirmation.

Here is a simple bash script to blink it 20 times:

i2cset -y 0 0x20 0x00 0x00
until [ $COUNTER -lt 10 ]; do
i2cset -y 0 0x20 0x12 0x01
sleep 1
i2cset -y 0 0x20 0x12 0x00
sleep 1
let COUNTER-=1

So there you go, a quick demo of an I2C I/O expander working on the Pi. Next I need to get set up for cross compiling and try the new hardware driver and look at how to use I2C in something a bit more advanced than bash.

UPDATE 20/5/12: I’ve got the 3.2 kernel with hardware I2C driver from here working now. One thing to note is that the 3.2 kernels seem to have some issues working with SD cards that worked fine under 3.1 The 32GB Verbatim Class 6 card I had been using was throwing “mmc0: problem reading SD Status register” and “error -110 whilst initialising SD card” errors as someone else noted in the comments there. I’ve switched to a Sandisk Class 4 MicroSD in an adapter now and that is working fine.

UPDATE 24/5/12: I’ve started work on some Python tools to control the MCP23017 using the Raspberry Pi, see this post for more information.

UPDATE 2/6/12: I’ve created a plug in expander board using the Ciseco “Slice of Pi” and the MCP23017, see this post for details and a step by step guide to getting it working with the above Python tools including a link to a Raspberry Pi kernel/modules with I2C compiled in.

21 thoughts on “Raspberry Pi and the MCP23017 I2C I/O Expander

  1. due to the sdcard problems with 3.2.x I backported Chris’ drivers into 3.1.9. That, along with some changes necessary to use the in-kernel mcp23s08 driver can be found here https://github.com/selsinork/raspberry-pi-i2c-spi-drivers it should be trivial to change the I2C_BOARD_INFO line in the platform data to work with the mcp23017. The SPI versions seem to need some slightly different platform data, but should be fairly simple too.

  2. Hi Peter,

    Funnily enough somebody mentioned that on Twitter last night. The reason it works is that the I2C lines are pulled high (to 3v3) by the Pi and devices on the I2C bus will pull them low so the Pi is never exposed to 5V from the MC23017. The only caveat is that the I2C slave device has to be capable of registering the 3v3 as a high, not a problem for the MCP23017.

    You could run it from the Pi’s 3v3 pin but you can only draw 50mA from that which doesn’t leave much to play with across 16 potential outputs, whereas with the 5v pin you can draw the difference between the input current and the draw of the Pi + accessories as mentioned above. If you need 3v3 IO then it would be better to power the chip from an external source which will work fine as long as the ground is common.

    I’ve also used one of the Ciseco “Slice of Pi” boards to make a plug in expander board using this, see: http://nathan.chantrell.net/20120602/raspberry-pi-io-expander-board/


  3. “The only caveat is that the I2C slave device has to be capable of registering the 3v3 as a high, not a problem for the MCP23017”

    Well it is a problem. Practically it might just work, sometimes, if you’re lucky.

    Check the datasheet Peter linked, page 28, ‘Input High Voltage’ Param No. D041, CS, GPIO, SCL/SCK, SDA, A2, RESET, min 0.8 Vdd, For entire Vdd range.

    0.8 x 5v is greater than 3.3v here 🙂 So will it work ? Probably, possibly, maybe.. Not guaranteed though, and relying on it for anything important probably not a good idea.

    Given that level shifting I2C can be done with two n channel fets with a total cost of about 10p I can’t see a reason why you wouldn’t.

    The main polyfuse on my Pi is only 700mA, so if you assume two 100mA usb devices and the base idle current of about 460mA I see with my Pi you can’t assume to have much spare current available even from the 5v pin on the gpio header.
    So I think you’re right with the advice to power external stuff from it’s own supply – it’s certainly what I do after finding out just how crappy phone chargers and micro usb cables really are. Plenty of threads on the forums and elsewhere on power issues.

  4. Yes that’s the same datasheet I linked to in the post. I wasn’t sure if it would work until I tried it, agree it is out of spec but has worked flawlessly under my tests so far, maybe I’m just lucky. I guess it would drop off rapidly if you tried to distance the expander from the Pi though. Good tip about level shifting with a couple of FETs, thanks, I had thought about using something like a P82B96 if it didn’t work but that would be a nice cheap way to do it and definitely a good idea.

    Seems you are right about the polyfuse on the microUSB port being 700mA, I’d simply gone off what is says on the eLinux wiki here but that can’t be right. The link to the citation for that claim has been broken by the forum switch too.

  5. Thanks for the explanation, makes sense.
    I’ve ordered Slice of Pi and some MC23017s yesterday so your other post will come in very handy Thanks for sharing all this!


  6. The fet level shifter isn’t my idea, the details are in http://ics.nxp.com/support/documents/interface/pdf/an97055.pdf

    There’s other IC solutions like the PCA9306 too

    These days 3.3v is the norm, heading towards 1.8v.. I think virtually all the i2c devices I’ve got will work from 1.8v up to 5.5v, so reading between the lines you’re probably fine for short runs. I’d be more concerned with a 5v only device, or a 10m cable.

    Really, the spec is there for one reason. So that they can disclaim liability when you run it out of spec, it doesn’t work and you’ve burned the house down with your home automation project. Much like lots of the datasheets come with disclaimers on using the device in medical equipment etc.

  7. Great idea. Thanks for that. I used it a bit different – built an USB-tiny-i2c adaptor and now I have a cheap router with lots of I/O for cheap, too 🙂
    Thanks again!

  8. Hi,
    I’m trying to interface with a MCP23016 using i2c.
    I think I set up everything as it should, but when I try to do a i2cset, I keep getting write errors. Also trying to write to the ic from a sample program gives the same I/O errors

    Any ideas?

    – I’m running the latest wheezy (2012-07-15)
    – i2detect shows my ic at 0x20
    – I did a chmod 666 on /dev/i2c-0 (even a chmod 777 just to be sure)

  9. @bmi I’ve the same problem with the 23016. No chance until now to write anything successful into the chip.

  10. Stephan,

    After not getting the 23016 to work, I just ordered a few 23017’s.
    And they are working just fine!
    I read somewhere that the 23016 has problems working with “new technology”.

  11. Good day. Please, need some advice – how to connect several 8×8 matrixes to mcp23017.For example 4 of 8×8 in series to make running text. Is need more mcp chips or one enought? Thanks

  12. Hello
    Explain me, please, the different between register GPIOA and OLATA in MCP23017. After rtfm and stfw I thought, that OLATA (0x14) is dedicated to write high signal to “LED” (PIOA is to read…) I checked it and 0x12 going OK, but why?

    Sorry for bugs, i’m not english speaking.

    Z partyjnym pozdrowieniem

  13. Hi, get a copy of the MCP23017 datasheet, you cannot get into this level of playing with the registers without it . Here is a relevant statement from it (page 9):

    Reading the GPIOx register reads the value on the port. Reading the OLATn register only reads the latches, not the actual value on the port. The GPIO module is a general purpose, 16-bit wide, bidirectional port that is functionally split into two 8-bit wide ports. Writing to the GPIOn register actually causes a write to the latches (OLATn). Writing to the OLATn register forces the associated output drivers to drive to the level in OLATn. Pins configured as inputs turn off the associated output driver and put it in high-impedance.

    I have made the experience that it is much simpler to place the MCP23017 in IOCON.BANK=1 mode (avoids toggling the GPIOs registers as in IOCON.BANK=0 mode). But watch out though – the addresses of the registers in this mode are very DIFFERENT (see page 5 of the datasheet)

  14. In case you have a 23008 not a 23017. You can turn GP0 on like this:

    # Set all to output – same in 23008 & 23017
    i2cset -y 1 0x20 0x00 0x00
    # Turn on GP0 – GPIO is at address 0x09 not 0x12 for 23008
    i2cset -y 1 0x20 0x09 0x01

    I hope this helps someone.

  15. Your code works great, solid explanation. I do have a question, with a brief explanation; Lets say, via cron a copy of your script is called that sets pins 3,4&7 low, now at xx time I want to set pins 10 & 13 low (without changing the state of 3,4&7), and xx time set 3 high (lather rinse & repeat). Currently the behavior seems to randomly set all pins to low or high (generally low) except those I explicitly call in the second (third, fourth iterations). Is there an easy way to stop this behavior and just set the pin(s) I want to change the state of? Or do I need to poll the state of each pin and reset them all each time I want to make a change. Less than ideal, and not sure exactly how to implement that, or what I’d end up with since they are controlling relays that control power to pumps, lights etc… the constant switching would not be a good thing.

    Any advice would be appreciated!

  16. David – simply put no. You need to either do a read-modify-write on the output register, or keep a local copy of the content of the register somewhere so that you already know what the current state is. Keeping a local copy also means you need to serialise accesses somehow so that another piece of code running in parallel can’t change the content of the register underneath you without your knowledge.
    There is a better option however. The kernel has proper gpio drivers for the MCP23017, so it’s possible to load the driver with the appropriate platform data or devicetree and have it appear under /sys/class/gpio just like any other set of gpio’s provided by the kernel. In this way the kernel will take care of all the details for you. Now the /sys/class/gpio interface isn’t particularly fast, but with an i2c connected gpio chip high frequency switching or sampling probably isn’t something you’re worried about anyway.

  17. Thanks Nathan for sharing.
    I’ve come across your post while doing some search on a problem I’m experiencing with the MCP23017.

    Just wanted to extend; as has been done by others above already that to enable the output, writing is not done directly to the gpio pin but to the olat pin when in output mode.



Leave a Reply

Your email address will not be published. Required fields are marked *

Notify me of followup comments via e-mail. You can also subscribe without commenting.