The MCP23017 I/O Expander

This MCP23017 Arduino tutorial shows you how to control the device in driving LEDs and reading button presses. It also shows you exactly how to use interrupts which is very tricky as there are some problems (solved here) in using existing Arduino code.

The MCP23017 is a port expander that gives you virtually identical PORTS compared to standard microcontrollers e.g. Arduino or PIC devices and they even include interrupts.It gives you an extra 16 I/O pins using an I2C interface as well as comprehensive interrupt control. This is a very versatile and multi-configurable I/O expander.

For some applications it could be a bit over the top; For example if you only want to control outputs a 74HC595 would be cheaper.

The IC2 version operates at 100kHz, 400kHz and 1.7MHz speeds. It also operates from 1.8V to 5.5V.


Having interrupt outputs is one of the most important features of the MCP23017, since the microcontroller does not have to continuously poll the device to detect an input change. Instead an interrupt service routine can be used to react quickly to an input change such a key press etc.

mcp23017 text circuit breadboard layout

The MCP23017 has internal pullups for each input pin (configurable) so there is no need to add external pull up resistors for inputs such as push buttons etc.

The device also has provision for 3 address inputs so you can add a total of 8 devices onto the same I2C bus which gives you 128 new I/o pins only requiring two microcontroller I2C pins for control.

An alternative device is the MCP23S17 which is uses the SPI interface that can operate at 10MHz (a lot faster than the I2C version). This SPI device has the same number and arrangement of pins, but uses two unused (I2C) pins to implement the SPI interface. In all other respects it operates the same as the MCP23017.

To make life even easier each GPIO input pin can be configured with an internal pullup (~100k) and that means you won't have to wire up external pull up resistors for keyboard input. You can also mix and match inputs and outputs the same as any standard microcontroller 8 bit port.

MCP23017 Pinout

mcp23017 pinout

MCP23017 Specifications

Parameter MCP23017
Power Supply: 1.8V ~ 5.5V
Supply Current (idle): 1mA
Operational Current Max out of Vss [*] 150mA
Operational Current Max into Vdd [*] 125mA
Output current per pin 25mA
Standby Current 1uA
I2C address 0x20 ~ 0x27

* This is an unusual parameter specification. and relates to the fact that there must be a asymmetrical architecture in the chip that can accept only the specified current. The overall sink current is 150mA Vss and overall source current is 125mA.

Sink current is the current flowing (from an external circuit) when a pin is taken low.

Source current is the flowing (to an external circuit) when a pin is taken high.

Current Max out of Vss is the sum of all sinking currents.

Current Max into Vdd is the sum of all sourcing currents.

MCP23017 LED Driver

The above specification shows that the MCP23017 is quite capable of driving current to LEDs however there are 16 outputs so the maximum output current for the whole device has to be shared by all the LEDs .

If you drive 16 LEDs then it will be 150/16mA =9.38mA per GPIO pin (MAX). This also assumes that you are turning the LED on when it is driven low. Note: Choose a lower current to keep within limits.

If you are turning it on when driven high then the equation is 125/16mA=7.8mA (MAX). Note: Choose a lower current to keep within limits.

Obviously reducing the number of outputs in use will allow increasing current up to a maximum of 25mA per pin (but you must still keep the max overall output for the chip within specified limits).

MCP23017 Addressing

The 23017 has three input pins to allow you to set a different address for each attached MCP23017. Addresses available are specified in the I2C control byte and each device is selected by the I2C sequence below:

mcp23017 hardware address

The above corresponds to a hardware address for the three lines A0, A1, A2 corresponding to the input pin values at the IC. You must set the value of these hardware inputs as 0V or (high ) volts and not leave them floating otherwise they will get random values from electrical noise and the chip will do nothing!

So the i2C address range is 32 decimal to 37 decimal or 0x20 to 0x27 for the MCP23017. In the Arduino the address range is the same.

MCP32017 Non interrupt registers

IODIR I/O direction register

For controlling I/O direction of each pin, register IODIR (A/B) lets you set the pin to an output when a zero is written and to an input when a '1' is written to the register bit. This is the same scheme for most microcontrollers - the key is to remember that zero ('0') equates to the 'O' in Output.

GPPU Pullup register

Setting a bit high sets the pullup active for the corresponding I/O pin.

OLAT Output Latch register

This is exactly the same as the I/O port in 18F series PIC chips where you can read back the "desired" output of a port pin whether or not the actual state of that pin is reached. i.e. consider a strong current LED attached to the pin - it is easily possible to pull down the output voltage at the pin to below the logic threshold i.e. you would read back a zero if reading from the pin itself.

IPOL pin inversion register

The IPOL(A/B) register allows you to selectively invert any input pin. This reduces the glue logic needed to interface other devices to the MCP23017 since you won't need to add inverter logic chips to get the correct signal polarity into the MCP23017.

It is also very handy for getting the signals the right way up e.g. it is common to use a pull up resistor for an input so when a user presses an input key the voltage input is zero, so in software you have to remember to test for zero.

Using the MCP23017 you could invert that input and test for a 1 (in my mind a key press is more equivalent to an on state i.e. a '1') however I use pullups all the time (and uCs in general use internal pullups when enabled) so have to put up with a zero as 'pressed'. Using this device would allow you to correct this easily.

SEQOP polling mode : register bit : (Within IOCON register)

If you have a design that has critical interrupt code e.g. for performing a timing critical measurement you may not want non critical inputs to generate an interrupt i.e. you reserve the interrupt for the most important input data.

In this case it may would make more sense to allow polling of some of the device inputs. To facilitate this "Byte mode" is provided. In this mode you can read the same set of GPIOs using clocks but not needling to provide other control information. i.e. it stays on the same set of GPIO bits, and you can continuously read it without the register address updating itself. In non byte mode you either have to set the address you read from (A or B bank) as control input data.

Note: Interrupt registers are discussed later on here.

Software Library and versions

Arduino IDE Version

Version : 1.8.3

Adafruit library

The libraries used are:

  • Adafruit MCP23017 Library 1.0.3

This is easily installed from the Arduino IDE.

If you do not see the library as an entry when you click the menus then install the library as follows:

Sketch-->Include Library

Then select manage libraries :

Sketch-->Include Library -->Manage Libraries...

Search for and install <lib name> using the "Filter Your Search" form.

Library Operation

Pin definition for MCP23017 library

Note: In the library pins are labelled from 0 to 15 where:

pin 0 is bit 0 of port A

pin 7 is bit 7 of port A

pin 8 is bit 0 of port B

pin 15 is bit 7 of port B

MPC23017 I/O control functions

Single Bit I/O

Similar member functions to the pin controls on the Arduino are used to control the MCP23017 pins:

mcp.pinMode(0, OUTPUT);

mcp.digitalWrite(0,HIGH);

mcp.digitalRead(0);

Connections

Simple Netlist connections

The following netlist and diagram show you how connecting the MCP23017 to the Arduino is very simple.

Connect pin #12 of the expander to Arduino Analog 5 (i2c clock)

Connect pin #13 of the expander to Arduino Analog 4 (i2c data)

Connect pin #19 of the exp[ander to Arduino pin 3 (interrupt input).

Connect pins #15, 16 and 17 of the expander to Arduino ground (address selection)

Connect pin #9 of the expander to Arduino 5V (power)

Connect pin #10 of the expander to Arduino ground (common ground)

Connect pin #18 of the expander through a ~10kohm resistor to 5V (reset pin, active low).

Connect pin #28 of the expander to +ve end of an LED then to a~1kohm resistor to GND (MCP_LED1).

Connect pin #26 of the expander to +ve end of an LED then to a~1kohm resistor to GND (MCP_LEDTOG1).

Connect pin #4 of the expander to +ve end of an LED then to a~1kohm resistor to GND (MCP_LEDTOG2).

Connect pin #1 of the expander to a normally open push button that then connects to GND (MCP_INPUTPIN).

Fritzing Layout

mcp23017 text circuit breadboard layout

Arduino MCP23017 Examples

Example 1 Basic operation

This example shows three LEDs on different ports of the MCP23017, with two on port A (Green and Red). Two LEDs are alternately flashed (Red ones) while the third shows the state of the input on GPB0 i.e GPB0 is read by the Arduino and then the Green LED is updated.. This shows independent control of individual port bits i.e. while flashing the red LEDs the button is read and the green one is updated.

It serves to demonstrate how interrupts will be useful (see next example for interrupt code). Try pushing the button and releasing it quickly - usually the output green LED does not change state immediately - you may find the key press is not even detected!.

The following example shows using arduino and the mcp23017 library.

// MCP23017 Example: Slow key press reaction.
//
// Toggle LEDs and detect keypress.
//
// Example code showing slow reaction of 'button' 
// LED to keypress. Leading into why interrupts
// are useful (See next example).
//
// Copyright : John Main
// Free for non commercial use.

#include <Wire.h>
#include <Adafruit_MCP23017.h>

#define MCP_LED1 7
#define MCP_INPUTPIN 8
#define MCP_LEDTOG1 11
#define MCP_LEDTOG2 4

Adafruit_MCP23017 mcp;
  
void setup() {  
  mcp.begin();      // Default device address 0

  mcp.pinMode(MCP_LEDTOG1, OUTPUT);  // Toggle LED 1
  mcp.pinMode(MCP_LEDTOG2, OUTPUT);  // Toggle LED 2
  
  mcp.pinMode(MCP_LED1, OUTPUT);     // LED output
  mcp.digitalWrite(MCP_LED1, HIGH);

  mcp.pinMode(MCP_INPUTPIN,INPUT);   // Button i/p to GND
  mcp.pullUp(MCP_INPUTPIN,HIGH);     // Puled high to ~100k
}

// Alternate LEDTOG1 and LEDTOG2.
// Transfer pin input to LED1.
void loop() {

  delay(300);

  mcp.digitalWrite(MCP_LEDTOG1, HIGH);
  mcp.digitalWrite(MCP_LEDTOG2, LOW);

  delay(300);

  mcp.digitalWrite(MCP_LEDTOG1, LOW);
  mcp.digitalWrite(MCP_LEDTOG2, HIGH);
  
  // Transfer input pin state to LED1
  if (mcp.digitalRead(MCP_INPUTPIN)) {
     mcp.digitalWrite(MCP_LED1,HIGH);
  } else {
     mcp.digitalWrite(MCP_LED1,LOW);
  }
  
}

Reason for lagging key detection

If you push the button to change the state of the LED (LED1) there is sometimes a lag from when you hit the button to when it goes off and the same is true when you release it. You could say the code has a rather relaxed attitude to displaying the key update - in some cases this would not matter e.g. non critical turn on of an actuator for a window opener in a greenhouse (which would be slow anyway) but in others this would be critical e.g. an emergency motor stop in a machine.

The reason it reacts slowly is because of the long delays within the code that are "do-nothing" delays and if you hit the button during one of these periods, the processor is ignoring your button press.

Lagging key detect solution

Of course this a slightly contrived example as you can easily change the code for faster polling or use dynamic delays (using the millis function) but it demonstrates real world operation since in a "real" program you will want the processor to do quite a lot of work and this work will produce unavoidable delays (since it can only do one thing at a time) . This is where interrupts become essential. Example 2 uses interrupts to overcome the delays within the loop function.

Warning on Wire Library and Interrupts

It took a little while to figure out but this is one of the pitfalls of working with the arduino library you sometimes have to have a deeper understanding of what is going on under the hood before you can see why something won't work.

Example 2 MCP23017 Interrupts Don't Work

In the first example when you push the button to zero the input port (GPB0) it takes a while for the output LED (LED1) to go low. The idea here was to use a simple interrupt routine to service the external interrupt, re-start them and continue so that the output LED is toggled immediately that the button is pressed i.e. using an interrupt for instant LED update.

However there is a major non-obvious gotcha in that idea that stops simple interrupt code from working.

The following code shows typical use for attaching a callback function to an interrupt [Source https://www.arduino.cc/en/Reference/AttachInterrupt]

The following code does work ;It is a normal external interrupt operating only on the Arduino Uno R3 - just attach a button to pin 2 and the LED will toggle. An external interrupt caused when you press a button that pulls pin 2 low fires the interrupt routine 'blink' that then toggles the LED state (written in the main loop).

const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
  digitalWrite(ledPin, state);
}

void blink() {
  state = !state;
}

Here is the code that I wanted to use and when you look at it, it appears perfectly reasonable.

Note: The example code below is show for educational purposes and does not work correctly (See below for why this is the case).

The following code has three standard functions:

  • setup()
  • isr()
  • loop()

The setup function initialises the ports while loop function toggles some leds and the isr reacts to a button press (it should do but the code fails).

Click in the code below to copy it to the clipboard.

Note: This code hangs waiting for interrupts see next code example for the solution.
// MCP23017 Example: Unexpected Interrupt failure.
//
// This code sends an interrupt from the MCP23017
// to an Arduino external interrupt pin when an
// MCP23017 input button press is detected.
// At the same time MCP LEDs are toggled on button 
// release. THIS CODE FAILS FOR A VERY SUBTLE REASON.
//
// Copyright : John Main
// Free for non commercial use.

#include <Wire.h>
#include <Adafruit_MCP23017.h>

// MCP23017 setup
#define MCP_LED1 7
#define MCP_INPUTPIN 8
#define MCP_LEDTOG1 11
#define MCP_LEDTOG2 4
// Register bits
#define MCP_INT_MIRROR true  // Mirror inta to intb.
#define MCP_INT_ODR    false // Open drain.

// Arduino pins
#define INTPIN 3   // Interrupt on this Arduino Uno pin.

volatile uint16_t dmy; // Dummy for use within interrupt.

Adafruit_MCP23017 mcp;

void setup(){
  
  mcp.begin();      // use default address 0

  pinMode(INTPIN,INPUT);
  
  mcp.pinMode(MCP_LEDTOG1, OUTPUT);  // Toggle LED 1
  mcp.pinMode(MCP_LEDTOG2, OUTPUT);  // Toggle LED 2
  
  mcp.pinMode(MCP_LED1, OUTPUT);     // LED output
  mcp.digitalWrite(MCP_LED1,LOW);
  
  // This Input pin provides the interrupt source.
  mcp.pinMode(MCP_INPUTPIN,INPUT);   // Button i/p to GND
  mcp.pullUp(MCP_INPUTPIN,HIGH);     // Puled high ~100k

  // On interrupt, polariy is set HIGH/LOW (last parameter).
  mcp.setupInterrupts(MCP_INT_MIRROR, MCP_INT_ODR, LOW);
  mcp.setupInterruptPin(MCP_INPUTPIN,FALLING);

  mcp.readGPIOAB(); // Initialise for interrupts.

  // Enable interrupts - This is the Arduino interrupt control.
  attachInterrupt(digitalPinToInterrupt(INTPIN),isr,FALLING);
}

// The interrupt routine handles MCP_LED1
// This is the button press since this is the only active interrupt.
void isr(){
static uint8_t ledState=0;
   
   // Debounce. Slow I2C: extra debounce between interrupts anyway.
   // Can not use delay() in interrupt code.
   delayMicroseconds(1000); 
   
   dmy = mcp.readGPIOAB();
  
   if ( ledState ) {
      mcp.digitalWrite(MCP_LED1, LOW);
   } else {
      mcp.digitalWrite(MCP_LED1, HIGH);
   }

  ledState = ! ledState;
}

//////////////////////////////////////////////
void loop(){ 
  
  delay(300);

  mcp.digitalWrite(MCP_LEDTOG1, HIGH);
  mcp.digitalWrite(MCP_LEDTOG2, LOW);
 
  delay(300);

  mcp.digitalWrite(MCP_LEDTOG1, LOW);
  mcp.digitalWrite(MCP_LEDTOG2, HIGH);
}

Example 3 How to use Interrupts

The requirement for operating interrupts is that to clear an interrupt state you have to read from either INTCAP (interrupt data captured) or GPIO.

Important: To clear interrupts you must read back data from either INTCAP(A/B) or GPIO(A/B).

In the above code, GPIO is read inside the isr() function in order to reset the MCP23017 interrupts. However the code hangs at that point!

If you look in the MCP23017 library code you won't see any reason for it at all.

Looking a little deeper reveals the culprit - which is the "Wire" library. In this library a hardware interrupt from the I2C module is in use. Since interrupts are disabled when the callback function is executed the code hangs waiting for the I2C interrupt that is never actioned.

That means you can not use the mcp23017 library to clear the interrupts using the "read GPIO" function because the Wire library never completes!

The key is to allow other interrupts in the current isr() routine but not those that would restart the current isr i.e. stop the MCP23017 interrupts temporarily. Arduino functions detatchInterrupt(), attachInterrupt(), interrupts(), noInterrupts() are used to achieve this.

Note: Accessing the MCP23017 via I2C takes a while so debounce is probably accounted for by the time that it takes to refresh the I/O expander.

It is worth studying the code as the way this works is fairly complex i.e. allowing interrupts from within an already operating interrupt triggered routine.

// MCP23017 Example: Interrupt operation.
//
// This code sends an interrupt from the MCP23017
// to an Arduino external interrupt pin when an
// MCP23017 input button press is detected.
// At the same time MCP LEDs are toggled on button 
// release.
//
// Copyright : John Main
// Free for non commercial use.

#include <Wire.h>
#include <Adafruit_MCP23017.h>

// MCP23017 setup
#define MCP_LED1 7
#define MCP_INPUTPIN 8
#define MCP_INPUTPIN2 10
#define MCP_LEDTOG1 11
#define MCP_LEDTOG2 4
// Register bits
#define MCP_INT_MIRROR true  // Mirror inta to intb.
#define MCP_INT_ODR    false // Open drain.

// Arduino pins
#define INTPIN 3   // Interrupt on this Arduino Uno pin.

#define controlArduioInt attachInterrupt(digitalPinToInterrupt(INTPIN),isr,FALLING)

Adafruit_MCP23017 mcp;

//////////////////////////////////////////////
void setup(){
  
  mcp.begin();      // use default address 0

  pinMode(INTPIN,INPUT);

  mcp.pinMode(MCP_LEDTOG1, OUTPUT);  // Toggle LED 1
  mcp.pinMode(MCP_LEDTOG2, OUTPUT);  // Toggle LED 2
  
  mcp.pinMode(MCP_LED1, OUTPUT);     // LED output
  mcp.digitalWrite(MCP_LED1,LOW);
  
  // This Input pin provides the interrupt source.
  mcp.pinMode(MCP_INPUTPIN,INPUT);   // Button i/p to GND
  mcp.pullUp(MCP_INPUTPIN,HIGH);     // Puled high ~100k

  // Show a different value for interrupt capture data register = debug.
  mcp.pinMode(MCP_INPUTPIN2,INPUT);   // Button i/p to GND
  mcp.pullUp(MCP_INPUTPIN2,HIGH);     // Puled high ~100k

  // On interrupt, polariy is set HIGH/LOW (last parameter).
  mcp.setupInterrupts(MCP_INT_MIRROR, MCP_INT_ODR, LOW); // The mcp output interrupt pin.
  mcp.setupInterruptPin(MCP_INPUTPIN,CHANGE); // The mcp  action that causes an interrupt.

  mcp.setupInterruptPin(MCP_INPUTPIN2,CHANGE); // No button connected, see effect on code=none.

  mcp.digitalWrite(MCP_LED1,LOW);

  mcp.readGPIOAB(); // Initialise for interrupts.

  controlArduioInt; // Enable Arduino interrupt control.

}

//////////////////////////////////////////////
// The interrupt routine handles LED1
// This is the button press since this is the only active interrupt.
void isr(){
uint8_t p,v;
static uint16_t ledState=0;

   noInterrupts();

   // Debounce. Slow I2C: extra debounce between interrupts anyway.
   // Can not use delay() in interrupt code.
   delayMicroseconds(1000); 
 
   // Stop interrupts from external pin.
   detachInterrupt(digitalPinToInterrupt(INTPIN));
   interrupts(); // re-start interrupts for mcp

   p = mcp.getLastInterruptPin();
   // This one resets the interrupt state as it reads from reg INTCAPA(B).
   v = mcp.getLastInterruptPinValue();

   // Here either the button has been pushed or released.
   if ( p==MCP_INPUTPIN && v == 1) { //  Test for release - pin pulled high
      if ( ledState ) {
         mcp.digitalWrite(MCP_LED1, LOW);
      } else {
         mcp.digitalWrite(MCP_LED1, HIGH);
      }

      ledState = ! ledState;
   }
   
   controlArduioInt;  // Reinstate interrupts from external pin.
}


//////////////////////////////////////////////
void loop(){
  
  delay(300);

  mcp.digitalWrite(MCP_LEDTOG1, HIGH);
  mcp.digitalWrite(MCP_LEDTOG2, LOW);
 
  delay(300);

  mcp.digitalWrite(MCP_LEDTOG1, LOW);
  mcp.digitalWrite(MCP_LEDTOG2, HIGH);
  
}

Warning: the arduino library cannot cope with more than one interrupt source since within Arduino routines the first interrupt that is found active (in the INTCAP register) returns that pin value - any more are ignored. For multiple interrupt sources you will need to write your own functions that are not pin-based.

MCP23017 Interrupt registers

The interrupt are comprehensive in this device, and consequently require a large amount of control registers. The following information allows you to easily understand and control the MCP23017 when using interrupt operation.

The device is split into two sets of 8 GPIO registers and each set can have a separate interrupt associated with it (INTA and INTB outputs).

These are the registers associated with interrupts in the MCP23017:

  • GPINTEN
  • INTCON
  • DEFVAL
  • IOCON.MIRROR (a bit in the IOCON register)
  • IOCON.INTPOL (a bit in the IOCON register)
  • IOCON.ODR (a bit in the IOCON register)
  • INTCAP
  • INTF

Add an A or B to the name for control of a specific register set.

Note: Register names may sound similar to PIC device names since the chip was also designed by MicroChip.

MIRROR register bit : (Within IOCON register)

The control bit MIRROR wire ORs INTA and INTB together if set - that means any interrupt condition on any port will cause both INTA and INTB to be activated. When not set you have two separate interrupt outputs: INTA and INTB each associated with the A or B register.

INTPOL register bit : (Within IOCON register)

If set means the interrupt output is active high.

ODR register bit : (Within IOCON register)

If set means the interrupt pin is open drain (overrides INTPOL).

INTCON register

INTCON controls how interrupt conditions are detected as below:

There are two ways of detecting interrupt states for a specific register bit:

  1. Input Pin change, (A specific INTCON bit associated with a pin is 0) - This is the default.
  2. Input pin change compared to defined value in DEFVAL, (A specific INTCON bit associated with a pin is 1)

In the first case an interrupt is generated if the new pin value is different to the old pin value.

In the second case an interrupt is generated if the new pin value is different to the stored default value (DEFVAL).

DEFVAL register

Sets the default value of a pin which is the inactive input interrupt state.

Say you have two interrupt sources e.g. an active low alarm (ALRM) and an active high sensor (SENS) and you want either of these to cause an interrupt. Using the DEFVAL register you would set:

  • the DEFVAL value for ALRM to high,
  • the DEFVAL value for SENS to low.

Any signal opposite to the DEFVAL value would trigger an interrupt. So if the ALRM signal goes to 0V an interrupt would be triggered, and if the SENS signal goes to 5V an interrupt would be triggered.

This saves having to put inverter chips all over the place to get the right signal polarity. You would also enable the corresponding INTCON and GPINTEN bits corresponding to the bit positions in the register for the pins used as interrupts.

GPINTEN register

You may have some pins as LED outputs so you won't want them causing an interrupt or you may have some inputs that you only need to poll occasionally. GPINTEN is the register that enables each bit of the register to act as an interrupt on change pin (IOC). Using GPINTEN, you enable specific input pins as interrupts by setting the corresponding bit high.

INTA and INTB registers

The chip also allows ultimate configuration of the output states of INTA and INTB. This is quite unusual as chips usually have a fixed output state that you have to add glue logic around to get the right signal polarity.

For each INTA and INTB you can set the output as:

  • Active low
  • Active high
  • Open Drain

These can be controlled from within the IOCON register.

Note: Only the INTA and INTB pins on the MCP23017 are open drain (if you set them to be open drain).

INTF Interrupt Flag register

The INTF register is the interrupt flag register and tells you which interrupt input cause the current interrupt trigger. When a bit is set in this register (that is enabled for interrupts) the associated pin caused the interrupt.

INTCAP Interrupt Data Capture register

A further feature of the chip is that the value of the register is captured when an interrupt is triggered. INTCAP stores the input state of the input pins when the interrupt occurred. Any further interrupt conditions will not cause an interrupt until either INTCAP or GPIO is read.

Interrupt Operation

During the process of reacting to the INTF flag your code will likely take too long to read the interrupt data registers (remember this is a serial interface device so it takes time to get the serial data back from the MCP23017). Even though it is on a fast serial interface you will have to set some command register addresses which are themselves serial output data bursts. You could have missed any changes from when the interrupt fired to the time that you get around to reading back exactly which interrupt caused INTF change!

INTCAP preserves that interrupt state when the interrupt fires so that you only see the correct state data matching the interrupt condition flags (INTF).

No other interrupts can fire until you either read a GPIO register or read the INTCAP register. Using INTCAP means that you can find out exactly what the pin value was at the time of the interrupt.

It is therefore up to you to figure out in your design whether your software can keep up with the expected interrupt input rate.

Multiple Interrupt Connections

If you want to have several MCP23017 devices on the I2C bus and you want to also allow any of them to interrupt a processor then you will need to use the open-drain capability of the interrupt output pin. Using a single resistor to pull the open drain high, tie all the interrupt outputs from each MCP23017 together. If any one of them causes an interrupt then this open drain "wire or" connection will be pulled low. It is then up to you to go and find out which device caused the interrupt.

Warning: This does mean that you have to have an active low interrupt input on the microcontroller.

Clearing interrupts

To clear interrupt conditions so that more interrupts can be received read either INTCAP or GPIO.

You must read either of these during initialisation otherwise the interrupts may not start at all.

Warning: Interrupts appear to fail if you do not read either INTCAP or GPIO.
One thing that can catch you out is while debugging - if you read the INTCAP register then the MCP23017 clears the INTF register and INTCAP register so reading it again will result in zeros in the response.

Important Interrupt Operation

A very important part of using interrupts with the MCP23017 is the following extract from the datasheet:

Causes of interrupts for the MCP23017

Note the 2nd condition which indicates that the interrupt will not be cleared after reading from INTCAP or GPIO. In this case you would have to remove the source of the interrupt (e.g. release a key) or re-program the interrupt condition. You should only use the 2nd case where you know that the interrupt sources is going to be short.

For instance if you use a keypress as the interrupt source and the 2nd case setting, the user can force the processor to halt by holding down the button.

(for detecting the key) and go away and poll the key until release because continuous interrupts will hang the processor waiting in the interrupt routine (or re-firing the ISR immediately which causes the same effect).

It is therefore best to employ the 1st method for detecting interrupt states simply because it is easier to turn them off!

New! Comments

Have your say about what you just read! Leave me a comment in the box below.
Become a subscriber (Free)

Join 29,000 other subscribers to receive subscriber sale discounts and other free resources.
:
:
Don't worry -- your e-mail address is totally secure. I promise to use it only to send you MicroZine.




Privacy Policy | Contact | About Me

Site Map | Terms of Use



Visit our Facebook Page:

   Click Here



Recent Articles

  1. How to use the MCP23017 I/O Expander on the Arduino

    How to use the MCP23017 to increase your I/O by 16 pins (or more) and use its interrupt system.

    Read more

  2. The Essential I2C Tutorial: All you need to know about I2C...

    How to use I2C. In this tutorial you will learn all about the 2 wire I2C serial protocol. Learn how easy it is to use, how it works and when to use it...

    Read more

  3. How RS232 Works: The Easy Guide to RS232.

    How RS232 Works: What exactly is RS232 and how does it work? - Find out Here!

    Read more

Sign up for MicroZine
''The'' Microcontroller Newsletter

Enter your first Name and primary email address in the form below:


And receive absolutely FREE a full project for:

"Measuring Analogue Voltages
Without An ADC"

(Using only one pin).

Instant Download:
You Can
Get It Right Now

Warning: This project could be  Removed 
at any time.  

It will  NOT be 
available indefinitely SO
To avoid 
disappointment  get it:

Now



:
:
Don't worry -- your e-mail address is totally secure. I promise to use it only to send you MicroZine
Remember this is a project with full description and fully debugged C Source code - and it's not available from the main website.

You can only get it through this newsletter.

To get exclusive access Enter your first name Name and primary email address Now in the form above.:



But wait !

There's more...

You'll receive more
free and exclusive reports as well as site information and site product updates


Scroll up to the form above and sign up NOW. Don't forget it's FREE and if you don't like it, you can unsubscribe at any time.

Click Here Now to use the form above to get your Valuable information absolutely free.



Readers Comments

"I wanted to thank
you so so so much
for all the information
you have provided in
your site it's

SUPERB and FANTASTIC."

- Ranish Pottath

"This site really is
the best and my favorite.
I find here many useful
projects and tips."

- Milan

bursach<at>gmail.com<

"Awesome site,
very, very easy and nice
to navigate!"


- Matt
matt_tr<at>
wolf359.cjb.net


Learn Microcontrollers

"Interested in
Microcontrollers?"

Sign up for The
Free 7 day guide:

FREE GUIDE : CLICK HERE


"I am a newbie to PIC
and I wanted to say
 how great your
site has been for me."


- Dave

de_scott<at>bellsouth.net

"Your site is a great
and perfect work.
congratulations."


- Suresh

integratredinfosys<at>
yahoo.com

"I couldn't find the correct
words to define
yourweb site.

Very useful, uncovered,
honest and clear.

Thanks so much for
your time and works.
Regards."


- Anon

Back to Top