Port expander MCP23017

About the post

In my post about the port expansion for the ESP-01 I had already briefly described the MCP23017, here I would like to go into detail about its many features. In the first part of the post I would like to show you how to use the MCP23017 (and the MCP23S17) with the help of a library. The second part is a deep dive for those who are interested in the internal details. I will refer to the numerous registers of the MCP23017.

MCP23017 – an overview

The MCP23017 is a 16-bit I/O port expander with convenient interrupt functions. The 16 I/O pins are organized in two ports (A and B), which are addressed separately (byte mode) or together (sequential mode). The supply voltage should be between 1.8 and 5.5 volts. The maximum current at the I/O pins is 25 mA in both directions. In total, the input current to VDD should not exceed 125 mA and not more than 150 mA should flow into VSS (GND). Communication takes place via I2C. A data sheet for the MCP23017 can be found here.

In terms of its flexibility, the MCP23017 is the Swiss army knife among the common port expanders, if you compare it with the 74HC595 shift register or the PCF8574, for example.<<<<<

Pinout of the MCP23017

Pinout of the MCP23017
Pinout of the MCP23017

The 16 I/O pins are named GPA0 to GPA7 or GPB0 to GPB7 according to the two ports. The power supply is via VDD and VSS. The connection of pins A0, A1 and A2 determines the I2C address according to the following scheme:

1 0 0 A2 A1 A0

For example, if A0 to A2 are LOW, then the address is 100000 (binary) = 32 (decimal) = 0x20 (hexadecimal). SDA and SCL are the two I2C pins. The reset pin is active-low. INTA and INTB are the interrupt pins for the two ports. You can set the polarity of the interrupt pins. You can also connect both interrupt pins (mirror function).

Controlling the MCP23017 with the library

I have developed a library that you can find and download here on Github. You can also install the library directly from the Arduino IDE library manager. The library is designed to address the two ports A and B in byte mode, i.e. separately. Maybe I’ll add sequential mode to the library at some point.

Simple input/output applications

The functionality of the I/O pins is comparable to the functionality of the Arduino I/O pins. Thus, the pins can be used as input or output, they are switched HIGH or LOW and they can act as both a (limited) power supplier and a power sink. If they are set as   input, the pins can serve as interrupt pins. But in the first example, we start easy and only 16 LEDs are controlled.

Circuit for controlling 16 LEDs
Circuit for controlling 16 LEDs

The address pins in my example are LOW, so the I2C address is 0x20. The reset pin is connected to the Arduino pin 5. The pins of port A and B each control eight LEDs of an LED bar. The I2C lines SDA and SCL should get pull-up resistors of 4.7 kOhm. Without the pull-ups, however, it also worked well for me.

In this example, the following functions are used (the MCP23017 object is called myMCP):
  • MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN) creates the MCP23017 object. You have to pass the I2C address to the constructor. Moreover, you can pass the reset pin and / or a Wire object. The latter option enables you to use e.g. the two I2C busses of an ESP32. You can also save a pin of your MCU by connecting the reset pin to VDD. In this case, you should pass a dummy reset pin of >= 99. This will trigger a software reset instead of a hardware reset in init().
  • myMCP.Init(); initializes the object with some presets
  • myMCP.setPinMode( pin,port,direction ); similar to the Arduino pinMode function, whereby the port is added as a parameter.
    • as direction, you can use: INPUT/OUTPUT/INPUT_PULLUP, OFF/ON or 0/1
    • “0”= INPUT, “1” =OUTPUT
  • myMCP.setPortMode( value,port ); set pinMode for an entire port; “value” is reasonably given as a binary number
  • myMCP.setPortMode( value,port,INPUT_PULLUP); with this variant all input pins are pulled up; no effect on output pins.
  • myMCP.setPin( pin,port,level ); similar to the digitalWrite function;
    •  permissible parameters for level are: 0/1, OFF/ON, LOW/HIGH
  • myMCP.setPort( value,port ); digitalWrite for an entire port; it makes sense to write “value” as a binary number
  • myMCP.togglePin( pin,port ); switches a pin from LOW to HIGH or from HIGH to LOW
  • myMCP.setPinX( pin,port,direction,level ); “extended version” of the setPin function or combination of setPinMode and setPin
  • myMCP.setPortModeX( direction,value,port ); “extended version” der setPort Funktion
  • myMCP.setAllPins( port,level ); sets all pins of a port to LOW or HIGH

Example sketch for simple input/output applications

Here is a sketch that plays with these functions and illustrates them:

#include <Wire.h>
#include <MCP23017.h>
#define MCP_ADDRESS 0x20 // (A2/A1/A0 = LOW)
#define RESET_PIN 5  

/* There are several ways to create your MCP23017 object:
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS)            -> uses Wire / no reset pin (if not needed)
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN)  -> uses Wire / RESET_PIN
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS)    -> uses the TwoWire object wire2 / no reset pin
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS, RESET_PIN) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN);

int wT = 1000; // wT = waiting time

void setup(){ 
  Serial.begin(9600);
  Wire.begin();
  if(!myMCP.Init()){
    Serial.println("Not connected!");
    while(1){} 
  }
  myMCP.setPortMode(0b11111101, A);  // Port A: all pins are OUTPUT except pin 1
  myMCP.setPortMode(0b11111111, B);  // Port B: all pins are OUTPUT
  delay(wT);
  myMCP.setAllPins(A, ON); // alle LEDs switched on except A1
  delay(wT);
  myMCP.setPinX(1, A, OUTPUT, HIGH); // A1 switched on 
  delay(wT); 
  myMCP.setPort(0b11110000, B); // B4 - B7 switched on
  delay(wT);
  myMCP.setPort(0b01011110, A); // A0,A5,A7 switched off
  delay(wT);
  myMCP.setPinX(0,B,OUTPUT,HIGH); // B0 switched on
  delay(wT);
  myMCP.setPinX(4,B,OUTPUT,LOW); // B4 switched off
  delay(wT);
  myMCP.setAllPins(A, HIGH); // A0 - A7 all on
  delay(wT);
  myMCP.setPin(3, A, LOW); // A3 switched off
  delay(wT);
  myMCP.setPortX(0b11110000, 0b01101111,B); // at port B only B5,B6 are switched on
  delay(wT);
  myMCP.setPinMode(0,B,OUTPUT); // B0 --> OUTPUT
  for(int i=0; i<5; i++){  // B0 blinking
    myMCP.togglePin(0,B); 
    delay(200);
    myMCP.togglePin(0,B);
    delay(200);
  }
  for(int i=0; i<5; i++){ // B7 blinking
    myMCP.togglePin(7,B);
    delay(200);
    myMCP.togglePin(7,B);
    delay(200);
  }
}

void loop(){ 
} 

In my example, the current (technical current direction Plus – Minus) flows > from the MCP23017 through the LEDs to GND. Instead, the MCP23017 could of course also be used as a current sink. Then an LED would light up if the corresponding pin is OUTPUT and LOW and voltage is supplied, comparable with how the Arduino works.

Read pin status

The following functions are used to query the pin status in the GPIO register:
  • myMCP.getPin( pin,port );  returns the level of one pin (as bool)
  • myMCP.getPort( port ); returns the status of an entire port (as byte), i.e. the content of the GPIO register.
Here is an example circuit for you:
Schaltplan zum Testen der GPIO-Lesefunktionen
Schaltplan zum Testen der GPIO-Lesefunktionen
Here is the corresponding example sketch:
#include <Wire.h>
#include <MCP23017.h>
#define MCP_ADDRESS 0x20 // (A2/A1/A0 = LOW)

/* A hardware reset is performed during init(). If you want to save a pin you can define a dummy 
 * reset pin >= 99 and connect the reset pin to HIGH. This will trigger a software reset instead 
 * of a hardware reset. 
 */
#define RESET_PIN 5
/* There are several ways to create your MCP23017 object:
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS)            -> uses Wire / no reset pin (if not needed)
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN)  -> uses Wire / RESET_PIN
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS)    -> uses the TwoWire object wire2 / no reset pin
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS, RESET_PIN) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN);

int wT = 1000; // wT = waiting time
byte portStatus;
bool pinStatus;

void setup(){ 
  Serial.begin(9600);
  Wire.begin();
  if(!myMCP.Init()){
    Serial.println("Not connected!");
    while(1){} 
  }  
  myMCP.setPortMode(0b00000000, A);  // Port A: all pins are INPUT
  myMCP.setPortPullUp(0b11110000, A);  // Port A: Pin 4 - 7 are pulled up
}

void loop(){ 
  
  portStatus = myMCP.getPort(A); // query the complete port status 
  Serial.print("Status GPIO A: ");
  Serial.println(portStatus, BIN);
  
  pinStatus = myMCP.getPin(5, A); // query one pin status
  Serial.print("Status Port A, Pin 5: ");
  Serial.println(pinStatus, BIN);
 
  Serial.println("-------------------------------------");
  delay(1000);
}
You can add an internal pull-up to I/Os configured as INPUT:
  • myMCP.setPinPullUp( pin,port,level ); sets a pull-up with a 100 kOhm resistor
  • myMCP.setPortPullUp( value,port ); is the equivalent for an entire port
To pull down inputs, have to add external pull-down resistors. And here is the output of the example sketch:
Ausgabe von mcp23017_gpio_reading.ino
Ausgabe von mcp23017_gpio_reading.ino

Interrupt-on-Change

All 16 I/O pins can be configured as interrupt pins. There are two modes, the interrupt-on-change and the interrupt-on-defval-deviation. I start with the interrupt-on-change function, where every switch from LOW to HIGH or HIGH to LOW triggers an interrupt. The interrupt induces a polarity change at the respective interrupt output INTA or INTB. Alternatively, you can merge INTA and INTB. You can also set the polarity of the interrupt outputs.

Only pins set as INPUT can act as interrupt pins, but this setting is done in the background by the library.

I have implemented the following functions for interrupt-on-change:

  • myMCP.setInterruptOnChangePin( pin,port ); sets up a single pin as an interrupt-on-change pin
  • myMCP.setInterruptOnChangePort( value,port ); sets up multiple or all pins of a port as an interrupt-on-change pin
  • myMCP.setInterruptPinPol( level ); sets the level of the active interrupt output
    • level = HIGH — > active-high, level = LOW — > active-low (default)
    • I have implemented only one setting for both outputs, i.e. both active-high or both active-low
  • myMCP.setIntOdr( value ); value = 1 or ON — > interrupt output pins go into the open drain state, the interrupt pin polarity is overwritten; value = 0 or OFF — > active-low or active-high (both)
  • myMCP.deleteAllInterruptsOnPort( port ); undo the setup of the interrupt pins
  • myMCP.setIntMirror ( value ); value = 1 or ON — > INTA / INTB are mirrored, value = 0 or OFF — > INTA / INTB are responsible for their ports separately (default)
  • myMCP.getIntFlag( port ); returns the value of the interrupt flag register as byte. In the interrupt flag register the bit is set which represents the pin responsible for the last interrupt
  • myMCPgetIntCap( port ); returns the value of the interrupt capture register. It contains the value of the GPIO register at the time of the interrupt.

For testing, I have set up the following circuit:

Circuit for testing the Interrupt on Change function with the MCP23017
Circuit for testing the interrupt-on-pin-change function

The pins of port B are set up as interrupt pins and receive HIGH signals via the buttons. The LEDs connected to port A should indicate on which pin the interrupt occurred.

Example sketch for interrupt-on-change

#include <Wire.h>
#include <MCP23017.h>
#define MCP_ADDRESS 0x20 // (A2/A1/A0 = LOW)
#define RESET_PIN 5 
int interruptPin = 3;
volatile bool event = false;
byte intCapReg; 

/* There are several ways to create your MCP23017 object:
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS)            -> uses Wire / no reset pin (if not needed)
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN)  -> uses Wire / RESET_PIN
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS)    -> uses the TwoWire object wire2 / no reset pin
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS, RESET_PIN) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN);

void setup(){ 
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), eventHappened, RISING);
  Serial.begin(9600);
  Wire.begin();
  if(!myMCP.Init()){
    Serial.println("Not connected!");
    while(1){} 
  }
  myMCP.setPortMode(0b11111111,A);
  myMCP.setPort(0b11111111, A); // just an LED test 
  delay(1000); 
  myMCP.setAllPins(A, OFF);
  delay(1000);
  myMCP.setInterruptPinPol(HIGH);
  delay(10);
  myMCP.setInterruptOnDefValDevPort(0b11111111, B, 0b00001111); // interrupt pins, port, DEFVALB
  myMCP.setPortPullUp(0b00001111, B); // pull-up for B0-B3
  event=false;
}  

void loop(){ 
  intCapReg = myMCP.getIntCap(B);
  if(event){
    delay(200);
    byte intFlagReg, eventPin; 
    intFlagReg = myMCP.getIntFlag(B);
    eventPin = log(intFlagReg)/log(2);
    intCapReg = myMCP.getIntCap(B);
    Serial.println("Interrupt!");
    Serial.print("Interrupt Flag Register: ");
    Serial.println(intFlagReg,BIN); 
    Serial.print("Interrupt Capture Register: ");
    Serial.println(intCapReg,BIN); 
    Serial.print("Pin No.");
    Serial.print(eventPin);
    Serial.print(" went ");
    if((intFlagReg&intCapReg) == 0){ // HIGH/LOW or LOW/HIGH change?
      Serial.println("LOW");
    }
    else{
      Serial.println("HIGH");
    }
    myMCP.setPort(intFlagReg, A);
    delay(1000);
    event = false;
  }
}

void eventHappened(){
  event = true;
}

Output of the Interrupt-on-Pin-Change sketch
Output of the Interrupt-on-Pin-Change sketch

Note 1: When the button is pressed quickly, the HIGH-LOW interrupt is ignored. 

Note 2: The interrupt remains active until a getIntCap or getPort query is made. When things go bad because you query at the wrong time or the next interrupt comes at the wrong time, the interrupt will remain unintentional active. That’s why I inserted an additional getIntCap query in line 28, which seems redundant at first glance. This ensures that the MCP23017 is ready for the next interrupt. The problem becomes particularly relevant for Interrupts-on-DefVal-Deviation. 

Interrupt-on-DefVal-Deviation

Here, the polarity of the interrupt pins is compared with the state defined in the so-called DEFVAL register. A deviation triggers an interrupt. If you read the interrupt capture or GPIO register, you delete the interrupt. However, if the interrupt condition is still met at this time, the next interrupt is triggered immediately.

For this interrupt method, I have implemented the following additional functions:

  • myMCP.setInterruptOnDefValDevPin( intpin,port,defvalstate ); intpin is the interruptpin, defvalstate is the default level and a deviation from it leads to the interrupt
  • myMCP.setInterruptOnDefValDevPort( intpins,Port,defvalstate );
    • intpins are the interruptpins, e.g. B10000001 would set the pins 0 and 7 as interrupt pins.
    • defvalstate is the value of the DEFVAL register; a deviation leads to the interrupt

As an example, I have set up the following circuit:

Circuit for testing the interrupt-on-DefVal-Deviation function of the MCP23017
Circuit for testing the Interrupt-on-DefVal-Deviation function

The port B pins are defined as interrupt pins. B0 to B3 are set HIGH with internal pull-ups, while B4 to B7 get pull-down resistors. The polarity on the respective pin is reversed by pushing the button. Port A is used to display the pin responsible for the interrupt, as in the last example.

Example sketch for interrupt-on-defval-deviation

#include <Wire.h>
#include <MCP23017.h>
#define MCP_ADDRESS 0x20 // (A2/A1/A0 = LOW)
#define RESET_PIN 5 
int interruptPin = 3;
volatile bool event = false;
byte intCapReg; 

/* There are several ways to create your MCP23017 object:
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS)            -> uses Wire / no reset pin (if not needed)
 * MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN)  -> uses Wire / RESET_PIN
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS)    -> uses the TwoWire object wire2 / no reset pin
 * MCP23017 myMCP = MCP23017(&wire2, MCP_ADDRESS, RESET_PIN) -> all together
 * Successfully tested with two I2C busses on an ESP32
 */
MCP23017 myMCP = MCP23017(MCP_ADDRESS, RESET_PIN);

void setup(){ 
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), eventHappened, RISING);
  Serial.begin(9600);
  Wire.begin();
  if(!myMCP.Init()){
    Serial.println("Not connected!");
    while(1){} 
  }
  myMCP.setPortMode(0b11111111,A);
  myMCP.setPort(0b11111111, A); // just an LED test 
  delay(1000); 
  myMCP.setAllPins(A, OFF);
  delay(1000);
  myMCP.setInterruptPinPol(HIGH);
  delay(10);
  myMCP.setInterruptOnDefValDevPort(0b11111111, B, 0b00001111); // interrupt pins, port, DEFVALB
  myMCP.setPortPullUp(0b00001111, B); // pull-up for B0-B3
  event=false;
}  

void loop(){ 
  intCapReg = myMCP.getIntCap(B);
  if(event){
    delay(200);
    byte intFlagReg, eventPin; 
    intFlagReg = myMCP.getIntFlag(B);
    eventPin = log(intFlagReg)/log(2);
    intCapReg = myMCP.getIntCap(B);
    Serial.println("Interrupt!");
    Serial.print("Interrupt Flag Register: ");
    Serial.println(intFlagReg,BIN); 
    Serial.print("Interrupt Capture Register: ");
    Serial.println(intCapReg,BIN); 
    Serial.print("Pin No.");
    Serial.print(eventPin);
    Serial.print(" went ");
    if((intFlagReg&intCapReg) == 0){ // HIGH/LOW or LOW/HIGH change?
      Serial.println("LOW");
    }
    else{
      Serial.println("HIGH");
    }
    myMCP.setPort(intFlagReg, A);
    delay(1000);
    event = false;
  }
}

void eventHappened(){
  event = true;
}

 

The crucial difference to interrupt-on-change is that in this method the polarity change leads only in one direction to the interrupt. The output looks like this:

Output of Interrupt-on-DefVal-Dev sketch
Output of Interrupt-on-DefVal-Dev sketch

Der MCP23S17

The only difference between the MCP23S17 and the MCP23017 is the communication via SPI. Therefore the pinout is slightly different. All Functions are identical. In my library you find an example sketch and the wiring.

MCP23017 / MCP23S17 intern

Here, as announced, some additional detailed information about the MCP23017 for those who still want it. The MCP23017 is just one representative of the larger MCP23XXX family, which differ from each other in the number of I/O pins, the control (I2C vs SPI) and the external connection. The MCP23017 is probably the most popular representative. I have described the complete family in a seperate post, which you find here.

The registers of the MCP23017

First of all, you have to decide how to address the registers. For this, you can set the BANK bit in the IOCON Register. If this is   set to 1, the port registers are in two separate banks. If, on the other hand, it is set to 0, the registers are in the same bank and the addresses are sequential. I chose the latter and did not implement the alternative as an option. The registers are thus defined as follows:

Register overview of the MCP23017 for IOCON.BANK = 0
Register overview of the MCP23017 if IOCON.BANK = 0

IODIR – I/O direction register

The IODIR register determines whether the pins are INPUT or OUTPUT pins. It is the only register with a start or reset value of 0b11111111. “1” means INPUT, “0” means OUTPUT, which sounds illogical to me, but maybe I am just used to the different definition in the world of Arduino. But because this irritates me, I have turned the values in my library accordingly. With myMCP.setPortMode() and myMCP.setPinMode() the IODIR register is addressed directly. For example, myMCP.setPortMode(B11111111, A) means that all pins of the port are OUTPUT pins.

IPOL – input polarity register

If you set the bits in this register, the inverted level of the pins is stored in the corresponding GPIO register. Because I could not imagine for what I could use it, I did not implement access to this register in my library.

GPINTEN – Interrupt-on-Change control register

This register controls which pins are used as interrupt pins. 0 = disable, 1 = enable. If you only want to implement interrupt-on-change, no further settings are necessary. However, in case you want to implement interrupt-on-defval-deviation, you have to make additional settings in the DEFVAL and INTCON registers. The GPINTEN register is accessed indirectly in my library via the setInterruptOnChangePin() and setInterruptOnDefValDevPin() functions or their counterparts for entire ports.

DEFVAL – default value register

This register is required to set up Interrupts-on-Defval-Deviation. If a value in the GPIO register differs from the DEFVAL register, an interrupt is triggered if the corresponding settings have been made in the GPINTEN and INTCON registers.

INTCON – interrupt control register

This register determines the conditions under which interrupts are triggered:

  • “0”: Comparison with previous pin status (interrupt-on-change)
  • “1”: Comparison with DEFVAL (Interrupt-on-DefVal-Deviation)

However, the settings are only effective on the pins for which the corresponding bits have been set in GPINTEN.

IOCON – I/O expander configuration register

In this register, some special settings can be made for the MCP23017.

  • BANK – Addressing mode for the registers
    • “1”: registers are in separate banks
    • “0”: registers are in the same bank
    • I did not implement an option to change that in my library
  • MIRROR – is adjustable in my library via setIntMirror()
    • “1”: INTA and INTB are connected (mirrored)
    • “0”: INTA and INTB are separately responsible for port A and B respectively
  • SEQOP – sequential addressing
    • “1”: disabled – the address pointer is not incremented
    • “0”: enabled – the address pointer is automatically incremented
    • I did not implement the option to change that in my library
  • DISSLW – Adjustment of the slope of the SDA output
    • “1”: disabled
    • “0”: enabled
    • not implemented in my library
  • HAEN – not relevant for the MCP23017
  • ODR – open drain for the interrupt pins INTA and INTB
    • “1”: open drain is active – overrides the INTPOL setting
    • “0”: disabled – polarity of the interrupt signal is determined by INTPOL
    • setting is done via setIntODR(); only one common setting for both pins is implemented in my library (either both 0 or both 1)
  • INTPOL – polarity of interrupt pins
    • “1”: active-high
    • “0”: active-low
    • in my library only one common setting of both pins is implemented
  • GPPU – GPIO pull-up register
    • “1”: pull-up with 100 kOhm resistor
    • “0”: no pull-up
    • implemented in my library by setPinPullUp() or setPortPullUp()
    • impacts only pins configured as input

INTF – interrupt flag register (read-only)

This register records which pin caused the last interrupt. The set bit reveals the “guilty” one. In my library, getIntFlag() queries the register content.

INTCAP – interrupt capture value register (read-only)

This register records the contents of the GPIO register at the time of the last interrupt. I implemented the query through the getIntCap() function.

GPIO – general purpose I/O port register

Contains the pin status (HIGH/LOW). Changes to the register also change the OLAT (Output Latch) register. In my library, write access is only implemented indirectly via other functions. Read access in my library is provided by getPin() or getPort().

OLAT – output latch register

Reading returns the state of the register, not the port’s state (this would be done through the GPIO Read). This means, for example, if a pin is set as LOW, but an external HIGH is attached, then you will read it as LOW here, whereas in the GPIO register you would read it as HIGH.

21 thoughts on “Port expander MCP23017

  1. Thank you for this post, Wolf. It was very useful for my home project.

    Specifically, Microchip’s documentation is unclear about how the latch register works. Your explanation is better.

    Cheers! …from the Sierra Foothills of California

    1. Thanks for this motivating comment! It’s great to get feedback from so far away. Greetings from Germany to California! Best wishes, Wolfgang

  2. Wolfgang Ewald
    Many thanks for replies.
    One query arise relate to Interrupt On Change.
    If we use all features and enable Interrupt On Change & other registers of MCP23017 and corresponding pins
    and not enable ISR or Attach Interrupt in Arduino code at all.
    We plan to deal only in LOOP().
    Will the register return the result of both corresponding register.
    For how much time that stay in register and when are they cleared.
    Regards

    Athar

    1. The data sheet says for the INTCAP registers:
      The register remains unchanged until the interrupt is cleared via a read of INTCAP or GPIO.

      For the INTF (Interrupt Flag) registers it doesn’t say anything. I assume it’s cleared when you read it. Just try I would say.

      1. Wolfgang
        INTCAP & INTF are cleared upon read. Well understood. Inturrupt Capture – Inturrupt Flag
        Can you brief me in your words as:
        When I press Switch on A at PIN 0.
        What happen to INTCAP (0x10 ) and INTF (0xE) before & after.
        Before and after.
        What are their exact Values before the push event and what is after event in both registers. and what value after i read them via READ command as cleared.
        Regards
        Athar Kaludi – PAKISTAN

        1. Hi,

          INTFA will be 1 (0b00000001), provided the pin A0 is set up as an interrupt pin. If you pressed a switch at A1, INTFA would be 2 (0b00000010), for A2 it would be 4 (0b00000100) and so on.

          The INTCAPA register records the content of the GPIO register at the time of the last interrupt. Therefore I can’t tell what the bits 1-7 would look like. It depends on what is connected to the these or if you have set them up to be LOW or HIGH. Pressing Switch A0 will only affect bit 0 of INTCAPA. If the switch provides a HIGH signal then bit 0 will be 1, if it provides a LOW signal then it will be 0.

          My recommendation: Try and play. It’s no big effort.

          1. Thanks Wolfgang
            Your Output and my output on Serial monitor differ in both programs.
            On Change and On Defval. It false take values and input even not touch switches ?
            Any Idea ?
            Regards
            Athar
            Module is using RESET tied HIGH and connected permanent on 5 volts

            1. I need more information. What do you mean with “both programs”? Which ones? When you say “your output and my output”, which ones are you talking about. Which sketch did you apply and which circuit? Do you get exactly the opposite result or random values? If it’s random values, have applied pulldown or pullup resistors?

              1. Wolfgang
                Output from Interrupt on Change and from DefVal
                Strange behave after first 10 – 15 minutes.

                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 10000111
                Pin No.7 went HIGH
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 11000111
                Pin No.7 went HIGH
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 10000111
                Pin No.7 went HIGH
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 1000111
                Pin No.7 went LOW
                Interrupt!
                Interrupt Flag Register: 1000000
                Interrupt Capture Register: 11000111
                Pin No.6 went HIGH
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 1000111
                Pin No.7 went LOW
                Interrupt!
                Interrupt Flag Register: 1000000
                Interrupt Capture Register: 11000111
                Pin No.6 went HIGH
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 1000111
                Pin No.7 went LOW
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 10000111
                Pin No.7 went HIGH
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 1000111
                Pin No.7 went LOW
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 111
                Pin No.7 went LOW
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 1000111
                Pin No.7 went LOW
                Interrupt!
                Interrupt Flag Register: 1000000
                Interrupt Capture Register: 11000111
                Pin No.6 went HIGH
                Interrupt!
                Interrupt Flag Register: 10000000
                Interrupt Capture Register: 1000111
                Pin No.7 went LOW

                Is the Library WIRE cause problem or where.
                I am unable to fix since week.

                Regards
                Athar

                1. Difficult to comment without seeing your circuit. If you get interrupts on pin A7 without having pressed a button then I assume it has an undefined state. Is pin A7 pulled down (or pulled high) while the button is not pressed? Same question for A6.

              2. Wolfgang
                Additionally – how to apply PULL DOWN – I see PullUp only in LIB.
                & more you enquire in .ino [interrupt on change & DefVal]
                FIrst INTCAP outside event loop
                intCapReg = myMCP.getIntCap(B);
                Then inside condition TRUE you did
                intFlagReg
                intCapReg

                so there sequence of issue is important as they read and clear the Register
                so placing them any where is not purpose just to read.
                These issue in way that upon event these are to read and compute pin number of guilt pin.
                soon after all is cleared
                Am I correct.
                regards
                Athar

                1. Sorry, I need to limit my support. I just can’t answer several questions per day. If something does not work as expected then please go back to my original examples, do EXACTLY what I did and start from there. Since I don’t know your circuit, it is difficult for me to solve your problems.

                  As you can see in the circuit for the interrupt-on-dafval-deviation examples, I have added external pull-down resistors to the pins B4-B7. These are pins which are at LOW level when the buttons are not pressed. Please do the same. Without the external pull-downs, it does not work (as you experience right now, I assume).
                  And can you send further request directly by e-mail? This is getting too big for the comment section.

                  1. Wolfgang
                    Problem solved a bit by PULLUP Resistor and Physical resistor and PULLUP. No more random garbage.

                    1 The Interrupt on change upon Push show once output. [Pushed and released quickly generate no ERROR – if kept long pushed and released cause FLAG & CAPTURE to flow Garbage upon exit]. Upon release show 0.
                    2 The Interrupt on DEFVAL upon Push button show continuous output on Serial Monitor. [Same Output continue and no garbage or REG Values change].
                    My needs are push button held for some 5 – 10 seconds and know output once – and know exit once. I record how much time pressed and passed.

                    I send email.
                    regards
                    ATHAR KALUDI
                    IC MCP 20317 is perfectly working in TEST OF 5 HOURS.
                    Solution & Correction lies in PullUP.

  3. Hi Wolfgang

    In inturrupt_on_change.ino sketch
    I seen these lines

    byte intFlagReg, eventPin;
    intFlagReg = myMCP.getIntFlag(B);
    eventPin = log(intFlagReg)/log(2);
    intCapReg = myMCP.getIntCap(B);

    How is eventPin calculated ?
    eventPin = log(intFlagReg)/log(2);

    Please explain as the return is intFlahReg and how we know the pin ?
    what is this formula to find location of bit – How is relationship of log and Bits ?
    Thanks
    Athar

    1. intFlagReg is the content of the interrupt flag register. So, if, for example, pin 5 caused the interrupt, then intFlagReg would be 0b100000 = 32. So the challenge is to calculate the the pin number (let’s call it x) from 32.

      2^x = 32, that means x = log_2(32), so we are looking for the value of the logarithm of 32 with a base of 2.

      C++ has no logarithm to the base of 2. It only has the function log() which is the natural logarithm (also known as ln or log_e) or log10() which is the logarithm to the base 10.

      But there is a rule for logarithms which helps:
      log_a(x) = log_b(x) / log_b(a)
      In words: The logarithm of x to base a is the logarithm of x to base b divided by the logarithm of b to base b;
      And that’s what I apply here. It’s just some math.

  4. Wolfgang Ewald
    Good Morning from Pakistan.
    I often use port expander – MCP23017 just arrived and i explore internet for datasheet and library.
    Your comprehensive article sits at TOP First Place for sure. The library you given are hard efforts and way we can access chip is amazing approach. Full working is so easily brief that one can utilize the MCP as per specific needs.
    I have few more things in mind will discuss via email or whatsapp. Be blessed. Be Happy. Email:athar.kaludi@gmail.com-WhatsApp: +92-335-3505911.
    ATHAR KALUDI

      1. I run GPIO reading from examples in your library – which circuit and bread board i follow with that example – it display serial monitor with some bits in A & B. guide me circuit photo to follow –

        1. There is no extra circuit for this in this post. Reading works like reading Arduino pins, but instead of digitalRead(pin) you use getPin(pin,port). So for example: myMCP.getPin(3,A) will return 1 or true if it is a HIGH status and 0 or false if it is in LOW status. Or, you query the complete status of the port using myMCP.getPort( port ), e.g. myMCP.getPort(A);
          Depending on what you have connected to your MCP23017, you might need to add a pull-down resistor or you you might want to add the internal pull-up using setPinPullUp( pin,port,level ); Like you would do it with an Arduino.

  5. Detailed info on MCP23017. I used it recently without understanding the inner workings of it and this helped me alot as a software developer who has little background in electronics

Leave a Reply

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