Trying to get interrupts to work with Adafruit MCP23017 Break out board

I am trying to use the interrupts on the Adafruit MCP23017 GPIO Expander and I just can't seem to figure out what I am doing wrong. Currently I have pin 7 of my Metro Mini connected the INTA port, that based on what I am reading in Adafruit's documentation controls the A0 - A7 ports of the MCP2301. I have a button connected to port A1 on the MCP23017. When I try to run my program the button doesn't trigger anything. I have a section of code that uses serial print to read the current state of the button, but the state of the button is always high.

Unfortunately, I haven't been able to find a decent tutorial on using the Adafruit MCP23017 library that uses the INTA ports. So I tried to cobble some code together base on one of the examples that is given in the Adafruit Library.

Here is my code:

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;


boolean flag1 = false;
boolean flag2 = false;

const int LED_PIN = 12;
const int neoBtnPins[2] = {2, 3};
const int usrNodeArray[4] = {0,1,2,3};
const long debounceTime = 50;
const long interval = 5000;
unsigned long prevMillis = 0;
static uint32_t lastPressTime = 0;
uint32_t neoPixArray[8];
uint32_t rmdPixArray[4];
uint32_t usrPixArray[4];
int colorIDX = 0;
int selectIDX = 0;
int usrNodeIDX = 0;
int usrNodeInc = 0;


volatile byte state = LOW;

void cycleColor();
void selectColor();
void asnColor();
void chkColorCode();
void randomize();
uint32_t Red;
uint32_t Blue;
uint32_t Yellow;
uint32_t Green;
uint32_t Gray;
uint32_t Magenta;
uint32_t Navy;
uint32_t Brown;





#define LED_COUNT 60
#define irqPinMCU 7 //Pin 7 on Metro Mini
#define btnSubmit 1 // A1 on Adafruit MCP23017 I/O Expander


Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup()
{

  Serial.begin(9600);
  mcp.begin_I2C();
  strip.begin();           
  strip.show();            
  strip.setBrightness(10); 



  pinMode(irqPinMCU, INPUT);
  mcp.setupInterrupts(true, false, LOW);

  mcp.pinMode(btnSubmit, INPUT_PULLUP);
  mcp.setupInterruptPin(btnSubmit, HIGH);

  for(int i = 0; i < 2 ; i++ )
    {
      
      pinMode(neoBtnPins[i], INPUT);

    }
  attachInterrupt(digitalPinToInterrupt(neoBtnPins[0]), cycleColor, CHANGE);
  attachInterrupt(digitalPinToInterrupt(neoBtnPins[1]), selectColor, CHANGE);



  Red = strip.Color(139, 0, 0);
  Green = strip.Color(0, 100, 0);
  Yellow = strip.Color(139, 139, 0);
  Blue = strip.Color(0, 0, 139);
  Gray = strip.Color(192, 192, 192);
  Magenta = strip.Color(255, 69, 0);
  Navy = strip.Color(25, 25, 112);
  Brown = strip.Color(139, 69, 19);

  
  neoPixArray[0] = Red;
  neoPixArray[1] = Green;
  neoPixArray[2] = Yellow;
  neoPixArray[3] = Blue;
  neoPixArray[4] = Gray;
  neoPixArray[5] = Magenta;
  neoPixArray[6] = Navy;
  neoPixArray[7] = Brown;



  for( int n = 0; n < 4; n++ )
  {
    randomize();
    uint32_t randomColor = random(0,7);
    rmdPixArray[n] = neoPixArray[randomColor];

  }
}



void loop()
{

  strip.setPixelColor(4,rmdPixArray[0]);
  strip.setPixelColor(5,rmdPixArray[1]);
  strip.setPixelColor(6,rmdPixArray[2]);
  strip.setPixelColor(7,rmdPixArray[3]);
  strip.show();
        

  if(usrNodeInc <= 3)
    {
      if(flag2 == false)
      {
        Serial.print("usrNodeInc is ");
        Serial.print("\t");
        Serial.print(usrNodeInc);
        Serial.println();
        flag2 = true;
      }
    
    if(colorIDX <= 7 /*&& usrNodeInc < 3*/)
      {
        strip.setPixelColor(usrNodeArray[usrNodeInc], neoPixArray[colorIDX]);
        strip.show();
        
      }
    else
      {
        colorIDX = 0;
      }
    }     
    else
      {
        //chkColorCode();
      }

    /*if(!digitalRead(irqPinMCU) && millis() - lastPressTime > debounceTime)
      {
        Serial.println("Submit Button is Working");
        mcp.clearInterrupts();
      }*/

        Serial.print("irqPinMCu Status ");
        Serial.print("\t");
        Serial.print(digitalRead(irqPinMCU));
        Serial.println();
        mcp.clearInterrupts();
       

      
}

void cycleColor()
{
  if(digitalRead(neoBtnPins[0]) && millis() - lastPressTime > debounceTime) 
  {
    lastPressTime = millis();
    state = !state;
    colorIDX++;
    flag1 = false;
  }

}

void selectColor()
{
  if(digitalRead(neoBtnPins[1]) && millis() - lastPressTime > debounceTime) 
  {
    lastPressTime = millis();
    state = !state;
    asnColor();
    usrNodeInc++;
    colorIDX = 0;
    //flag2 = false;
    //flag1 = false;
    //chkColorCode();
    
    
  }

}

void asnColor()
  {
      usrPixArray[usrNodeIDX] = neoPixArray[colorIDX];
      if(flag1 == false)
        {
          Serial.print("usrPixArray");
          Serial.print("\t");
          Serial.print(usrPixArray[usrNodeIDX]);
          Serial.println();
          flag1 = true;
        }
  }
void randomize() 
  {
    uint32_t newSeed = 0;
    for (int i=0; i < 32; i++) 
      {
      uint32_t r = analogRead(A0);
      r <<= (analogRead(A1) >> 3) & 0x03;
      r += analogRead(A2);
      newSeed <<= 1;
      if (r & 0x04)  
        {
          newSeed |= 0x1;
        }
      }
  randomSeed(newSeed);
}

void chkColorCode()
  {
      //if(flag1 == false)
      //{
        for(int test01 = 0; test01 <=3; test01++)
          {
            Serial.print("usrPixArray 0 is");
            Serial.print("\t");
            Serial.print(usrPixArray[test01]);
            Serial.println();
          }
          //flag1 = true;
     // }
  }

The MCP23017 interrupt output is an open collector (or open drain) output. That means it needs to be connected to an input with the internal pull up resistors enabled.

Do not confuse this pin with interrupts on the Arduino, they can be used like that but it is not necessary. I use them to pole the chip (just read the state of this pin) to see if any input pins of the MCP23017's interrupt control register has changed.

From the data sheet:-

So to trigger the interrupt you have to set a bit in this register and if that input pin changes you will see an output on the interrupt pin.

If that bit is set to zero then you will not generate an interrupt output if anything changes on this pin.

You are best to download the data sheet for that chip and read it.

Have you tried the Adafruit mcp23xxx_interrupt.ino example by itself to see if it works?
This may help you debug the problem.

@drunyan0824

Try changing:
mcp.setupInterruptPin(btnSubmit, HIGH)
to:
mcp.setupInterruptPin(btnSubmit, CHANGE)

Sorry for the delay in my response, I got locked out of my account and it took me a week to get back in.

I loaded up the Adafruit mcp23xxx_interrupt.ino example and i think that I am having the same problem, becasue when I press the button that is supposed to trigger the interrupt on the MCP23017 I get this message on the serial monitor:

MCP23xxx Interrupt Test!
Looping...
Interrupt detected on pin: 255
Pin states at time of interrupt: 0b0

even after I release the button.

Sorry for the delay in my response, I got locked out of my account and it took me a week to get back in. I have been trying to read that datasheet and understand how I set the bit in the register. There is a line in the code in the Adafruit mcp23xxx_interrupt.ino that I am pretty sure it does exactly what you are saying.

This is the full code of the example

// NOTE: This is a simple example that only reads the INTA or INTB pin
//       state. No actual interrupts are used on the host microcontroller.
//       MCP23XXX supports the following interrupt modes:
//           * CHANGE - interrupt occurs if pin changes to opposite state
//           * LOW - interrupt occurs while pin state is LOW
//           * HIGH - interrupt occurs while pin state is HIGH

// ok to include only the one needed
// both included here to make things simple for example
#include <Adafruit_MCP23X08.h>
#include <Adafruit_MCP23X17.h>

#define BUTTON_PIN 1   // MCP23XXX pin used for interrupt

#define INT_PIN 7      // microcontroller pin attached to INTA/B

// only used for SPI
#define CS_PIN 6

// uncomment appropriate line
Adafruit_MCP23X08 mcp;
//Adafruit_MCP23X17 mcp;

void setup() {
  Serial.begin(9600);
  //while (!Serial);
  Serial.println("MCP23xxx Interrupt Test!");

  // uncomment appropriate mcp.begin
  if (!mcp.begin_I2C()) {
  //if (!mcp.begin_SPI(CS_PIN)) {
    Serial.println("Error.");
    while (1);
  }

  // configure MCU pin that will read INTA/B state
  pinMode(INT_PIN, INPUT);

  // OPTIONAL - call this to override defaults
  // mirror INTA/B so only one wire required
  // active drive so INTA/B will not be floating
  // INTA/B will be signaled with a LOW
  mcp.setupInterrupts(true, false, LOW);

  // configure button pin for input with pull up
  mcp.pinMode(BUTTON_PIN, INPUT_PULLUP);

  // enable interrupt on button_pin
  mcp.setupInterruptPin(BUTTON_PIN, LOW);

  Serial.println("Looping...");
}

void loop() {
  if (!digitalRead(INT_PIN)) {
    Serial.print("Interrupt detected on pin: ");
    Serial.println(mcp.getLastInterruptPin());
    Serial.print("Pin states at time of interrupt: 0b");
    Serial.println(mcp.getCapturedInterrupt(), 2);
    delay(250);  // debounce
    // NOTE: If using DEFVAL, INT clears only if interrupt
    // condition does not exist.
    // See Fig 1-7 in datasheet.
    mcp.clearInterrupts();  // clear
  }
}

now doesn't the line

pinMode(INT_PIN, INPUT);

set the register so that an output will be generated on the pin?

No. That just set the Arduino pin 7 as an input, so the Arduino can read that pin.

The statement, "The MCP23017 interrupt output is an open collector (or open drain) output." is not totally correct. The interrupt output can be configured as open drain OR as push-pull. In the example sketch it is set as push-pull which means it will go HIGH and LOW. So the statement about needing a pull-up is wrong.

Well that would indicate a more serious problem somewhere else unless you mean that it continually prints only this:

Interrupt detected on pin: 255
Pin states at time of interrupt: 0b0

Here is the problem

// uncomment appropriate line
Adafruit_MCP23X08 mcp;
//Adafruit_MCP23X17 mcp;

Delete the Adafruit_MCP23X08 mcp; line

Change
//Adafruit_MCP23X17 mcp;
To
Adafruit_MCP23X17 mcp;

Well, thank you. I feel really silly now. That solved the problem.

I am curious, how can you tell from the sketch that the MCP23017 is set up as a PUSH-PULL and not as a open collector?

  // OPTIONAL - call this to override defaults
  // Mirror INTA/B so only one wire required (true)
  // Active drive so INTA/B does not need a pull-up (false)
  // INTA/B will be signaled with a LOW
  mcp.setupInterrupts(true, false, LOW);

The middle argument is set to false, that means use push-pull (Active drive)
true = opendrain

Note:- having a push pull output means that you will not be able to use both interrupts with the same pin on the Arduino. To do that you need them to be open collector / drain.

So instead of just using Pin 7 of the arduino, I would have use pins 7 and 8 both INTA and INTB?

Not sure what you mean here?
do you mean

I would have to use pins 7 and 8 both INTA and INTB?

Yes that would mean two pins to check and two pins to trigger two separate interrupts.

No. The way it is now, any activation of the A Pins or B pins will cause an interrupt on INTA OR INTB. so you only need one pin.
Again you have been misinformed.

At this point, if I am understanding things correctly, since the MCP23017 is setup in push/pull mode, I can trigger an interrupt on any INTA or INTB pins and use one pin of the Arduino to read the change of the pin.

If I were to set the MCP23017 as an open drain(if that is the correct term) then I would have to use two pins on the arduino because one of the arduino pins would be polling the INTA pins and the other Arduino pin would be polling the INTB pins.

No.
The fact that you can use INTA or INTB for both A and B pins or separately has nothing to do with whether the outputs are push-pull or open drain.

// OPTIONAL - call this to override defaults
// Mirror INTA/B so only one wire required (true)
// Active drive so INTA/B does not need a pull-up (false)
// INTA/B will be signaled with a LOW
mcp.setupInterrupts(true, false, LOW);

See the comment about "Mirror".

It is the first argument and is set to true. That means that button activation of A OR B pins will cause an interrupt on BOTH INTA and INTB outputs and has nothing to do with open-drain or push-pull. If that argument is set to false, then A pin button activation will cause an interrupt on INTA only and B pin interrupts go to INTB only.

The LOW means the interrupt will go from HIGH to LOW when it occurs.
The library is poorly documented like most Adafruit libraries.