MCP23017 GPIO Expander python 3 Library – With Interrupts!

There are numerous python libraries out there for the 16 port MCP23017 GPIO expander chip that works with the Raspberry Pi, so why yet another one? None of the ones I could find actually implement interrupts via the chip Polling is not ideal for my home monitoring setup. I was already monitoring via interrupts and the onboard GPIO pins using the awesome RPIO module and wanted to continue using interrupts with the expansion chip. So I built my own module, leveraging Adafruit’s I2C library for the nitty gritty backend interface.

  • Simple digital input and output via all pins
  • Input interrupts
  • Interrupt port mirroring is configurable – either INTA and INTB can trigger independently for their respective GPIO port banks, or both INTA and INTB can trigger at the same time regardless of what GPIO pin causes the interrupt
  • Configurable interrupt polarity – INT could pull the pin high or push it low
  • Each GPIO pin can be configured for interrupts independently to either compare against the previous value or against a default pin value
  • A utility method cleanupInterrupts that can be called periodically to clear the interrupt if it somehow gets stuck on

You can find the library on my BitBucket.

Stick around after the break for a breakdown of how to use it.


First, this is a python 3 library, so you’ll need to get python 3 installed.

Second, you’ll need to python 3 smbus python module installed. As of this writing, there is no prebuilt package for python 3 smbus, however, cobbling together various instructions I came across, I assembled this process that works to install a python 3 compatible smbus module:

Lastly, you’ll need a python 3 port of Adafruit’s I2C library, which I have included as part of the BitBucket repository. Note that this chip is available in both I2C and SPI interfaces – my library uses I2C.

Wiring up the chip

For ease, I went with the Slice Of Pi/O breakout board for this chip which already has the header to connect to the Pi and does not block the other pins that aren’t used by the MCP23017. Here is the site with assembly instructions: I purchased mine on eBay from a US seller but they are available from several resellers.

Once you’ve assembled the Slice of Pi/O, plug it into the Pi and configure I2C on the Pi. Adafruit once again to the rescue with a simple tutorial to get I2C configured:

By default, the Slice of Pi/O puts the MCP23017 on the 0x20 address. If you have multiple I2C devices, you will need to solder the jumpers on the bottom of the board to change the address – details are at the assembly instructions link above.

Now, to use interrupts from the expander, you need to use at least one of the onboard Pi GPIO pins to monitor for when an interrupt occurs. I’ve wired mine up to onboard port 4 using a 12K ohm resistor to INTB on the expander as seen in the picture below. I’m using interrupt pin mirroring so I only need one of the ports wired up. Note, you really need to use a resistor to keep the current flow between the two ports low to avoid damage – 10 or 12K will work fine.


Interrupt port mirroring means that if an interrupt appears on any of the 16 ports, both INT ports will trigger at the same time. Alternatively, the library can configure the chip so an interrupt on the first 8 ports would trigger INTA and the second 8 ports would trigger INTB.


I have several functioning examples for using this chip the examples directory on BitBucket:

  • reads input via polling
  • blinks 6 connected LEDS randomly
  • is a complete example implementing interrupts
  • blinks a single LED

That is it! I hope others find this useful. If you have improvements or bug fixes, please submit a pull request!

21 thoughts on “MCP23017 GPIO Expander python 3 Library – With Interrupts!”

  1. Dan, I am very excited about trying out your code, but am having trouble with the installation. On the “Install smbus for python 3 on the Raspberry Pi” portion, the python3 build command fails with with the following output:

    I am using Pi 2, gcc version 4.6.3 (Debian 4.6.3-14+rpi1), Python 3.2 and lm_sensors-2.10.8.tar.gz

    The file i2c-dev.h was copied to ./lm_sensors-2.10.8/prog/py-smbus/smbusmodule.c – is that correct?

    BTW, here are the unpacking commands for that package on the Pi:
    gunzip lm_sensors-2.10.8.tar.gz
    tar -xvf lm_sensors-2.10.8.tar

    1. Hi Clint!
      I see now that my instructions weren’t very clear! What you actually need to do is copy this file:




      Then run `python3 build` and `sudo python3 install` from within the ./i2c-tools-3.1.1/py-smbus/ directory.

      Give that a try and let me know how it goes – hope it works.

  2. Hi there
    I seem to have all libraries etc. installed correctly but when I run I get the following:

    Traceback (most recent call last):
    File “/home/horse/Eclipse workspace root/workspace4python/mcp23017test/src/”, line 10, in
    mcp = MCP23017(address = 0x20, num_gpios = 16) # MCP23017
    File “/home/horse/Eclipse workspace root/workspace4python/mcp23017test/src/”, line 67, in __init__
    self.direction |= self.i2c.readU8(MCP23017_IODIRB) << 0x08
    TypeError: unsupported operand type(s) for <<: 'NoneType' and 'int'

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "/home/horse/Eclipse workspace root/workspace4python/mcp23017test/src/", line 22, in
    mcp.output(pin, 0)
    NameError: name ‘mcp’ is not defined

    do you have any idea what is going wrong?



    1. Hmm, I don’t know for sure, but it seems like maybe the Adafruit I2C library isn’t instantiating itself correctly. Is I2C setup and working? Is the chip showing connected when you run i2cdetect per the Adafruit guide above?

      1. I2C is running OK on the Pi as I’ve had success using it to flash LED’s with the mcp23017 using a different library (smbus)
        The line:
        self.direction = self.i2c.readU8(MCP23017_IODIRA)
        doesn’t throw up a problem but appears to be returning ‘None’
        If I run Adafruit I2C as main:
        bus = Adafruit_I2C(address=0x20)
        returns “Default I2C bus is accessible”
        from within main I can also run
        print(bus.getPiRevision()) and print(bus.getPiI2CBusNumber())
        and get 2 and 1 respectively which seems correct.
        However when I run:
        print(bus.write8(0, 0xff))
        it returns ‘None’

  3. Hiya
    I’ve found the problem.
    The default busnum in the AdafruitI2C class is set to -1 and for some reason it fails to get the correct busnum when initialising.
    When I force this to be 1 in the init process everything springs to life and the LED flashes
    I’ll try the other stuff now and give you a shout if I encounter other problems if that’s OK

    1. Oh interesting. I’m glad you got it working. Looking at my setup, I don’t set busnum explicitly so something must be slightly different with your setup than mine. Regardless, glad it’s working!

  4. Hiya
    I’ll have a look and see what may be causing it. In the meantime
    blinky and basicinput work fine but when I run interrupttest I get the following:

    Traceback (most recent call last):
    File “/home/horse/Eclipse workspace root/workspace4python/mcp23017test/src/”, line 7, in
    import RPIO as GPIO
    File “/usr/local/lib/python3.4/site-packages/RPIO-0.10.0-py3.4-linux-armv7l.egg/RPIO/”, line 115, in
    import RPIO._GPIO as _GPIO
    SystemError: This module can only be run on a Raspberry Pi!

    Do you have any idea what might be causing this?


  5. Interesting read! I have been experimenting with the MCP23017 as well, and I too noticed that most libraries (in my case Pi4j in Java) only poll the chip for changes rather than use the interrupt pin(s).

    Out of curiosity, why did you choose to set the interrupt pin to either high or low, but not the third option of open drain (ODR)? You do have a comment in your code that mentions the ODR bit (L.198), but you don’t appear to set it.

    Testing an MCP23017 with a rotary encoder connected to it I noticed that the chip would sometimes ‘hang’ after rotating the knob rapidly. I seem to have solved this by polling the chip for its pin states every 50ms in addition to acting on the interrupt signal. You mention the MCP23017 getting stuck as well, does this match your experience?

    1. Hi! Glad you found it interesting.

      Regarding the open drain, in honesty I didn’t use it because I didn’t understand it 🙂 I’m not an electrical engineer and while I can cobble through some code to solve a problem given enough time and frustration, I don’t know what I don’t know. I googled open-drain a bit and I’m still not sure when/why I would use it… that’s not helpful at all, I know. Sorry!

      Regarding the chip getting stuck, that is the exact experience I had – rapid changes caused a hang. Perhaps there’s a better solution to address it in hardware – maybe the rapid changes are causing voltage fluctuations or something? Again, there’s my lack of knowledge. However, I only experienced the hangs when testing and developing the code. I’ve had the chip in ‘production’ monitoring some door and motion sensors since I originally wrote this post and looking through my code, I never call clearInterrupts. I haven’t had one hang.

  6. Hi, first of all many thanks for your code. I’ve been doing a lot of experimenting with the MCP23017 and I have so much success and fun with your code. Haven’t tried the interrupts yet ’cause I had been working on purely outputs only. That will be next in line, for multiple inputs and few outputs (separate MCP23017 for inputs only and another MCP23017 for outputs only, as I would do with a PLC ).

    Recently I tried your code for MCP23008. Wired it properly and there were no errors in my python code. However when I run the, am not getting any outputs. Even replacing the MCP23008 with another one there were no outputs. Tried using the Adafruit_MCP230xx code (just for checking) and I got it running, to my relief, the chips were not busted after all.

    Hope some updates can be made so the code can be usable in both MCP23017 and MCP23008.

    Once again many thanks!!

    1. Hi, glad it is useful for you! I don’t have access to or need for a MCP23008 at this point. It would probably be pretty easy change, though, maybe a few register addresses. The Adafruit code can probably provide some insight. If you were to modify my code, feel free to submit a pull request and I’ll incorporate your changes!

  7. hi
    i am getting this error
    “error: command ‘arm-linux-gnueabihf-gcc’ failed with exit status 1” when the command “python3 build” is entered.

    request your help / support in getting this resolved.

  8. Hi this the error am facing can you help to sort it out

    Error in main: [Errno 2] No such file or directory
    Traceback (most recent call last):
    File “”, line 54, in
    NameError: name ‘mcp’ is not defined

    1. Hi. This error is happening in the ‘finally’ block due to exception handling happening. This is technically kind of a bug in my interrupttest script, but all it really means is that that MCP23017 is not being initialized at this line:

      mcp = MCP23017(address = 0x20, num_gpios = 16) # MCP23017

      I’d recommend making sure I2C is set up correctly and checking the address of the chip. The code isn’t talking to the hardware.

  9. Hi Dan

    I tried checking the I2C independently its working fyne, and when i run the file this the error am getting and not able to connect to I2c

    Error accessing 0x20: Check your I2C address
    Error accessing 0x20: Check your I2C address
    Error accessing 0x20: Check your I2C address
    Error accessing 0x20: Check your I2C address
    Error accessing 0x20: Check your I2C address
    Error accessing 0x20: Check your I2C address
    Error accessing 0x20: Check your I2C address
    Traceback (most recent call last):
    File “”, line 38, in
    GPIO.add_interrupt_callback(4, inthappened, edge=’rising’, pull_up_down=GPIO.PUD_DOWN, threaded_callback=False, debounce_timeout_ms=5)
    File “build/bdist.linux-armv7l/egg/RPIO/”, line 220, in add_interrupt_callback
    File “build/bdist.linux-armv7l/egg/RPIO/”, line 182, in add_interrupt_callback
    IOError: [Errno 16] Device or resource busy

    1. Other than what the errors say, I’m not sure. Are you running the script as root? I can’t remember if root is required to access I2C but that might help.

Leave a Reply

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