MCP23017 - General Routines that need testing

There's quite a lot you can do with the MCP and I find that the Adafruit library is not clear enough for me in terms of the setting of the MCP.
So I have written a set of subroutines that I think are easier to understand. Nick Gammon produced a similar set years ago but his does not allow customising of each port in an MCP, it assumes both ports are the same
I have tested all the basic functions but am struggling with the interrupts as I do not have a suitable connection and test gear for the interrupt testing.

I would be pleased if someone could test these out and comment back

// MCP23017 - General Simple Subroutines
// for initialising, reading, writing to ports
// and setting of interrupts
// Written by : Rod McMahon
// Written : 26 May 2022
// Thanks to Nick Gammon
//----------------------------------------------------------------------

#include <Wire.h>

//MCP23017 General
uint8_t PortAState; // Used for setting and storing output states
uint8_t PortBState; // Used for setting and storing output states

// MCP23017 registers (everything except direction defaults to 0)
// Thanks to Nick Gammon
#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 IOCONA 0x0A // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
#define IOCONB 0x0B
#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

//--------------------------------------------------
// Writes Data to Register in MCPAdd
//--------------------------------------------------
void MCPWrite(uint8_t MCPAdd,uint8_t Reg, uint8_t Data)
{
Wire.beginTransmission(MCPAdd);
Wire.write(Reg);
Wire.write(Data);
Wire.endTransmission();
}

//--------------------------------------------------
//Reads data from Register in MCPAdd
//--------------------------------------------------
uint8_t MCPRead(int MCPAdd, uint8_t Reg)
{
Wire.beginTransmission(MCPAdd);
Wire.write(Reg); //Sets address to Port B
Wire.endTransmission();
Wire.requestFrom(MCPAdd,1,true); //Gets one uint8_t from register, stops request message
return Wire.read(); //Reads in the uint8_t
}

//--------------------------------------------------
// Initialises the two IOPorts on MCPAdd
// Ports are labelled "Input" or "Output"
// IOPOLA and GPPUA can be deteted or others added
//----------------------------------------------------
void MCPInitialize(uint8_t MCPAdd,String IOPortAType,String IOPortBType)
{
if(IOPortAType =="Input")
{
MCPWrite(MCPAdd, IODIRA,0xFF); // IODIRA set all of bank A to Inputs
MCPWrite(MCPAdd, IOPOLA,0xFF); // IOPOLA Invert all Inputs
MCPWrite(MCPAdd, GPPUA, 0xFF); // GPPUA set all of bank A Pull Ups
}
if(IOPortAType =="Output")
MCPWrite(MCPAdd, IODIRA,0x00); // IODIRA set all of bank A to Outputs

if(IOPortBType =="Input")
{
MCPWrite(MCPAdd, IODIRB,0xFF); // IODIRB set all of bank B to Inputs
MCPWrite(MCPAdd, IOPOLB,0xFF); // IOPOLB Invert all Inputs
MCPWrite(MCPAdd, GPPUB, 0xFF); // GPPUB set all of bank B Pull Ups
}
if(IOPortBType =="Output")
MCPWrite(MCPAdd, IODIRB,0x00); // IODIRB set all of bank B to Outputs
}

//-------------------------------------------------
// Sets or clears one output of an IOPort on MCPAdd
// Port_x_State is a global variable so each IO change
// does not loose the other output states
// Pin Number = 0 to 7
// Value
//-------------------------------------------------
void MCPSetOutput(uint8_t MCPAdd,uint8_t IOPort,uint8_t PinNumber,boolean Value)
{
if (IOPort == GPIOA )
{
if (Value = true)
PortAState = bitSet(PortAState,PinNumber); // Sets the bit in PortAState
else
PortAState = bitClear(PortAState,PinNumber); // Clears the bit in PortAState
MCPWrite(MCPAdd,GPIOA,PortAState);
}

if (IOPort == GPIOB)
{
if (Value = true)
PortBState = bitSet(PortBState,PinNumber);
else
PortBState = bitClear(PortBState,PinNumber);
MCPWrite(MCPAdd,GPIOB,PortBState);
}
}

//-------------------------------------------------
// Sets interrupt for Change only
// not for reference to DEVAL
// Interrupt pin set normally High
// Uses the default that INTCON register is 0x00
//-------------------------------------------------
void MCPSetIntChange(uint8_t MCPAdd,uint8_t IOPort)
{
uint8_t ClearInt;
if (IOPort == GPIOA)
{
MCPWrite(MCPAdd,GPINTENA,0xFF); // GPINTENA Interrupt all IO on change
MCPWrite(MCPAdd,IOCONA, 0b00000010); // Interrupt pin normally High
ClearInt = MCPRead(MCPAdd, INTCAPA); // Reads to clear interrupt
}
if (IOPort == GPIOB)
{
MCPWrite(MCPAdd,GPINTENB,0xFF); // GPINTENB Interrupt all IO on change
MCPWrite(MCPAdd,IOCONB, 0b00000010); // Interrupt pin normally High
ClearInt = MCPRead(MCPAdd, INTCAPB); // Reads to clear interrupt
}
}

//-------------------------------------------------
// Sets interrupt for difference to DEVAL
// Interrupt pin set normally High
// IntConState determines which pin is compared to the values in DEFVAL
// DefvalState defines the state that the pin is compared against
// GPINTEN can be masked so only some pins change on interrupt
//-------------------------------------------------
void MCPSetIntDefvalChange(uint8_t MCPAdd,uint8_t IOPort, uint8_t IntConSet, uint8_t DefvalSet)
{
uint8_t ClearInt;
if (IOPort == GPIOA)
{
MCPWrite(MCPAdd,GPINTENA,0xFF); // GPINTENA Interrupt all IO on change
MCPWrite(MCPAdd,IOCONA, 0b00000010); // Interrupt pin normally High
MCPWrite(MCPAdd,INTCONA,IntConSet); // Determines which pins are compared to DEVALA
MCPWrite(MCPAdd,DEFVALA,DefvalSet); // Mask for DEFVALA
ClearInt = MCPRead(MCPAdd, INTCAPA); // Reads to clear interrupt
}
if (IOPort == GPIOB)
{
MCPWrite(MCPAdd,GPINTENB,0xFF); // GPINTENA Interrupt all IO on change
MCPWrite(MCPAdd,IOCONB, 0b00000010); // Interrupt pin normally High
MCPWrite(MCPAdd,INTCONB,IntConSet); // Determines which pins are compared to DEVALA
MCPWrite(MCPAdd,DEFVALB,DefvalSet); // Mask for DEFVALA
ClearInt = MCPRead(MCPAdd, INTCAPB); // Reads to clear interrupt
}
}

//-------------------------------------------------
// Reads the interrupt flag
// Should be called after every interrupt detected
// 1= input that caused interrupt
// This also resets interrupts
//-------------------------------------------------
uint8_t MCPReadInterrupt(uint8_t MCPAdd,uint8_t IOPort)
{
if (IOPort == GPIOA)
return MCPRead(MCPAdd, INTCAPA);
if (IOPort == GPIOB)
return MCPRead(MCPAdd, INTCAPB);
}

//-------------------------------------------------
//Turns ON all of the outputs of a IOPort
//-------------------------------------------------
void MCPPortAllOn(uint8_t MCPAdd,uint8_t IOPort)
{
if (IOPort ==GPIOA)
MCPWrite(MCPAdd,GPIOA,0xFF);
if (IOPort ==GPIOB )
MCPWrite(MCPAdd,GPIOB,0xFF);
}

//-------------------------------------------------
//Turns OFF all of the outputs of a IOPort
//-------------------------------------------------
void MCPPortAllOff(uint8_t MCPAdd,uint8_t IOPort)
{
if (IOPort ==GPIOA)
MCPWrite(MCPAdd,GPIOA,0x00);
if (IOPort ==GPIOB )
MCPWrite(MCPAdd,GPIOB,0x00);
}

//-------------------------------------------------------------------
// Turns ON the outputs depending on the mask sent
// This does not use the Port-x-State variable
// The selection is from Pin 7 on Left to Pin 0 on right
// So Pin 7 and Pin 1 = 0bx10000010
//------------------------------------------------------------------
void MCPPortOutSelection(uint8_t MCPAdd,uint8_t IOPort, uint8_t IOMask)
{
if (IOPort ==GPIOA)
MCPWrite(MCPAdd,GPIOA,IOMask);
if (IOPort ==GPIOB)
MCPWrite(MCPAdd,GPIOB,IOMask);
}

//-------------------------------------------------------------------
// Read one input from an IOPort
// Output 1 or 0 depends on setting of IOPOLA
//-------------------------------------------------------------------
boolean MCPReadOneInput(uint8_t MCPAdd,uint8_t IOPort, uint8_t IONo)
{
uint8_t IOState = MCPRead(MCPAdd,IOPort); //Reads state of IO selected
if (bitRead(IOState,IONo)==1)
return true;
else
return false;
}

//-------------------------------------------------------------------
void setup()
{
Wire.begin();
Serial.begin(115200);
MCPInitialize(0x20,"Output","Output");
MCPInitialize(0x21,"Input","Input");
MCPInitialize(0x22,"Input","Output");
MCPSetIntChange(0x21,GPIOA);
MCPSetIntChange(0x21,GPIOB);

MCPPortAllOff(0x22,GPIOB);
delay(1000);

MCPPortOutSelection(0x22,GPIOB,0b01011001);
//MCPPortAllOn(0x22,GPIOB);
}

void loop()
{
Serial.print("A = ");
Serial.println(MCPReadOneInput(0x21,GPIOA,0));
Serial.print("B = ");
Serial.println(MCPReadOneInput(0x21,GPIOB,3));
MCPReadInterrupt(0x21,GPIOA);
MCPReadInterrupt(0x21,GPIOB);
}Preformatted text

Go back and read the instructions for this forum and post the code appropriately<>

@gilshultz, looks like OP tried but got it wrong :wink:

type or pa// MCP23017 - General Simple Subroutines
// for initialising, reading, writing to ports
// and setting of interrupts
// Written by : Rod McMahon
// Written    :   26 May 2022
// Thanks to Nick Gammon
//----------------------------------------------------------------------

#include <Wire.h>

  //MCP23017 General
  uint8_t PortAState;         // Used for setting and storing output states
  uint8_t PortBState;         // Used for setting and storing output states
  
  // MCP23017 registers (everything except direction defaults to 0)
  // Thanks to Nick Gammon
  #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 IOCONA   0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
  #define IOCONB   0x0B   
  #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

//--------------------------------------------------
// Writes Data to Register in MCPAdd
//--------------------------------------------------
void MCPWrite(uint8_t MCPAdd,uint8_t Reg, uint8_t Data)
{
  Wire.beginTransmission(MCPAdd);
  Wire.write(Reg);
  Wire.write(Data);
  Wire.endTransmission();
}

//--------------------------------------------------
//Reads data from Register in MCPAdd
//--------------------------------------------------
uint8_t MCPRead(int MCPAdd, uint8_t Reg)
  {
  Wire.beginTransmission(MCPAdd);
  Wire.write(Reg);                      //Sets address to Port B
  Wire.endTransmission(); 
  Wire.requestFrom(MCPAdd,1,true);      //Gets one uint8_t from register, stops request message
  return Wire.read();                   //Reads in the uint8_t
  }

//--------------------------------------------------
// Initialises the two IOPorts on MCPAdd
// Ports are labelled "Input" or "Output"
// IOPOLA and GPPUA can be deteted or others added
//----------------------------------------------------
void MCPInitialize(uint8_t MCPAdd,String IOPortAType,String IOPortBType)
  {
  if(IOPortAType =="Input")
    {
    MCPWrite(MCPAdd, IODIRA,0xFF);     // IODIRA  set all of bank A to Inputs
    MCPWrite(MCPAdd, IOPOLA,0xFF);     // IOPOLA  Invert all Inputs 
    MCPWrite(MCPAdd, GPPUA, 0xFF);     // GPPUA set all of bank A Pull Ups                 
    }
  if(IOPortAType =="Output")
    MCPWrite(MCPAdd, IODIRA,0x00);     // IODIRA  set all of bank A to Outputs
 
  if(IOPortBType =="Input")
    {
    MCPWrite(MCPAdd, IODIRB,0xFF);     // IODIRB  set all of bank B to Inputs
    MCPWrite(MCPAdd, IOPOLB,0xFF);     // IOPOLB  Invert all Inputs 
    MCPWrite(MCPAdd, GPPUB, 0xFF);     // GPPUB set all of bank B Pull Ups                 
    }
  if(IOPortBType =="Output")
    MCPWrite(MCPAdd, IODIRB,0x00);     // IODIRB  set all of bank B to Outputs
  }

//-------------------------------------------------
// Sets or clears one output of an IOPort on MCPAdd
// Port_x_State is a global variable so each IO change
// does not loose the other output states 
// Pin Number = 0 to 7
// Value
//-------------------------------------------------
void MCPSetOutput(uint8_t MCPAdd,uint8_t IOPort,uint8_t PinNumber,boolean Value)    
  {
  if (IOPort == GPIOA )
   {
    if (Value = true)
      PortAState = bitSet(PortAState,PinNumber);       // Sets the bit in PortAState
    else
      PortAState = bitClear(PortAState,PinNumber);     // Clears the bit in PortAState
    MCPWrite(MCPAdd,GPIOA,PortAState);    
   }

  if (IOPort == GPIOB)
    {
    if (Value = true)
     PortBState = bitSet(PortBState,PinNumber);
    else
     PortBState = bitClear(PortBState,PinNumber);
    MCPWrite(MCPAdd,GPIOB,PortBState); 
    }  
  }

//-------------------------------------------------
// Sets interrupt for Change only
// not for reference to DEVAL
// Interrupt pin set normally High
// Uses the default that INTCON register is 0x00
//-------------------------------------------------
void MCPSetIntChange(uint8_t MCPAdd,uint8_t IOPort)
  {
  uint8_t ClearInt;
  if (IOPort == GPIOA)
    {
    MCPWrite(MCPAdd,GPINTENA,0xFF);         // GPINTENA Interrupt all IO on change
    MCPWrite(MCPAdd,IOCONA, 0b00000010);    // Interrupt pin normally High
    ClearInt =  MCPRead(MCPAdd, INTCAPA);   // Reads to clear interrupt
     }
  if (IOPort == GPIOB)
    {
    MCPWrite(MCPAdd,GPINTENB,0xFF);        // GPINTENB Interrupt all IO on change
    MCPWrite(MCPAdd,IOCONB, 0b00000010);   // Interrupt pin normally High
    ClearInt =  MCPRead(MCPAdd, INTCAPB);  // Reads to clear interrupt
    }
  }

//-------------------------------------------------
// Sets interrupt for difference to DEVAL
// Interrupt pin set normally High
// IntConState determines which pin is compared to the values in DEFVAL
// DefvalState defines the state that the pin is compared against
// GPINTEN can be masked so only some pins change on interrupt
//-------------------------------------------------
void MCPSetIntDefvalChange(uint8_t MCPAdd,uint8_t IOPort, uint8_t IntConSet, uint8_t DefvalSet)
  {
  uint8_t ClearInt;
  if (IOPort == GPIOA)
    {
    MCPWrite(MCPAdd,GPINTENA,0xFF);         // GPINTENA Interrupt all IO on change
    MCPWrite(MCPAdd,IOCONA, 0b00000010);    // Interrupt pin normally High
    MCPWrite(MCPAdd,INTCONA,IntConSet);   // Determines which pins are compared to DEVALA
    MCPWrite(MCPAdd,DEFVALA,DefvalSet);   // Mask  for DEFVALA
    ClearInt =  MCPRead(MCPAdd, INTCAPA);   // Reads to clear interrupt
     }
  if (IOPort == GPIOB)
    {
    MCPWrite(MCPAdd,GPINTENB,0xFF);         // GPINTENA Interrupt all IO on change
    MCPWrite(MCPAdd,IOCONB, 0b00000010);    // Interrupt pin normally High
    MCPWrite(MCPAdd,INTCONB,IntConSet);   // Determines which pins are compared to DEVALA
    MCPWrite(MCPAdd,DEFVALB,DefvalSet);   // Mask  for DEFVALA
    ClearInt =  MCPRead(MCPAdd, INTCAPB);   // Reads to clear interrupt
     }   
  }


//-------------------------------------------------
// Reads the interrupt flag 
// Should be called after every interrupt detected
// 1= input that caused interrupt
// This also resets interrupts
//-------------------------------------------------
uint8_t MCPReadInterrupt(uint8_t MCPAdd,uint8_t IOPort)
{
  if (IOPort == GPIOA)
    return MCPRead(MCPAdd, INTCAPA);
  if (IOPort == GPIOB)
    return MCPRead(MCPAdd, INTCAPB);
}

//-------------------------------------------------
//Turns ON all of the outputs of a IOPort
//-------------------------------------------------
void MCPPortAllOn(uint8_t MCPAdd,uint8_t IOPort)
  {
  if (IOPort ==GPIOA)
     MCPWrite(MCPAdd,GPIOA,0xFF);
  if (IOPort ==GPIOB )
    MCPWrite(MCPAdd,GPIOB,0xFF);
  }

//-------------------------------------------------
//Turns OFF all of the outputs of a IOPort
//-------------------------------------------------
void MCPPortAllOff(uint8_t MCPAdd,uint8_t IOPort)
  {
  if (IOPort ==GPIOA)
     MCPWrite(MCPAdd,GPIOA,0x00);
  if (IOPort ==GPIOB )
    MCPWrite(MCPAdd,GPIOB,0x00);
  }

//-------------------------------------------------------------------
// Turns ON the outputs depending on the mask sent
// This does not use the Port-x-State variable
// The selection is from Pin 7 on Left to Pin 0 on right
// So Pin 7 and Pin 1 = 0bx10000010 
//------------------------------------------------------------------
void MCPPortOutSelection(uint8_t MCPAdd,uint8_t IOPort, uint8_t IOMask)
  {
  if (IOPort ==GPIOA)
    MCPWrite(MCPAdd,GPIOA,IOMask);
  if (IOPort ==GPIOB)
    MCPWrite(MCPAdd,GPIOB,IOMask);
  }


//-------------------------------------------------------------------
// Read one input from an IOPort 
// Output 1 or 0 depends on setting of IOPOLA
//-------------------------------------------------------------------
boolean MCPReadOneInput(uint8_t MCPAdd,uint8_t IOPort, uint8_t IONo)
  {
    uint8_t IOState = MCPRead(MCPAdd,IOPort);     //Reads state of IO selected
    if (bitRead(IOState,IONo)==1)              
      return true;
    else
     return false;  
  }


//-------------------------------------------------------------------
void setup() 
  {
  Wire.begin();
  Serial.begin(115200);  
  MCPInitialize(0x20,"Output","Output");
  MCPInitialize(0x21,"Input","Input");
  MCPInitialize(0x22,"Input","Output");
  MCPSetIntChange(0x21,GPIOA);
  MCPSetIntChange(0x21,GPIOB);

  MCPPortAllOff(0x22,GPIOB);
  delay(1000);
 
  MCPPortOutSelection(0x22,GPIOB,0b01011001);
 //MCPPortAllOn(0x22,GPIOB);
  }

void loop() 
{
Serial.print("A =  ");
Serial.println(MCPReadOneInput(0x21,GPIOA,0));
Serial.print("B =  ");
Serial.println(MCPReadOneInput(0x21,GPIOB,3));
MCPReadInterrupt(0x21,GPIOA);
MCPReadInterrupt(0x21,GPIOB);
}

All better now?

Yes :wink: Except for the "type or pa" but we can live with that :slight_smile:

The interrupt pins do not have to trigger an interrupt on the Arduino. I normally connect them to a digital input and poll them to see if I need to read the full contents of the chip.

I am not sure what you mean by "suitable connector", you just connect them with wires like any other of the pins you are testing.

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