MCP23016 I/O Expander - using interrupt

I am able to utilize a MCP23016 chip using I2C (WIRE.H) with no problem but when I try to use AttachInterrupt to execute a function it doesn't seem to be triggered.

I have the MCP23016 INT (pin 6) connected to digital pin 2 on the Arduino for interrupt 0 and also pulled high with a 10K resistor. It goes low when input pins on the I/O expander are pushed but the code in the interrupt function doesn't execute. I was trying to the INTCAP1 register, shift it and write it to the GP1 register.

I tried using a Serial.print with the function and then just toggling a volatile int variable to control the LED on pin 13 just to get a visible sign that the function was executing. No luck with any.

Any suggestions?

Thanks!

//MPC23016 SDA to analog pin 4   SCL to analog pin 5
//MCP23016 INT to digital pin 2 for interrupt processing

#define DEBUG           

#include "Wire.h"

#define MCP23016_I2C_ADDRESS 0x20       //seven bit address

#define GP0       0x00                    // register addresses
#define GP1       0X01
#define OLAT0   0x02
#define OLAT1   0x03
#define IPOL0     0x04
#define IPOL1     0x05
#define IODIR0    0x06
#define IODIR1    0x07
#define INTCAP0 0x08
#define INTCAP1 0x09
#define IOCON0  0x0A
#define IOCON1  0x0B

 byte dataGP0, dataGP1;
// volatile byte GPint;
 int pin = 13;
volatile int state = LOW;

 void setup()
{
  Serial.begin(9600);  // start serial for output  
  Wire.begin();
  delay(100);                                   // allow device POR
  write_io(IODIR0, B00000000);     // GP0 pins are outputs   
  write_io(IODIR1, B00001111);     // GP1.0 - GP1.3 are inputs   
  write_io(IPOL1, B00001111);      // invert inputs       (LEDs lit when button pushed) 
//  write_io(IPOL1, B00000000);      //  non-inverted inputs (LEDs lit until button pushed)

  pinMode(pin, OUTPUT);
  attachInterrupt(0, MCP_interrupt, CHANGE);
} 

void loop()
{
  dataGP0 =dataGP0 + 1; 
//  delay(100);
}

byte read_io(byte cmd_byte)
{
  byte tmp;   
  Wire.beginTransmission(MCP23016_I2C_ADDRESS);
  Wire.send(cmd_byte);         // 0 or 1 -- GP0 or GP1
  Wire.endTransmission();
  Wire.requestFrom(MCP23016_I2C_ADDRESS, 1);
  tmp=Wire.receive();
  return tmp;
} 

void write_io(byte cmd_byte, byte reg_val)
{
  Wire.beginTransmission(MCP23016_I2C_ADDRESS);
  Wire.send(cmd_byte);
  Wire.send(reg_val);
  Wire.endTransmission();
} 

void MCP_interrupt()
{
  state = !state;
/*
   GPint = read_io(INTCAP1);       //read_io(GP1);
   Serial.println(GPint, BIN);
   GPint = GPint << 4 ;  // move inputs to outputs  
   write_io(GP1, GPint);     // output buttons  to LEDs
*/   
}

You can't use serial print inside an interrupt service routine. Likewise you can't use the I2C inside an IRS either. That's why it won't work.

Mike,
Thanks - I suspected it might not work. Is it because WIRE is using the interrupts or maybe because there is limit to the instructions or number of cycle that can be used within an interrupt service routine?

The reference info for AttachInterrupt does not provide much insight...

I know you have discussed MCP23016 chips before - is there another way to utiltize the INT pin with an Arduino?

I changed the code to set a switch in the interrupt routine and test it within the main program loop.
Then the GP registers can be read only if the interrupt was triggered. It saves a little polling time and probably would suffice from many applications...

Thanks again!

  • Ron

Yes it is because the I2C requires interrupts and in an ISR they are disabled. You could rewrite the I2C routines to get round this if you want. There is no limit to the length of ISR but the longer it is the more other things get messed up like timers and incoming serial bytes.

Also the best way to use that pin is to pole it. It is quite useful because any change can be spotted without having to spend the time doing the transaction every time.