MCP23017 and interrupts, no library

An MCP23017 is set up for:

  1. bank = 0 (A and B addresses are sequential)
  2. Mirror = 1 (INTA and INTB internally connected)
  3. port A = output
  4. port B = input, pull-up
  5. normally-open switch connects GPB0 to ground
  6. GPB1 to GPB7 connected with resistor to ground
  7. all resistor values >1k
  8. I2C address = 0x20
  9. interrupt-on-change enabled
  10. change versus previous value is enabled (INTCON = 0b00000000, default value)

So when pushing the button, the value on GPIOB goes from 1 to 0, and the interrupt is is set until register INTCAPB is read.
The LED on INTB signals the status of that output: when an interrupt occurs INTB goes low and the LED on that pin lights up.

However, the serial monitor output shows following values, regardless of whether the button is pushed or not:
INTFB = : 11110000 INTCAPB = : 10001000

Why does the serial output not show the status of INTCAPB as I would expect: 00000001 when released, 00000000 when pressed?

And why does INTFB show 11110000? I would have expected 00000001 after an interrupt on GPB0?

The schematic:

#include <Wire.h>

// MCP23017 registers (everything except direction defaults to 0)
#define IODIRA   0x00   // IO direction  (0 = output, 1 = input (Default))
#define IODIRB   0x01
#define IOPOLA   0x02   // IO polarity   (0 = normal, 1 = inverse)
#define IOPOLB   0x03
#define GPINTENA 0x04   // Interrupt on change (0 = disable, 1 = enable)
#define GPINTENB 0x05
#define DEFVALA  0x06   // Default comparison for interrupt on change (interrupts on opposite)
#define DEFVALB  0x07
#define INTCONA  0x08   // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
#define INTCONB  0x09
#define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
//#define IOCON 0x0B  // same as 0x0A
#define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
#define GPPUB    0x0D
#define INFTFA   0x0E   // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
#define INFTFB   0x0F
#define INTCAPA  0x10   // Interrupt capture (read only) : value of GPIO at time of last interrupt
#define INTCAPB  0x11
#define GPIOA    0x12   // Port value. Write to change, read to obtain value
#define GPIOB    0x13
#define OLLATA   0x14   // Output latch. Write to latch output.
#define OLLATB   0x15


#define port 0x20  // MCP23017 is on I2C port 0x20: base address A0, A1 A2 = 0

#define ISR_INDICATOR 12  // pin 12
#define ONBOARD_LED 13    // pin 13

volatile bool keyPressed;

// set register "reg", and next register, on expander to "data"
// for example, IO direction
void expanderWriteBoth (const byte reg, const byte data )
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.write (data);  // port A
  Wire.write (data);  // port B
  Wire.endTransmission ();
} // end of expanderWrite

// set register "reg" on expander to "data"
void expanderWriteSingle (const byte reg, const byte data)
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.write (data);  // port
  Wire.endTransmission ();
}

// read a byte from the expander
unsigned int expanderRead (const byte reg)
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.endTransmission ();
  Wire.requestFrom (port, 1);
  return Wire.read();
} // end of expanderRead

// interrupt service routine, called when pin D2 goes from 1 to 0
void keypress ()
{
  digitalWrite (ISR_INDICATOR, HIGH);  // debugging
  keyPressed = true;   // set flag so main loop knows
}  // end of keypress

void setup ()
{
  pinMode (ISR_INDICATOR, OUTPUT);  // for testing (ISR indicator)
  pinMode (ONBOARD_LED, OUTPUT);  // for onboard LED

  Wire.begin ();
  Serial.begin (115200);
  Serial.println ("Starting ...");

  // expander GPIO register setting
  expanderWriteSingle (IODIRA, 0b00000000);

  // expander configuration register
  expanderWriteBoth (IOCON, 0b01100000); // mirror interrupts, disable sequential mode

  // enable pull-up on switches
  expanderWriteBoth (GPPUA, 0xFF);   // pull-up resistor for switch - both ports

  // invert polarity
  expanderWriteBoth (IOPOLA, 0xFF);  // invert polarity of signal - both ports

  // enable all interrupts
  expanderWriteBoth (GPINTENA, 0xFF); // enable interrupts - both ports

  // no interrupt yet
  keyPressed = false;

  // read from interrupt capture ports to clear them
  expanderRead (INTCAPA);
  expanderRead (INTCAPB);

  // pin 19 of MCP23017 is plugged into D2 of the Arduino which is interrupt 0
  attachInterrupt(0, keypress, FALLING);

}  // end of setup

// time we turned LED on
unsigned long time = 0;

// called from main loop when we know we had an interrupt
void handleKeypress ()
{
  unsigned int keyValue = 0;

  delay (100);  // de-bounce before we re-enable interrupts

  keyPressed = false;  // ready for next time through the interrupt service routine
  digitalWrite (ISR_INDICATOR, LOW);  // debugging

  // Read port values, as required. Note that this re-arms the interrupts.
  if (expanderRead (INFTFA))
    keyValue |= expanderRead (INTCAPA) << 8;    // read value at time of interrupt
  if (expanderRead (INFTFB))
    keyValue |= expanderRead (INTCAPB);        // port B is in low-order byte

  Serial.println ("Button states");
  Serial.println ("0                   1");
  Serial.println ("0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5");

  // display which buttons were down at the time of the interrupt
  for (byte button = 0; button < 16; button++)
  {
    // this key down?
    if (keyValue & (1 << button))
      Serial.print ("1 ");
    else
      Serial.print ("0 ");

  } // end of for each button

  Serial.println ();

  // if a switch is now pressed, turn LED on  (key down event)
  if (keyValue)
  {
    time = millis ();  // remember when
    digitalWrite (ONBOARD_LED, HIGH);  // on-board LED
  }  // end if

}  // end of handleKeypress

void loop ()
{
  // was there an interrupt?
  if (keyPressed)
    handleKeypress ();

  // turn LED off after 500 ms
  if (millis () > (time + 500) && time != 0)
  {
    digitalWrite (ONBOARD_LED, LOW);
    time = 0;
  }  // end if time up

}  // end of loop

Link to MCP23017 data sheet. Please remember to post link to data sheet when you have questions about the chip, as a courtesy to anyone who might want to help.

Why do you have resistors on each pin of the B port? They don't seem necessary, and depending on the value (you said >1K but that could be 100K or 10M), could cause the pins to float and pick up random noise if the resistors are high values, by balancing out the effect of the internal pull-up resistors.

I would suggest checking each use of the expanderWriteBoth() function and ask yourself if it is the correct thing to be doing, given that you are using the two ports for completely different purposes. For example, why do you enable internal pull-up resistors on pins with LEDs connected to them?

You have misunderstood what the INT pin does on this chip. You have wired it up so that it resets the chip itself, this is wrong.

The INT pins are designed for connecting to an Arduino input. This is so you can monitor selected pins without doing a read of the whole chip. These pins are optional and you can just leave them unconnected if you like. If you connect them to Arduino pins make sure you enable the internal pull up resistors on those pins.

Pull up resistors of 4K7 on both I2C lines will make the chip more reliable.

I don't think so, Mike. Reset is connected to Vcc. You are of course correct saying that it should be connected to an Arduino pin, but I think that is just an omission, like the connections to SDA/SCL.

Yes you are right, just had a second look.

The resistors are 2.2k.
The internal pull-up is configured on the inputs only (B bank).
SCL and SDA are connected to A5 and A4.

I agree that resistors on the B port are not necessary because pull-ups already are configured there. But removing them does not solve the issue.

About the register setup (expanderWriteBoth and expanderWriteSingle), in my opinion they are correctly configured and explained in my OP. But again, I may be wrong?

Why would the INT pin be wired so that it resets itself? With the LED on INTB I can clearly see that the interrupt gets reset when reading INTCAPB, so that works as expected.

But again: why the unexpected readouts from INTFB and INTCAPB?

The comment says "both ports", and if you look at how expanderWriteBoth() is defined:

void expanderWriteBoth (const byte reg, const byte data )
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.write (data);  // port A
  Wire.write (data);  // port B
  Wire.endTransmission ();
} // end of expanderWrite

it does indeed write to both ports A and B, because that second Wire.write() will write to the next address, which is the pull-up register for port B:

#define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
#define GPPUB    0x0D

So I repeat my suggestion

because maybe one of those uses of expanderWriteBoth() is causing some strange effects.

1 Like

Oh stupid me!!! I did post/paste the wrong code in my OP, I am so sorry, here below the correct code, as in use now and at posting time.
My comments and explanatory notes in my OP however stand and are as they should be.

#include <Wire.h>
#define INTPIN 3   // Interrupt on this Arduino Uno pin.
#define controlArduinoInt attachInterrupt(digitalPinToInterrupt(INTPIN),isr,FALLING)  // serve interrupt when falling, ie when button is released

// MCP23017 registers (everything except direction defaults to 0)
#define IODIRA   0x00   // IO direction  (0 = output, 1 = input (Default))
#define IODIRB   0x01
#define IOPOLA   0x02   // IO polarity   (0 = normal, 1 = inverse)
#define IOPOLB   0x03
#define GPINTENA 0x04   // Interrupt on change (0 = disable, 1 = enable)
#define GPINTENB 0x05
#define DEFVALA  0x06   // Default comparison for interrupt on change (interrupts on opposite)
#define DEFVALB  0x07
#define INTCONA  0x08   // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
#define INTCONB  0x09
#define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
//#define IOCON 0x0B  // same as 0x0A
#define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
#define GPPUB    0x0D
#define INTFA   0x0E   // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
#define INTFB   0x0F
#define INTCAPA  0x10   // Interrupt capture (read only) : value of GPIO at time of last interrupt
#define INTCAPB  0x11
#define GPIOA    0x12   // Port value. Write to change, read to obtain value
#define GPIOB    0x13
#define OLLATA   0x14   // Output latch. Write to latch output.
#define OLLATB   0x15

#define port1 0x20  // MCP23017 is on I2C port 0x20, A0 = 0, A1 = 0, A2 = 0
#define ISR_INDICATOR 12  // pin 12
#define ONBOARD_LED 13    // pin 13
byte value;
byte value2;

//////////////////////////////////////////////

void expanderWriteBoth (const byte port, const byte reg, const byte data)
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.write (data);  // port A
  Wire.write (data);  // port B
  Wire.endTransmission ();
} // end of expanderWrite

//////////////////////////////////////////////

void expanderWriteSingle (const byte port, const byte reg, const byte data)
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.write (data);  // port
  Wire.endTransmission ();
}

//////////////////////////////////////////////

unsigned int expanderRead (const byte port, const byte reg)
{
  Wire.beginTransmission (port);
  Wire.write (reg);
  Wire.endTransmission ();
  Wire.requestFrom (port, 1);
  return Wire.read();
} // end of expanderRead

//////////////////////////////////////////////

void setup() {

  Wire.begin ();
  Serial.begin(9600);
  delay(100);
  Serial.println("Arduino_MCP23017_basic_I2C_interrupt_v2.ino");
  // Serial.println("YourDuino.com terry@yourduino.com");
  Serial.println();
  delay(1000);
  pinMode(INTPIN, INPUT);
  pinMode (ISR_INDICATOR, OUTPUT);  // for testing (ISR indicator)
  pinMode (ONBOARD_LED, OUTPUT);  // for onboard LED
  expanderWriteBoth (port1, IOCON, 0b01000000); // mirror interrupts, enable sequential mode, disable ODR, active low interrupt
  expanderWriteSingle (port1, IODIRA, 0b00000000); // 1 = input, 0 = output
  expanderWriteSingle (port1, GPPUB, 0b11111111); // 1 = pull-up, 0 = no pull-up
  expanderWriteSingle (port1, GPINTENB, 0xFF); // enable interrupts - B port

  // read from interrupt capture ports to clear them
  expanderRead (port1, INTCAPA);
  expanderRead (port1, INTCAPB);

  // controlArduinoInt; // Enable Arduino interrupt control.
}

void loop() {

  expanderWriteSingle (port1, GPIOA, 0b00000000);
  delay(400);
  expanderWriteSingle (port1, GPIOA, 0b10000000); // write to GPA7 (pin 28)
  delay(400);


  expanderRead (port1, INTFB);  // read on which pin of B the interrupt occured
  Serial.print("    INTFB =  : ");
  value = INTFB;
  for (byte i = 0; i < 8; i++) {
    Serial.print((value & (1 << i)) ? 1 : 0);
  }
  // Serial.println(INFTFB);
  delay(300);

  expanderRead (port1, INTCAPB); // read the value on GPIOB; this read causes reset of the interrupt register
  Serial.print("    INTCAPB =  : ");
  value = INTCAPB;
  value2 = INTCAPB;
  for (byte i = 0; i < 8; i++) {
    Serial.print((value & (1 << i)) ? 1 : 0);
  }
  Serial.println();

}
  Serial.print("    INTFB =  : ");
  value = expanderRead (port1, INTFB);  // read on which pin of B the interrupt occured
  for (byte i = 0; i < 8; i++) {
    Serial.print((value & (1 << i)) ? 1 : 0);
  }
1 Like

Oh thanks so much Paul; I could stomp myself.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.