Thursday, December 29, 2016

Arduino and MCP23S17 Port Expander (Part 4)

MCP23S17 Port Expander Interrupts— 
The MCP23S17 Port Expander chip has two built-in hardware interrupt pins. Pin 20 is the Port A interrupt (INTA) and pin 19 is the Port B interrupt (INTB). INTA and INTB terminology distinguishes the MCP interrupts from the Arduino Uno interrupts, INT0 and INT1.

The INTA and INTB pins can be used to sense when a pin on that particular port (A or B) has changed state. Alternatively, INTA and INTB pins can be mirrored so that any one of the 16 digital pins can be sensed when it has changed state.
The INTA interrupt pin can be wired to the Arduino INT0 pin so that the Arduino processor can be interrupted instantly if a pin changes state on an MCP Port A pin (any 1 or more of 8 pins). Similarly, the INTB interrupt pin can be wired to the Arduino INT1 pin so that the Arduino processor can be interrupted instantly if a pin changes state on an MCP Port B pin (any 1 or more of 8 pins).
The Majenko MCP23S17 library (.h and .cpp files) provides functions to handle MCP interrupts which use similar functions as the Arduino interrupt functions:
  • void enableInterrupt(uint8_t pin, uint8_t type);
  • void disableInterrupt(uint8_t pin);
  • void setMirror(boolean m);
  • uint16_t getInterruptPins();
  • uint16_t getInterruptValue();
  • void setInterruptLevel(uint8_t level);
  • void setInterruptOD(boolean openDrain);
I added Port interrupt functions to Majenko's code. The Port functions allow reading the 8 pin states of the port that was interrupted, as opposed to the functions provided by Majenko that read all 16 pin states when an interrupt occurs. Otherwise, the functions behave the same. The Port interrupt function code is provided later in this blog.
Function enableInterrupt(pin, type) is used on any MCP input pin on which an interrupt is desired. The type is CHANGE, RISING, or FALLING which matches interrupt types for the Arduino. CHANGE means trigger an interrupt on both rising or falling changes of state on this particular input pin.
Function setInterruptLevel(level), where level = HIGH means the interrupt pin will go HIGH when an interrupt occurs, level = LOW means it will go LOW.
Missing from the MCP interrupt functions is the Arduino attachInterrupt() function. For example, if MCP pin 4 changing state causes the MCP INTA pin to go LOW, and the MCP INTA pin is wired to the Arduino INT0 pin, then the INT0 pin will also go LOW triggering the INT0 code being immediately processed to take some action. The INTA and INT0 pins require the wire connection to trigger the Arduino attachInterrupt() function, otherwise the MCP interrupt pin INTA would have to be polled by the sketch to determine when INTA changed state. In cases where speed is not of concern, polling for an interrupt might work well. But if speed is a possible requirement, hardware interrupts will provide the best option.
Writing code for handling interrupts is a bit of a challenge, because MCP interrupts can occur on any input pin, but each triggers either INTA or INTB. The pin that caused the INTA or INTB interrupt must be determined so the proper action can be taken. The interrupt must be cleared so that another interrupt can be registered. And, what happens when more than one pin changes state at the same time? Can that happen?
Another issue surfaces for Arduino Uno processors that only have INT0 and INT1 hardware interrupt pins. What does one do if they want to use interrupts for more than one MCP23S17 chip? Where do the MCP23S17 INTA and INTB pins connect on the Arduino? The simple answer is to use a processor, like the Mega 2560, with more interrupts. A bit more difficult approach is to program other Arduino digital pins to have "pin change interrupts" and have code respond to those interrupts. A third option (yet untested) is to tie the MCP interrupts together using the setInterruptOD(boolean openDrain) function and attach them to one set of Arduino interrupts.
The getInterruptPins(), getInterruptAPins(), and getInterruptBPins() functions return either 16 or 8 pin values at the time of an MCP interrupt. These values need to be retained in uint16_t or uint8_t variables, because the registers on the chip will be reset when the interrupt is cleared. When that interrupt is cleared, the next interrupt can be triggered and new values will be written into the MCP interrupt registers. Meanwhile, the values saved in the variables do not change and can be used as needed in the sketch. Their values can then be updated for the new interrupt and the process can be repeated.
  • Note: use .getInterruptPins() to identify the pin that interrupted. Its value is retained until the interrupt is cleared. Further interrupts are blocked until the current interrupt is cleared.
  • Use .getInterruptValue() or .readPort(port) to clear the interrupt
  • Note: .getInterruptValue retains its value. It changes value when it reads the pins after a new pin interrupt. It then takes on the new pin values of .getInterruptPins.
The getInterruptValue(), getInterruptAValue(), getInterruptBValue() functions behave differently than the getInterruptPins() functions. The getInterruptValue() functions retain the values read for the 16 or 8 pins, until the values change. If pins are read over and over using getInterruptValue(), the values may or may not change. Using getInterruptValue() will clear the interrupt, but the getInterruptValue() pin values will not change until getInterruptPins() is called after a new interrupt occurs.
Test sketches with debugging indicate the following results:
Example 1. Ground MCP pin 9, followed by getInterruptBPins() indicates pin 9 was interrupted
     > Port B Interrupt Pins: 00000010
Then,  getInterruptBValues() captures port values
     > Port B Interrupt Values: 11111101
Then a second getInterruptBPins() indicate interrupts are cleared
     > Port B Interrupt Pins: 00000000
Again, getInterruptBValues() captures port values that have not changed
     > Port B Interrupt Values: 11111101
Example 2. Interrupt Port A pin, then interrupt Port B pin
- MCP INTA is wired to Arduino INT0.
- MCP INTB is wired to Arduino INT1.
     > INT0 is triggered by INTA and INT0 state changes from HIGH to LOW.
     > INTA is not cleared.
     > Independent of INTA, INT1 is triggered by INTB and INT1 state changes from HIGH to LOW.
     > INTB is not cleared.
- Neither INTA or INTB allow further interrupts on INT0 or INT1 until INTA and INTB  are cleared.
Example 3. Two or more MCP simultaneous interrupts can occur on a Port.
- Simultaneously ground 2 MCP pins 10 & 13 on Port B.
     > Port B Interrupt Pins: 00100100
     > Port B Interrupt Values: 11011011
- Determine which MCP interrupts (bits [7...0] , right to left) were set (LOW)
     > whichBit: 2
     > whichBit: 5
     > whichBitCount: 2
Example 3 demonstrates that one Arduino interrupt can indicate that one or more MCP port interrupts have occurred. Each MCP Port interrupt would need to be processed individually. Successive interrupts are therefore different than simultaneous interrupts, and would require different code to interpret the interrupts.
The INT0 and INT1 ISR code --

void interruptA() {
  // ISR to respond to INT0 interrupt
  // Respond to the INTA interrupt
  // which was triggered by one or more MCP inputs
  interruptMCP(Bank0, 0); // MCP ISR (bank, port)
}

When INT0 is triggered by INTA (due to an MCP pin going LOW), the function interruptA is executed and calls the interruptMCP(bank, port) function. Similar code is used for INT1 and INTB.

void interruptMCP(MCP23S17 &bank, uint8_t port) {
  // read the Port Expander Interrupt pins
  readBankPortIntPins(bank, port);

  // read the Port Expander Interrupt values
  readBankPortIntVal(bank, port);

  // take action on new pin interrupt values
  if (port < 1) {
    flagMCPA = 1; // set flag that Port A interrupt occurred
  } else {
    flagMCPB = 1; // set flag that Port B interrupt occurred
  }
  // act on Reading in loop()
}

The interruptMCP() function reads the Port getInterruptXPins(), where X=A or B. It then reads the Port getInterruptXValues() which saves the values to the mcpXReading variable. The mcpXReading variable (8 bits holding the Port X interrupt pin values) is then processed to take some action. In this case, a flag is set and passed to loop(). In loop(), the 8 bits are read to determine which pins caused the interrupt, and then that information is printed to Serial Monitor to show what just happened, and further processed as a demonstration of the code. 
Port Interrupt functions --
Add the following code at the end of public: in MCP23S17.h
/* Following functions added by Lowell Bahner, 2016-12-29, for Port interrupts */
        uint8_t getInterruptAPins();
        uint8_t getInterruptAValue();
        uint8_t getInterruptBPins();
        uint8_t getInterruptBValue();

And, add the following code to the end of MCP23S17.cpp:


/* Following functions added by Lowell Bahner, 2016-12-29, for Port interrupts */

/*! This function returns an 8-bit bitmap of the Port-A pin or pins that have caused an interrupt to fire.
 *
 *  Example:
 *
 *      unsigned int pins = myExpander.getInterruptAPins();
 */
uint8_t MCP23S17::getInterruptAPins() {
    readRegister(INTFA);
    return  _reg[INTFA];
}

/*! This returns a snapshot of the Port-A IO pin states at the moment the last interrupt occured.  Reading
 *  this value clears the interrupt status (and hence the INT pins) for the port.
 *  Until this value is read (or the current live port value is read) no further interrupts can
 *  be indicated.
 *
 *  Example:
 *
 *      unsigned int pinValues = myExpander.getInterruptAValue();
 */
uint8_t MCP23S17::getInterruptAValue() {
    readRegister(INTCAPA);
    return _reg[INTCAPA];
} 

/*! This function returns an 8-bit bitmap of the Port-B pin or pins that have caused an interrupt to fire.
 *
 *  Example:
 *
 *      unsigned int pins = myExpander.getInterruptBPins();
 */
uint8_t MCP23S17::getInterruptBPins() {
    readRegister(INTFB);
    return _reg[INTFB];
}

/*! This returns a snapshot of the Port-B IO pin states at the moment the last interrupt occured.  Reading
 *  this value clears the interrupt status (and hence the INT pins) for the port.
 *  Until this value is read (or the current live port value is read) no further interrupts can
 *  be indicated.
 *
 *  Example:
 *
 *      unsigned int pinValues = myExpander.getInterruptBValue();
 */
uint8_t MCP23S17::getInterruptBValue() {
    readRegister(INTCAPB);
    return _reg[INTCAPB];
} 

Sketch Results and Code --

Following is an example of the Serial Monitor report from the sketch. Each time one or more MCP23S17 input pins are grounded (Switch or wire), the Port pins that caused the interrupt are identified and counted, then those pins are further processed, as an example of processing each pin that had changed state.

 Starting MCP23S17 Interrupt Test

 > Wire MCP23S17 Pin INTA to Arduino interrupt pin pinA (D2). 
 > Wire MCP23S17 Pin INTB to Arduino interrupt pin pinB (D3). 
 > Run Sketch with Serial Monitor window ON at 115200 baud.
 > Take MCP23S17 input pin(s) LOW (Switch or wire to Ground). 

 >> Ready >> 

 --- Interrupts Port A Values: 00100100
 Port A whichBit: 2
 Port A whichBit: 5
 byteACount: 2

 Process Port A pin: 2
 Process Port A pin: 5

 --- Interrupts Port B Values: 10000000
 Port B whichBit: 7
 byteBCount: 1

 Process Port B pin: 7


Sketch to demonstrate MCP23S17 Interrupts --

/*
 * MCP23S17_Interrupt_Counter_01
 * 2016-12-29
 * Lowell Bahner
 *
 * This code counts interrupts on an MCP23S17 Port Expander
 *
 * A push button or wire takes one or more MCP23S17 input pins LOW to trigger interrupts
 * which are processed and the action is printed to Serial Monitor.
 *
 * The MCP23S17 has an interrupt on each digital input pin which can set
 * a PORT interrupt pin. The Port A interrupt (INTA) is chip pin 20, and Port B
 * interrupt (INTB) is chip pin 19.
 *
 * The MCP23S17 INTA and INTB can be wired to interrupt pins on the Arduino
 * which can use hardware interrupts to interrupt the sketch code to take
 * immediate action.
 *
 * UNO hardware interrupt pins INT0 (D2), INT1 (D3)
 * Mega 2560 interrupt pins D2, D3, D18, D19, D20, D21
 *
 * ======================================================

  Sketch Interrupt Tests:
  Test 1. Demonstrate MCP port interrupt process.
   - Ground MCP pin 9 and getInterruptBPins() indicate pin 9 was interrupted
      >  Port B Interrupt Pins: 00000010
   - then getInterruptBValues() captures port values
      >  Port B Interrupt Values: 11111101
   - then a second getInterruptBPins() indicate interrupts are cleared
      >  Port B Interrupt Pins: 00000000
   - again, getInterruptBValues() captures port values that have not changed
      >  Port B Interrupt Values: 11111101

  Test 2. Interrupt A and B are separate events.
          INTA ==> INT0 state change is captured.
          INTA is not cleared and does not capture another interrupt.
          INTB ==> INT1 state changes.
          INTB is not cleared and does not capture another interrupt.
      Index  Int Pin     IntA     IntB     Time
  -------  -------  -------  -------  -------
        0        x        1        1  0
        1        A        0        1  4

    Index  Int Pin     IntA     IntB     Time
  -------  -------  -------  -------  -------
        0        x        0        1  0
        1        B        0        0  4

  Test 3. Ground 2 MCP pins 10 & 13 on Port B.
    - One Arduino interrupt can indicate one or more simultaneous MCP interrupts.
    >  Port B Interrupt Pins: 00100100
    >  Port B Interrupt Values: 11011011
    - Determine which MCP interrupts (bits [7...0]) were set (LOW)
    >  whichBit: 2
    >  whichBit: 5
    >  whichBitCount: 2


 * ======================================================
 *
 *
   Sketch Output to Serial Monitor

   Starting MCP23S17 Interrupt Test


 > Wire MCP23S17 Pin INTA to Arduino interrupt pin pinA (D2).
 > Wire MCP23S17 Pin INTB to Arduino interrupt pin pinB (D3).
 > Run Sketch with Serial Monitor window ON at 115200 baud.
 > Take MCP23S17 input pin(s) LOW (Switch or wire to Ground).

 >> Ready >>


 --- Interrupts Port A Values: 00100100
 Port A whichBit: 2
 Port A whichBit: 5
 byteACount: 2

 Process Port A pin: 2
 Process Port A pin: 5

 --- Interrupts Port B Values: 10000000
 Port B whichBit: 7
 byteBCount: 1

 Process Port B pin: 7

 *
 *
 */

// ======================================================
// Sketch Code

// Majenko MCP23S17 Library (revised 2017-Jan-23)
#include <MCP23S17.h>

// Arduino Library SPI.h
#include <SPI.h>

const byte NUM_ENCODERS = 4;

// SPI CS/SS chipselect pin can be changed by user as desired
const uint8_t chipSelect = 10;

uint8_t pinA = 2;   // INT0 Connected to MCP INTA
uint8_t pinB = 3;   // INT1 Connected to MCP INTB

volatile uint16_t mcpReading = 0; // 16-bit interrupt reading
volatile uint8_t mcpAReading = 0; // 8-bit Port-A interrupt reading
volatile uint8_t mcpBReading = 0; // 8-bit Port-B interrupt reading
uint8_t whichBit = 0;    // one bit [7...0] that is set in byte
uint8_t byteACount = 0; // number of bits set in byte
uint8_t byteBCount = 0; // number of bits set in byte
uint8_t byteABits[8] = {0}; // set bit array
uint8_t byteBBits[8] = {0}; // set bit array
volatile uint8_t flagMCPA = 0;
volatile uint8_t flagINTA = 0;
volatile uint8_t flagMCPB = 0;
volatile uint8_t flagINTB = 0;

// Create an object for each chip
// Bank0 is address 0. Pins A0,A1,A2 grounded.
// Bank1 is address 1. Pin A0=+5V, A1,A2 grounded.
// Bank2 is address 2. Pin A1=+5V, A0,A2 grounded.
// Bank3 is address 3. Pin A0,A1=+5V, A2 grounded.

MCP23S17 Bank0(&SPI, chipSelect, 0);
MCP23S17 Bank1(&SPI, chipSelect, 1);
MCP23S17 Bank2(&SPI, chipSelect, 2);
MCP23S17 Bank3(&SPI, chipSelect, 3);

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function setup
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void setup() {

  Bank0.begin();
  Bank1.begin();
  Bank2.begin();
  Bank3.begin();

  pinMode (pinA, INPUT_PULLUP);
  pinMode (pinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(pinA), interruptA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pinB), interruptB, CHANGE);

  Serial.begin (115200);
  Serial.println(F("\n Starting MCP23S17 Interrupt Test"));

  displayInstructions();
  displayReady();

  //----------------------------------------------------------
  // Port Expander pin and interrupts configuration
  //----------------------------------------------------------
  //
  // Input port code
  // pins 0-15 are on device:port
  // device==1 Bank0
  // port==0 Port A, 1 Port B
  //
  // Set MCP23S17 pin modes and Interrupt configurations
  // example set one pin: chip.pinMode(0, INPUT_PULLUP);
  //
  setChipPins(); // use loop to set individual pins

  // clear the interrupt values variables
  mcpAReading = 0;
  mcpBReading = 0;
  mcpReading = 0;

}  // end of setup



// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function setChipPins()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Set all Chip pins to desired mode
void setChipPins() {
  // Example: pass the Bank object to the setPin function

  setPin(Bank0);
  setPin(Bank1);
  setPin(Bank2);
  setPin(Bank3);
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function setPin()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Set all Port Expander pins to desired mode INPUT_PULLUP
// Set all Port Expander input pins interrupts
void setPin(MCP23S17 &bank) {
  for (uint8_t ind = 0; ind <= 15; ind++) {
    bank.pinMode(ind, INPUT_PULLUP);
    bank.enableInterrupt(ind, FALLING);
  }

  // set Port Expander Interrupt configuratons
  bank.setMirror(false);
  bank.setInterruptOD(false);
  bank.setInterruptLevel(LOW);
  // setInterruptLevel(LOW) requires the revised library (2017-Jan-23)
  // clear all interrupts on this Port Expander
  mcpReading = bank.getInterruptValue();
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function readBankPortIntVal()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Read all Port Expander port interrupt values
void readBankPortIntVal(MCP23S17 &bank, uint8_t port) {
  if (port < 1) { // Port A
    mcpAReading = bank.getInterruptAValue();
    //Serial.print ("\n Port A Interrupt Values: "); print8Bits(mcpAReading);
  } else {        // Port B
    mcpBReading = bank.getInterruptBValue();
    //Serial.print ("\n Port B Interrupt Values: "); print8Bits(mcpBReading);
  }
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function readBankPortIntPins()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Read all Port Expander port interrupt pins
void readBankPortIntPins(MCP23S17 &bank, uint8_t port) {
  if (port < 1) { // Port A
    mcpAReading = bank.getInterruptAPins();
    //Serial.print ("\n Port A Interrupt Pins: "); print8Bits(mcpAReading);
  } else {        // Port B
    mcpBReading = bank.getInterruptBPins();
    //Serial.print ("\n Port B Interrupt Pins: "); print8Bits(mcpBReading);
  }
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function readByteBits()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Read all Port Expander port interrupt pins
// save set bit to byteBits arrays
// byteBits array can be sequentially processed later
void readByteBits(MCP23S17 &bank, uint8_t port) {
  if (port < 1) { // Port A
    byteACount = 0;
    for (uint8_t ind = 0; ind < 8; ind++) {
      if (bitRead(~mcpAReading, ind)) { // ~ inverts bit values
        byteABits[byteACount] = ind; // save the bit number to sequential array
        byteACount = byteACount + 1;
        Serial.print ("\n Port A whichBit: "); Serial.print (ind);
      }
    }
    Serial.print ("\n byteACount: "); Serial.print (byteACount);
  } else { // Port B
    byteBCount = 0;
    for (uint8_t ind = 0; ind < 8; ind++) {
      if (bitRead(~mcpBReading, ind)) { // ~ inverts bit values
        byteBBits[byteBCount] = ind; // save the bit number to sequential array
        byteBCount = byteBCount + 1;
        Serial.print ("\n Port B whichBit: "); Serial.print (ind);
      }
    }
    Serial.print ("\n byteBCount: "); Serial.print (byteBCount);
  }
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function processPins()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Process the port byteBits array of interrupted MCP pins
void processPins(MCP23S17 &bank, uint8_t port) {
  if (port < 1) { // Port A
    // byteACount = number of pins to process
    for (uint8_t ind = 0; ind < byteACount; ind++) {
      uint8_t pin = byteABits[ind]; // recover the bit number from sequential array
      Serial.print ("\n Process Port A pin: "); Serial.print (pin);
    }
  } else { // Port B
    // byteACount = number of pins to process
    for (uint8_t ind = 0; ind < byteBCount; ind++) {
      uint8_t pin = byteBBits[ind]; // recover the bit number from sequential array
      Serial.print ("\n Process Port B pin: "); Serial.print (pin);
    }
  }
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function interruptA()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void interruptA() {
  // ISR to respond to INT0 interrupt
  // Respond to the INTA interrupt
  // which was triggered by one or more MCP inputs
  interruptMCP(Bank0, 0); // MCP ISR (bank, port)
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function interruptB()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void interruptB() {
  // ISR to respond to INT1 interrupt
  // Respond to the INTB interrupt
  // which was triggered by one or more MCP inputs
  interruptMCP(Bank0, 1); // MCP ISR (bank, port)
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function interruptMCP()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void interruptMCP(MCP23S17 &bank, uint8_t port) {
  // read the Port Expander Interrupt pins
  readBankPortIntPins(bank, port);

  // read the Port Expander Interrupt values
  readBankPortIntVal(bank, port);

  // take action on new pin interrupt values
  if (port < 1) {
    flagMCPA = 1; // set flag that Port A interrupt occurred
  } else {
    flagMCPB = 1; // set flag that Port B interrupt occurred
  }
  // act on Reading in loop()
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function loop
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void loop ()
{

  // This code runs the MCP Interrupt processing from the Arduino ISR
  // and then takes action (prints) on the MCP pins that went LOW

  if (flagMCPA > 0) {
    //Serial.println ("\n loop flagMCPA");
    printInterruptHeader(Bank0, 0);
    processPins(Bank0, 0); // do some processing of each interrupted pin
    flagMCPA = 0;
  }

  if (flagMCPB > 0) {
    //Serial.println ("\n loop flagMCPB");
    printInterruptHeader(Bank0, 1);
    processPins(Bank0, 1); // do some processing of each interrupted pin
    flagMCPB = 0;
  }

  delay (10); // give loop something to do while idle

}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function printInterruptHeader
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void printInterruptHeader(MCP23S17 bank, uint8_t port) {
  // inReading is either mcpAReading or mcbBReading 8-pin byte values
  Serial.print ("\n\n --- Interrupts Port ");
  if (port < 1) {
    Serial.print (F("A "));
    Serial.print ("Values: "); print8Bits(~mcpAReading);
  } else {
    Serial.print (F("B "));
    Serial.print ("Values: "); print8Bits(~mcpBReading);

  }
  // get the bit count and write the bits to byteBit[] array
  readByteBits(bank, port);
  Serial.println ();
} // end of printInterruptHeader


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function displayInstructions()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void displayInstructions() {
  Serial.println (F("\n"));
  Serial.println (F(" > Wire MCP23S17 Pin INTA to Arduino interrupt pin pinA (D2). "));
  Serial.println (F(" > Wire MCP23S17 Pin INTB to Arduino interrupt pin pinB (D3). "));
  Serial.println (F(" > Run Sketch with Serial Monitor window ON at 115200 baud."));
  Serial.println (F(" > Take MCP23S17 input pin(s) LOW (Switch or wire to Ground). "));
}

void displayReady() {
  Serial.println (F("\n >> Ready >> "));
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// print binary8 binary16 and hex functions
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

//---------------------------------------------------------------------------------
// print 8-bit byte as 8 bit binary string
//---------------------------------------------------------------------------------

void print8Bits(uint8_t myByte) {
  for (uint8_t mask = 0x80; mask; mask >>= 1) {
    if (mask  & myByte)
      Serial.print('1');
    else
      Serial.print('0');
  }
}

//---------------------------------------------------------------------------------
// print 16-bit word as 16 bit binary string
//---------------------------------------------------------------------------------

void print16Bits(uint16_t myWord) {
  for (uint16_t mask = 0x8000; mask; mask >>= 1) {
    if (mask  & myWord)
      Serial.print('1');
    else
      Serial.print('0');
  }
}


//---------------------------------------------------------------------------------
// crPrintHEX print value as hex with specified number of digits
//---------------------------------------------------------------------------------

void crPrintHEX(unsigned long DATA, unsigned char numChars) {
  unsigned long mask  = 0x0000000F;
  mask = mask << 4 * (numChars - 1);
  Serial.print("0x");
  for (unsigned int eID = numChars; eID > 0;  --eID) {
    Serial.print(((DATA & mask) >> (eID - 1) * 4), HEX);
    mask = mask >> 4;
  }
  Serial.print("  ");
}

Useful references -
  1. SPI - Serial Peripheral Interface - for Arduino (gammon, 2011)
  2. MCP23017 Interrupts (gammon, 2013)
(Dec 29, 2016)

Wednesday, December 14, 2016

Arduino and MCP23S17 Port Expander (Part 3)

Testing the MCP23S17 Port Expander — 
In the past two Parts of this series, the Microchip MCP23S17 Port Expander was installed either in a single chip breadboard prototype or as a quad-chip Arduino backpack. In the sketches, there was a lot of redundant code due to having multiple MCP Bank objects. In this Part 3, a sketch is provided that shows how to pass the instantiated Bankx objects to a function. As an example, the quad backpack has four (4) MCP23S17 chips, instantiated as Bank0, Bank1, Bank2, and Bank3. To process each of these objects, separate functions with identical internal code could be written for each. Alternatively, the object can be passed to a function, so that only one copy of the code has to be written. The SetChipPins() function used in Part 1 can be modified to pass the Bank object to another function setPin(&object). Each of the four Banks are processed using that same code.

// Set all Chip pins to desired mode
void setChipPins() {
// Example: pass the Bank object to the setPin function

    setPin(Bank0);
    setPin(Bank1);
    setPin(Bank2);
    setPin(Bank3);
}

void setPin(MCP23S17 &bank) {
    for (uint8_t i = 0; i <= 15; i++)
    bank.pinMode(i, INPUT_PULLUP);
}

The following sketch will print one line on Serial Monitor when one or more MCP23S17 input pins are pulled LOW (i.e., shorted with switch or wire to ground). The binary string that is printed will show which Bank digital pin [15,14,...,1,0] is LOW. For example, "Bank2: BIN: 1101111111111111 HEX: 0xDFFF DEC: 57343" shows that the Bank2 input pin 13 (INPUT_PULLUP, normally HIGH) was grounded (LOW). The binary display of the Port A and/or Port B pins provides a means of visually checking on the state of 8 or 16 pins. The functions "print8Bits(uint8_t)" and "print16Bits(uint16_t)" handle the binary print to Serial Monitor in place of the standard Serial.print function.

/*
 * MCP23S17_MultiButtonTestInputs_01.ino
 * Library Ref: github.com/MajenkoLibraries/MCP23S17
 *
 * Lowell Bahner
 * 2016-12-11
 *
 * Hardware: Arduino UNO/Mega2560/Mega2560&CH340-Serial-USB
 * IDE: Arduino 1.6.5
 *
 * Useful SPI Ref: gammon.com.au/spi
 *
 * Wire the SPI Interface common lines (Mega2560 SPI pins shown in [ ]):
 * Arduino SPI_MOSI pin 11 [51] <->   SI  MCP23S17 pin 13
 * Arduino SPI_MISO pin 12 [50] <->   SO  MCP23S17 pin 14
 * Arduino SPI_CLOCK pin 13 [52]<->   SCK MCP23S17 pin 12
 * Arduino SPI_CS pin 10 [53]   <->   SS  MCP23S17 pin 11
 * MCP23S17 VDD pin 9 to +5V
 * MCP23S17 VSS pin 10 to Gnd
 * MCP23S17 Reset pin 18 to +5V
 * MCP23S17 Interrupt pins 19-20
 * MCP23S17 A0,A1,A2 pins connect to Gnd or +5V (000 = Address 0, 100 = Address 1, 010 = Address 2, etc)
 * MCP23S17 Port A pins are chip pins 21-28
 * MCP23S17 Port B pins are chip pins 1-8
 *
 * This sketch tests button inputs.
 * Temporarily short each MCP23S17 pin to ground to test for continuity.
 * If a pin is grounded the pin value will print to Serial Monitor.
 *
 * This sketch instantiates 4 MCP23S17 chips.
 * 
 * This code passes the Bank objects as parameters to a function
 *
 * This code was tested on UNO and Mega2560
 * using the ICSP pins for SPI addressing.
 *
 * On each chip, Port A and Port B are set to INPUT_PULLUP.
 * Buttons are connected to one or more Port A and B inputs.
 *
 * Port A & B INPUT_PULLUP pins are set HIGH.
 * When a switch sets the chip's Port A or B input pin to LOW
 * both Port A and Port B 0-7 pin values are read as uint8_t
 * (byte) values and printed on Serial Output. oneTime variable
 * limits printing of multiple values to Serial Monitor.
 *
 * Majenko MCP23S17 library functions (see .h,.cpp files for details):
 *
 *  bank.pinMode(uint8_t pin, uint8_t mode)
 *    where modes are:
 *      OUTPUT - sets pin HIGH
 *      INPUT  - sets pin LOW for sensing change to HIGH +V
 *      INPUT_PULLUP - sets pin HIGH for sensing change to LOW 0V
 *
 *  bank.digitalWrite(uint8_t pin, uint8_t value)
 *    if mode=OUTPUT, pin value = LOW (0) or HIGH (1)
 *    if mode=INPUT, value LOW disables pullup on pin, value HIGH enables pullup on pin
 *
 *  uint8_t value = bank.digitalRead(uint8_t pin)
 *    read the state of the pin
 *
 *  uint8_t value = bank.readPort(uint8_t port)
 *    read entire 8-bit port of all INPUT pins on Port A (port=0) or Port B (port>=1)
 *
 *  uint16_t longValue = bank.readPort()
 *    read both ports as 16-bit combined
 *
 *  bank.writePort(uint8_t port, uint8_t val)
 *    write val (0 or 1, hex example 0x55) to all OUTPUT pins on Port A (port=0) or Port B (port>=1)
 *
 *  bank.writePort(uint16_t val)
 *    write val (hex example 0x55AA) to all OUTPUT pins on Port A and Port B
 *
 *  Also, interrupt functions are defined:
 *      enableInterrupt(uint8_t pin, uint8_t type);
 *      void disableInterrupt(uint8_t pin);
 *      void setMirror(boolean m);
 *      uint16_t getInterruptPins();
 *      uint16_t getInterruptValue();
 *      void setInterruptLevel(uint8_t level);
 *      void setInterruptOD(boolean openDrain);
 *
 *
 *
 *
Run Sketch: Example Serial Monitor Output

-- MCP23S17_MultiButtonTestInputs_01 --

Bank0: BIN: 1111111111111110  HEX: 0xFFFE    DEC: 65534
Bank0: BIN: 1111011111111111  HEX: 0xF7FF    DEC: 63487
Bank1: BIN: 1111111111101111  HEX: 0xFFEF    DEC: 65519
Bank2: BIN: 1101111111111111  HEX: 0xDFFF    DEC: 57343
Bank3: BIN: 1111011111111111  HEX: 0xF7FF    DEC: 63487

 *
 *
 *
 */

// Majenko MCP23S17 Library (revised 2017-Jan-23)
#include <MCP23S17.h>

// Arduino Library SPI.h
#include <SPI.h>

// SPI CS/SS chipselect pin can be changed by user as desired
const uint8_t chipSelect = 10;

// Limit when printing occurs
uint8_t limitPrint = 1; // if 1, then limit printing to button push events

// Limit pin print value to one time
uint16_t oneTime0 = 0;

// example Arduino board pin
uint8_t pin7 = 7;

// Create an object for each chip
// Bank0 is address 0. Pins A0,A1,A2 grounded.
// Bank1 is address 1. Pin A0=+5V, A1,A2 grounded.
// Bank2 is address 2. Pin A1=+5V, A0,A2 grounded.
// Bank3 is address 3. Pin A0,A1=+5V, A2 grounded.

MCP23S17 Bank0(&SPI, chipSelect, 0);
MCP23S17 Bank1(&SPI, chipSelect, 1);
MCP23S17 Bank2(&SPI, chipSelect, 2);
MCP23S17 Bank3(&SPI, chipSelect, 3);

void setup() {

  Serial.begin(9600);

  Serial.print("\n -- MCP23S17_MultiButtonTestInputs_01 --"); Serial.println("");

  Bank0.begin();
  Bank1.begin();
  Bank2.begin();
  Bank3.begin();

  // LED connected to Arduino pin 7 to test inter-device communication
  pinMode(pin7, OUTPUT);

  //
  // Set MCP23S17 pin modes:
  // example set one pin: chip.pinMode(0, OUTPUT); // sets chip pin 0 (Port A 0) to OUTPUT mode
  //
  setChipPins(); // use loop to set individual pins

}

// Set all Chip pins to desired mode
void setChipPins() {
  // Example: pass the Bank object to the setPin function

  setPin(Bank0);
  setPin(Bank1);
  setPin(Bank2);
  setPin(Bank3);
}

void setPin(MCP23S17 &bank) {
  for (uint8_t i = 0; i <= 15; i++)
    bank.pinMode(i, INPUT_PULLUP);
}

//
// Read a button push on input pin
// 1) Switch an INPUT_PULLUP pin to ground
// 2) Read the pin value
// 3) Write the value to Serial Monitor
// Use functions digitalRead(pin) and digitalWrite(pin,value)
//

//
// Read Bank values
//
void RWinputs(MCP23S17 &bank, uint8_t id) {

  // read the Chip pin values
  // id is just passed so the bank is identified in Serial.print
  // id could also be used in functions that take different actions for
  // different objects
  //
  uint16_t value = bank.readPort();

  if (oneTime0 != value) {
    if ((limitPrint < 1) || (value < 65535)) {
      Serial.print("\nBank"); Serial.print(id);
      Serial.print(": BIN: "); print16Bits(value);
      Serial.print("  HEX: "); crPrintHEX(value, 4);
      Serial.print("  DEC: "); Serial.println(value, DEC);
      oneTime0 = value;
    }
  }
}


void loop() {

  // Read Switches on Port A & B and Write Switch settings
  // to Serial Monitor
  RWinputs(Bank0, 0);
  RWinputs(Bank1, 1);
  RWinputs(Bank2, 2);
  RWinputs(Bank3, 3);

  delay(100);

}

//---------------------------------------------------------------------------------
// print 8-bit byte as 8 bit binary string
//---------------------------------------------------------------------------------


void print8Bits(uint8_t myByte) {
  for (byte mask = 0x80; mask; mask >>= 1) {
    if (mask  & myByte)
      Serial.print('1');
    else
      Serial.print('0');
  }
}

//---------------------------------------------------------------------------------
// print 16-bit word as 16 bit binary string
//---------------------------------------------------------------------------------

void print16Bits(uint16_t myWord) {
  for (uint16_t mask = 0x8000; mask; mask >>= 1) {
    if (mask  & myWord)
      Serial.print('1');
    else
      Serial.print('0');
  }
}


//---------------------------------------------------------------------------------
// crPrintHEX print value as hex with specified number of digits
//---------------------------------------------------------------------------------

void crPrintHEX(unsigned long DATA, unsigned char numChars) {
  unsigned long mask  = 0x0000000F;
  mask = mask << 4 * (numChars - 1);
  Serial.print("0x");
  for (unsigned int i = numChars; i > 0; --i) {
    Serial.print(((DATA & mask) >> (i - 1) * 4), HEX);
    mask = mask >> 4;
  }

  Serial.print("  ");
}

  Checking on SPI -- Initially the quad MCP23S17 backpack did not work as expected due to some missing wires on the board. A Syscomp CGR-101 oscilloscope was used to take a look at the SPI pins to see if SPI was working. If you experience issues with your hardware, a small oScope can be useful. The oScope was almost essential trying to figure out how rotary encoders worked. Encoders will be discussed in future blogs.

The following image shows the CS pin (Channel B, blue) displayed against SPI pin SCK (Channel A, red). If a similar pattern is not displayed on an oScope when the sketch is running, you can be pretty sure that SPI is not functioning.



Useful references -


  1. SPI - Serial Peripheral Interface - for Arduino (gammon, 2011)


(Dec 14, 2016)

Monday, December 12, 2016

Arduino and MCP23S17 Port Expander (Part 2)

Adding a Second MCP23S17 Port Expander — 
Adding a second MCP23S17 adds 16 additional pins and is not difficult to accomplish. The second (or more) chip just has to have +V and Ground connections for power, each IC chip pin 18 (Reset) needs to have the 10K resistor to +V, each of the A0, A1, and A2 pins must be either Ground or +V, the CS pin and the three SPI pins (SCK, SI, and SO) must be connected together between chips to the Arduino CS ,CLK, MOSI, and MISO pins. You now have 16 or more additional pins.
Since that was easy, we will skip ahead to building a quad MCP23S17 Port Expander backpack for the Arduino. Four MCP23S17 chips were added to a backpack expander board that fits on both an Uno and Mega 2560 Arduino. SPI on the Arduino uses 3 SPI pins (Uno pins D11, D12, D13, or on Mega 2560 pins D50, D51, D52). SPI also requires a CS pin which is used to select which SPI device is active at any given time. The default Arduino CS pin is D10 on Uno and D53 on Mega 2560. The SPI pins are connected to the Arduino ICSP pins, so with careful construction adding an ICSP 6-pin jack, the backpack will automatically work with either the Uno or Mega. The only requirement is to connect the CS pin which is not part of the ICSP configuration. Pin D10 was used for CS with this backpack. The ICSP does provide +V and Ground connections, which would allow the port expander board to be mounted separately from the Arduino, while using a 6-wire ICSP jumper for power and SPI. The 6th ICSP wire is for Reset which is not used in this application, so a 7th wire would be required for the CS connection. The Arduino pins just pass through the backpack board so that other backpacks could be placed on top. Only the SPI and CS pins, and V+ and Ground, make a connection between the boards.


Pictures: The finished backpack mounted on a Mega 2560 uses the ICSP for SPI and D10 for CS. Each MCP digital pin is brought out to an 8-pin header (Port A pins [0...7] and Port B pins [8...15]). The MCP23S17 interrupt pins (INTA and INTB x 4 chips = 8) were brought out to a separate header where they could be used for processing interrupts. The backside of the port expander backpack is a tangle of wires required to make all of the necessary connections.  An etched and drilled circuit board would have made this much easier.
MultiTest Sketch --
The following MCP23S17_MultiTest_01.ino sketch code is lengthy but it provides several different examples of using the Majenko library with the quad port expander. Each example can be turned off by commenting out the function calls in loop(). The code instantiates the 4 MCP23S17 chips on the quad backpack, various combinations of input and output pins are set, pins are read and written, 8-pin ports are read and written, and long 16-pin (Port A & Port B) words are read and written. Then, 16 pins are sequentially set and cleared with counts displayed to Serial Monitor in decimal, hex, and binary. This process is conducted for each of the four chips. The code demonstrates how to do each of these functions so they can be used in other applications.
This code does not conduct any interrupt processing. Interrupts will be covered in detail in future parts of this series.

/*
 * MCP23S17_MultiTest_01.ino
 * Library Ref: github.com/MajenkoLibraries/MCP23S17
 *
 * Lowell Bahner
 * 2016-12-11
 *
 * Hardware: Arduino UNO / Mega2560
 * IDE: Arduino 1.6.5
 *
 * Useful SPI Ref: gammon.com.au/spi
 *
 * Wire the SPI Interface common lines (Mega2560 SPI pins shown in [ ]):
 * Arduino SPI_MOSI pin 11 [51] <->   SI  MCP23S17 pin 13
 * Arduino SPI_MISO pin 12 [50] <->   SO  MCP23S17 pin 14
 * Arduino SPI_CLOCK pin 13 [52]<->   SCK MCP23S17 pin 12
 * Arduino SPI_CS pin 10 [53]   <->   SS  MCP23S17 pin 11
 * MCP23S17 VDD pin 9 to +5V
 * MCP23S17 VSS pin 10 to Gnd
 * MCP23S17 Reset pin 18 to +5V
 * MCP23S17 Interrupt pins 19-20
 * MCP23S17 A0,A1,A2 pins connect to Gnd or +5V (000 = Address 0, 100 = Address 1, 010 = Address 2, etc)
 * MCP23S17 Port A pins are chip pins 21-28
 * MCP23S17 Port B pins are chip pins 1-8
 *
 * This sketch runs a series of functions to demonstrate library capabilities.
 * This sketch does not demonstrate interrupt functionality.
 *
 * This sketch instantiates 4 MCP23S17 chips.
 * Bank1 (MCP23S17 address 1) pins are set as INPUT or INPUT_PULLUP.
 *    Normally HIGH, use switch to set a pin LOW.
 * Bank0 (MCP23S17 address 0) pins are set as OUTPUT.
 *    MCP23S17 outputs will power LEDs up to 25ma through 1000ohm resistor to ground.
 *    
 * Bank3 (MCP23S17 address 3) pins are set as INPUT or INPUT_PULLUP.
 *    Normally HIGH, use switch to set a pin LOW.
 * Bank2 (MCP23S17 address 2) pins are set as OUTPUT.
 *    MCP23S17 outputs will power LEDs up to 25ma through 1000ohm resistor to ground.
 *
 * Majenko MCP23S17 library functions (see .h,.cpp files for details):
 *
 *  bank.pinMode(uint8_t pin, uint8_t mode)
 *    where modes are:
 *      OUTPUT - sets pin HIGH
 *      INPUT  - sets pin LOW for sensing change to HIGH +V
 *      INPUT_PULLUP - sets pin HIGH for sensing change to LOW 0V
 *
 *  bank.digitalWrite(uint8_t pin, uint8_t value)
 *    if mode=OUTPUT, pin value = LOW (0) or HIGH (1)
 *    if mode=INPUT, value LOW disables pullup on pin, value HIGH enables pullup on pin
 *
 *  uint8_t value = bank.digitalRead(uint8_t pin)
 *    read the state of the pin
 *
 *  uint8_t value = bank.readPort(uint8_t port)
 *    read entire 8-bit port of all INPUT pins on Port A (port=0) or Port B (port>=1)
 *
 *  uint16_t longValue = bank.readPort()
 *    read both ports as 16-bit combined
 *
 *  bank.writePort(uint8_t port, uint8_t val)
 *    write val (0 or 1, hex example 0x55) to all OUTPUT pins on Port A (port=0) or Port B (port>=1)
 *
 *  bank.writePort(uint16_t val)
 *    write val (hex example 0x55AA) to all OUTPUT pins on Port A and Port B
 *
 *  Also, interrupt functions are defined:
 *      enableInterrupt(uint8_t pin, uint8_t type);
 *      void disableInterrupt(uint8_t pin);
 *      void setMirror(boolean m);
 *      uint16_t getInterruptPins();
 *      uint16_t getInterruptValue();
 *      void setInterruptLevel(uint8_t level);
 *      void setInterruptOD(boolean openDrain);
 *
 *
 *
 *
Run Sketch: Serial Monitor Output

 -- RWpins() --

Bank1: Switch i15: 1 (Push button switch not pushed, Bank1 INPUT pin 15 is HIGH +5V)
Bank0: Output j4:  1 (Bank0 OUTPUT pin 4 is HIGH.
                      Arduino pin 7 is set HIGH.
                      LED on pin 4 is ON. LED on pin 7 is ON.)

Bank1: Switch i15: 0 (Push button switch pushed, grounds Bank1 INPUT pin 15)
Bank0: Output j4:  0 (Bank0 OUTPUT pin 4 switches to LOW.
                      Arduino pin 7 is set LOW.
                      LED on pin 4 is OFF. LED on pin 7 is OFF.)

 -- RWport() --

Bank0: Port A BIN: 00000000
Bank0: Port B BIN: 00000000

Bank0: Port A BIN: 11110100  DEC: 244
Bank0: Port B BIN: 00001011  DEC: 11

 -- RWlongPortValue() --

Bank0: Port A & B BIN: 0000101111110100  HEX: 0x0BF4    DEC: 3060

 -- RWlongPort() --

Bank0: Port A & B BIN: 1011100101000010  HEX: 0xB942    DEC: 47426

 -- CyclePins() --

Bank0: Port A & B BIN: 0000000000000000  HEX: 0x0000    DEC: 0
Bank0: Port A & B BIN: 0000000000000001  HEX: 0x0001    DEC: 1
Bank0: Port A & B BIN: 0000000000000011  HEX: 0x0003    DEC: 3
Bank0: Port A & B BIN: 0000000000000111  HEX: 0x0007    DEC: 7
Bank0: Port A & B BIN: 0000000000001111  HEX: 0x000F    DEC: 15
Bank0: Port A & B BIN: 0000000000011111  HEX: 0x001F    DEC: 31
Bank0: Port A & B BIN: 0000000000111111  HEX: 0x003F    DEC: 63
Bank0: Port A & B BIN: 0000000001111111  HEX: 0x007F    DEC: 127
Bank0: Port A & B BIN: 0000000011111111  HEX: 0x00FF    DEC: 255
Bank0: Port A & B BIN: 0000000111111111  HEX: 0x01FF    DEC: 511
Bank0: Port A & B BIN: 0000001111111111  HEX: 0x03FF    DEC: 1023
Bank0: Port A & B BIN: 0000011111111111  HEX: 0x07FF    DEC: 2047
Bank0: Port A & B BIN: 0000111111111111  HEX: 0x0FFF    DEC: 4095
Bank0: Port A & B BIN: 0001111111111111  HEX: 0x1FFF    DEC: 8191
Bank0: Port A & B BIN: 0011111111111111  HEX: 0x3FFF    DEC: 16383
Bank0: Port A & B BIN: 0111111111111111  HEX: 0x7FFF    DEC: 32767
Bank0: Port A & B BIN: 1111111111111111  HEX: 0xFFFF    DEC: 65535

Bank0: Port A & B BIN: 1111111111111111  HEX: 0xFFFF    DEC: 65535
Bank0: Port A & B BIN: 0111111111111111  HEX: 0x7FFF    DEC: 32767
Bank0: Port A & B BIN: 0011111111111111  HEX: 0x3FFF    DEC: 16383
Bank0: Port A & B BIN: 0001111111111111  HEX: 0x1FFF    DEC: 8191
Bank0: Port A & B BIN: 0000111111111111  HEX: 0x0FFF    DEC: 4095
Bank0: Port A & B BIN: 0000011111111111  HEX: 0x07FF    DEC: 2047
Bank0: Port A & B BIN: 0000001111111111  HEX: 0x03FF    DEC: 1023
Bank0: Port A & B BIN: 0000000111111111  HEX: 0x01FF    DEC: 511
Bank0: Port A & B BIN: 0000000011111111  HEX: 0x00FF    DEC: 255
Bank0: Port A & B BIN: 0000000001111111  HEX: 0x007F    DEC: 127
Bank0: Port A & B BIN: 0000000000111111  HEX: 0x003F    DEC: 63
Bank0: Port A & B BIN: 0000000000011111  HEX: 0x001F    DEC: 31
Bank0: Port A & B BIN: 0000000000001111  HEX: 0x000F    DEC: 15
Bank0: Port A & B BIN: 0000000000000111  HEX: 0x0007    DEC: 7
Bank0: Port A & B BIN: 0000000000000011  HEX: 0x0003    DEC: 3
Bank0: Port A & B BIN: 0000000000000001  HEX: 0x0001    DEC: 1
Bank0: Port A & B BIN: 0000000000000000  HEX: 0x0000    DEC: 0
 *
 *
 *
 */

// Majenko MCP23S17 Library (revised 2017-Jan-23)
#include <MCP23S17.h>

// Arduino Library SPI.h
#include <SPI.h>

// SPI CS/SS chipselect pin can be changed by user as desired
const uint8_t chipSelect = 10; // use for Uno or Mega

// example Arduino board pin
uint8_t pin7 = 7;

// Create an object for each chip
// Bank0 is address 0. Pins A0,A1,A2 grounded.
// Bank1 is address 1. Pin A0=+5V, A1,A2 grounded.

MCP23S17 Bank0(&SPI, chipSelect, 0);
MCP23S17 Bank1(&SPI, chipSelect, 1);
MCP23S17 Bank2(&SPI, chipSelect, 2);
MCP23S17 Bank3(&SPI, chipSelect, 3);

void setup() {

  // Slow down the master a bit if desired
  // SPI.setClockDivider(SPI_CLOCK_DIV8);

  Serial.begin(9600);

  Bank0.begin();
  Bank1.begin();
  Bank2.begin();
  Bank3.begin();

  // LED connected to Arduino pin 7 to test inter-device communication
  pinMode(pin7, OUTPUT);

  //
  // Set MCP23S17 pin modes:
  // example set one pin: chip.pinMode(0, OUTPUT); // sets chip pin 0 (Port A 0) to OUTPUT mode
  //
  setChipPins(); // use loop to set individual pins
  // or, since this library does not have a setPortPinMode, do 8 pin loops
  //setPortPins(); // example: set pins on each Port on each chip to different modes.
  //

}

// Set all Chip pins to desired mode
void setChipPins() {
  // Example usage

  // Set all Bank0 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank0.pinMode(i, OUTPUT);

  // Set all Bank1 pins to be INPUT_PULLUP
  for (uint8_t i = 0; i <= 15; i++)
    //Bank1.pinMode(i, INPUT);  // unswitched pin voltages about +0.1V, switch must pull pin to +5V
    Bank1.pinMode(i, INPUT_PULLUP);  // unswitched pin voltages about +4.9V, switch must ground pin

  // Set all Bank2 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank2.pinMode(i, OUTPUT);

  // Set all Bank3 pins to be INPUT_PULLUP
  for (uint8_t i = 0; i <= 15; i++)
    //Bank3.pinMode(i, INPUT);  // unswitched pin voltages about +0.1V, switch must pull pin to +5V
    Bank3.pinMode(i, INPUT_PULLUP);  // unswitched pin voltages about +4.9V, switch must ground pin
}

// Set all Chip pins to OUTPUT mode
void setAllPins() {
  // Example usage

  // Set all Bank0 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank0.pinMode(i, OUTPUT);

  // Set all Bank1 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank1.pinMode(i, OUTPUT);

  // Set all Bank2 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank2.pinMode(i, OUTPUT);

  // Set all Bank3 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank3.pinMode(i, OUTPUT);
}

// Set Port pins to desired mode
void setPortPins() {
  // Example usage

  // Set all Bank0 Port A pins to OUTPUT
  for (uint8_t i = 0; i <= 7; i++)
    Bank0.pinMode(i, OUTPUT);

  // Set all Bank0 Port B pins to OUTPUT
  for (uint8_t i = 8; i <= 15; i++)
    Bank0.pinMode(i, OUTPUT);

  // Set all Bank1 Port A pins to be INPUT_PULLUP
  for (uint8_t i = 0; i <= 7; i++)
    Bank1.pinMode(i, INPUT_PULLUP);

  // Set all Bank1 Port B pins to be INPUT_PULLUP
  for (uint8_t i = 8; i <= 15; i++)
    Bank1.pinMode(i, INPUT_PULLUP);

  // Set all Bank2 Port A pins to OUTPUT
  for (uint8_t i = 0; i <= 7; i++)
    Bank2.pinMode(i, OUTPUT);

  // Set all Bank2 Port B pins to OUTPUT
  for (uint8_t i = 8; i <= 15; i++)
    Bank2.pinMode(i, OUTPUT);

  // Set all Bank3 Port A pins to be INPUT_PULLUP
  for (uint8_t i = 0; i <= 7; i++)
    Bank3.pinMode(i, INPUT_PULLUP);

  // Set all Bank3 Port B pins to be INPUT_PULLUP
  for (uint8_t i = 8; i <= 15; i++)
    Bank3.pinMode(i, INPUT_PULLUP);
}


//
// Change LED On/Off with button push
// 1) Switch an INPUT_PULLUP pin to ground
// 2) Read the pin value
// 3) Write the value to an OUTPUT pin
// 4) Turn LED On/Off
// Use functions digitalRead(pin) and digitalWrite(pin,value)
//
void RWpins01() {

  setChipPins();

  Serial.print("\n -- RWpins01() --"); Serial.println("");

  uint8_t b15 = 0;  // Bank1 input pin value (0 push LOW, 1 normally open HIGH) with push button switch to gnd
  uint8_t i = 15;   // Bank1 pin number
  uint8_t a4 = 0;   // Bank0 output pin value
  uint8_t j = 4;    // Bank0 pin number

  // read the switch value into b15
  b15 = Bank1.digitalRead(i);
  Serial.print("\nBank1: Switch i"); Serial.print(i); Serial.print(": ");  Serial.println(b15);

  // write the switch pin i, value b15, to the output pin j
  Bank0.digitalWrite(j, b15);

  a4 = Bank0.digitalRead(j);
  Serial.print("Bank0: Output j"); Serial.print(j); Serial.print(": ");  Serial.println(a4);

  // Turn on/off LED connected to Arduino pin 7 through resistor to ground
  digitalWrite(pin7, a4);
}

void RWpins23() {

  setChipPins();

  Serial.print("\n -- RWpins23() --"); Serial.println("");

  uint8_t b15 = 0;  // Bank1 input pin value (0 push LOW, 1 normally open HIGH) with push button switch to gnd
  uint8_t i = 15;   // Bank1 pin number
  uint8_t a4 = 0;   // Bank0 output pin value
  uint8_t j = 4;    // Bank0 pin number

  // read the switch value into b15
  b15 = Bank3.digitalRead(i);
  Serial.print("\nBank3: Switch i"); Serial.print(i); Serial.print(": ");  Serial.println(b15);

  // write the switch pin i, value b15, to the output pin j
  Bank2.digitalWrite(j, b15);

  a4 = Bank2.digitalRead(j);
  Serial.print("Bank2: Output j"); Serial.print(j); Serial.print(": ");  Serial.println(a4);

  // Turn on/off LED connected to Arduino pin 7 through resistor to ground
  digitalWrite(pin7, a4);
}


//
// Read and Write Port values
//
void RWport0() {

  Serial.print("\n -- RWport0() --"); Serial.println("");

  // set the port pin modes
  setAllPins(); // all Bank0 pins are OUTPUT but set as Ports

  // clear the port pin values to 0
  uint8_t port = 0; // Port A
  uint8_t val = 0x00; // set to 0000 0000
  Bank0.writePort(port, val);
  port = 1; // Port B
  Bank0.writePort(port, val); // set to 0000 0000

  // read the port pin values
  uint8_t valueA = Bank0.readPort(0);
  Serial.print("\nBank0: Port A BIN: "); print8Bits(valueA); Serial.println("");
  uint8_t valueB = Bank0.readPort(1);
  Serial.print("Bank0: Port B BIN: "); print8Bits(valueB); Serial.println("");

  // set the port pin values
  port = 0; // Port A
  val = 0xF4; // set to 1111 0100
  Bank0.writePort(port, val);
  port = 1; // Port B
  val = 0x0B; // set to 0000 1011
  //val = 0xB0; // set to 1011 0000
  Bank0.writePort(port, val);

  // read the port pin values
  valueA = Bank0.readPort(0);
  Serial.print("\nBank0: Port A BIN: "); print8Bits(valueA);
  Serial.print("  DEC: "); Serial.println(valueA, DEC);

  valueB = Bank0.readPort(1);
  Serial.print("Bank0: Port B BIN: "); print8Bits(valueB);
  Serial.print("  DEC: "); Serial.println(valueB, DEC);
}

void RWport1() {

  Serial.print("\n -- RWport1() --"); Serial.println("");

  // set the port pin modes
  setAllPins(); // all Bank0 pins are OUTPUT but set as Ports

  // clear the port pin values to 0
  uint8_t port = 0; // Port A
  uint8_t val = 0x00; // set to 0000 0000
  Bank1.writePort(port, val);
  port = 1; // Port B
  Bank1.writePort(port, val); // set to 0000 0000

  // read the port pin values
  uint8_t valueA = Bank1.readPort(0);
  Serial.print("\nBank1: Port A BIN: "); print8Bits(valueA); Serial.println("");
  uint8_t valueB = Bank1.readPort(1);
  Serial.print("Bank1: Port B BIN: "); print8Bits(valueB); Serial.println("");

  // set the port pin values
  port = 0; // Port A
  val = 0xF4; // set to 1111 0100
  Bank1.writePort(port, val);
  port = 1; // Port B
  val = 0x0B; // set to 0000 1011
  //val = 0xB0; // set to 1011 0000
  Bank1.writePort(port, val);

  // read the port pin values
  valueA = Bank1.readPort(0);
  Serial.print("\nBank1: Port A BIN: "); print8Bits(valueA);
  Serial.print("  DEC: "); Serial.println(valueA, DEC);

  valueB = Bank1.readPort(1);
  Serial.print("Bank1: Port B BIN: "); print8Bits(valueB);
  Serial.print("  DEC: "); Serial.println(valueB, DEC);
}

void RWport2() {

  Serial.print("\n -- RWport2() --"); Serial.println("");

  // set the port pin modes
  setAllPins(); // all Bank2 pins are OUTPUT but set as Ports

  // clear the port pin values to 0
  uint8_t port = 0; // Port A
  uint8_t val = 0x00; // set to 0000 0000
  Bank2.writePort(port, val);
  port = 1; // Port B
  Bank2.writePort(port, val); // set to 0000 0000

  // read the port pin values
  uint8_t valueA = Bank2.readPort(0);
  Serial.print("\nBank2: Port A BIN: "); print8Bits(valueA); Serial.println("");
  uint8_t valueB = Bank2.readPort(1);
  Serial.print("Bank2: Port B BIN: "); print8Bits(valueB); Serial.println("");

  // set the port pin values
  port = 0; // Port A
  val = 0xF4; // set to 1111 0100
  Bank2.writePort(port, val);
  port = 1; // Port B
  val = 0x0B; // set to 0000 1011
  //val = 0xB0; // set to 1011 0000
  Bank2.writePort(port, val);

  // read the port pin values
  valueA = Bank2.readPort(0);
  Serial.print("\nBank2: Port A BIN: "); print8Bits(valueA);
  Serial.print("  DEC: "); Serial.println(valueA, DEC);

  valueB = Bank2.readPort(1);
  Serial.print("Bank2: Port B BIN: "); print8Bits(valueB);
  Serial.print("  DEC: "); Serial.println(valueB, DEC);
}

void RWport3() {

  Serial.print("\n -- RWport3() --"); Serial.println("");

  // set the port pin modes
  setAllPins(); // all Bank3 pins are OUTPUT but set as Ports

  // clear the port pin values to 0
  uint8_t port = 0; // Port A
  uint8_t val = 0x00; // set to 0000 0000
  Bank3.writePort(port, val);
  port = 1; // Port B
  Bank3.writePort(port, val); // set to 0000 0000

  // read the port pin values
  uint8_t valueA = Bank3.readPort(0);
  Serial.print("\nBank3: Port A BIN: "); print8Bits(valueA); Serial.println("");
  uint8_t valueB = Bank3.readPort(1);
  Serial.print("Bank3: Port B BIN: "); print8Bits(valueB); Serial.println("");

  // set the port pin values
  port = 0; // Port A
  val = 0xF4; // set to 1111 0100
  Bank3.writePort(port, val);
  port = 1; // Port B
  val = 0x0B; // set to 0000 1011
  //val = 0xB0; // set to 1011 0000
  Bank3.writePort(port, val);

  // read the port pin values
  valueA = Bank3.readPort(0);
  Serial.print("\nBank3: Port A BIN: "); print8Bits(valueA);
  Serial.print("  DEC: "); Serial.println(valueA, DEC);

  valueB = Bank3.readPort(1);
  Serial.print("Bank3: Port B BIN: "); print8Bits(valueB);
  Serial.print("  DEC: "); Serial.println(valueB, DEC);
}


void RWlongPortValue0() {

  Serial.print("\n -- RWlongPortValue0() --"); Serial.println("");

  // Read the Bank0 Port pin values as a combined long value
  uint16_t valueLong = Bank0.readPort();
  Serial.print("\nBank0: Port A & B BIN: "); print16Bits(valueLong);
  Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
  Serial.print("  DEC: "); Serial.println(valueLong, DEC);
}

void RWlongPortValue1() {

  Serial.print("\n -- RWlongPortValue1() --"); Serial.println("");

  // Read the Bank1 Port pin values as a combined long value
  uint16_t valueLong = Bank1.readPort();
  Serial.print("\nBank1: Port A & B BIN: "); print16Bits(valueLong);
  Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
  Serial.print("  DEC: "); Serial.println(valueLong, DEC);
}

void RWlongPortValue2() {

  Serial.print("\n -- RWlongPortValue2() --"); Serial.println("");

  // Read the Bank2 Port pin values as a combined long value
  uint16_t valueLong = Bank2.readPort();
  Serial.print("\nBank2: Port A & B BIN: "); print16Bits(valueLong);
  Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
  Serial.print("  DEC: "); Serial.println(valueLong, DEC);
}

void RWlongPortValue3() {

  Serial.print("\n -- RWlongPortValue3() --"); Serial.println("");

  // Read the Bank3 Port pin values as a combined long value
  uint16_t valueLong = Bank3.readPort();
  Serial.print("\nBank3: Port A & B BIN: "); print16Bits(valueLong);
  Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
  Serial.print("  DEC: "); Serial.println(valueLong, DEC);
}


void RWlongPort0() {

  Serial.print("\n -- RWlongPort0() --"); Serial.println("");

  // Write the Bank0 Port pin values as a combined long value
  uint16_t valueLong = 0xB942;
  Bank0.writePort(valueLong);

  // Read the Bank0 Port pin values as a combined long value
  uint16_t valueLongIn = Bank0.readPort();
  Serial.print("\nBank0: Port A & B BIN: "); print16Bits(valueLongIn);
  Serial.print("  HEX: "); crPrintHEX(valueLongIn, 4);
  Serial.print("  DEC: "); Serial.println(valueLongIn, DEC);
}

void RWlongPort1() {

  Serial.print("\n -- RWlongPort1() --"); Serial.println("");

  // Write the Bank1 Port pin values as a combined long value
  uint16_t valueLong = 0xB942;
  Bank1.writePort(valueLong);

  // Read the Bank1 Port pin values as a combined long value
  uint16_t valueLongIn = Bank1.readPort();
  Serial.print("\nBank1: Port A & B BIN: "); print16Bits(valueLongIn);
  Serial.print("  HEX: "); crPrintHEX(valueLongIn, 4);
  Serial.print("  DEC: "); Serial.println(valueLongIn, DEC);
}

void RWlongPort2() {

  Serial.print("\n -- RWlongPort2() --"); Serial.println("");

  // Write the Bank2 Port pin values as a combined long value
  uint16_t valueLong = 0xB942;
  Bank2.writePort(valueLong);

  // Read the Bank2 Port pin values as a combined long value
  uint16_t valueLongIn = Bank2.readPort();
  Serial.print("\nBank2: Port A & B BIN: "); print16Bits(valueLongIn);
  Serial.print("  HEX: "); crPrintHEX(valueLongIn, 4);
  Serial.print("  DEC: "); Serial.println(valueLongIn, DEC);
}

void RWlongPort3() {

  Serial.print("\n -- RWlongPort3() --"); Serial.println("");

  // Write the Bank3 Port pin values as a combined long value
  uint16_t valueLong = 0xB942;
  Bank3.writePort(valueLong);

  // Read the Bank3 Port pin values as a combined long value
  uint16_t valueLongIn = Bank3.readPort();
  Serial.print("\nBank3: Port A & B BIN: "); print16Bits(valueLongIn);
  Serial.print("  HEX: "); crPrintHEX(valueLongIn, 4);
  Serial.print("  DEC: "); Serial.println(valueLongIn, DEC);
}


//
// Cycle through OUTPUT pins with count-up count-down
//
void CyclePins0() {

  Serial.print("\n -- CyclePins0() --"); Serial.println("\n");

  // Set all Bank0 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank0.pinMode(i, OUTPUT);

  // clear Bank0 using writePort
  uint16_t valueLong = 0x0000;
  Bank0.writePort(valueLong);

  // Read the Bank0 Port pin values as a combined long value
  for (int8_t i = -1; i <= 15; i++) {
    if (i >= 0) Bank0.digitalWrite(i, 1); // i=-1 allows print of 0 data
    valueLong = Bank0.readPort(); // read 16 bits
    Serial.print("Bank0: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
  }

  Serial.println("");

  for (int8_t i = 15; i >= -1; i--) { // i=-1 allows print of 0 data
    // decrement after the read while reading down the values
    valueLong = Bank0.readPort(); //  read 16 bits
    Serial.print("Bank0: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
    Bank0.digitalWrite(i, 0);
  }
}

void CyclePins1() {

  Serial.print("\n -- CyclePins1() --"); Serial.println("\n");

  // Set all Bank1 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank1.pinMode(i, OUTPUT);

  // clear Bank1 using writePort
  uint16_t valueLong = 0x0000;
  Bank1.writePort(valueLong);

  // Read the Bank1 Port pin values as a combined long value
  for (int8_t i = -1; i <= 15; i++) {
    if (i >= 0) Bank1.digitalWrite(i, 1); // i=-1 allows print of 0 data
    valueLong = Bank1.readPort(); // read 16 bits
    Serial.print("Bank1: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
  }

  Serial.println("");

  for (int8_t i = 15; i >= -1; i--) { // i=-1 allows print of 0 data
    // decrement after the read while reading down the values
    valueLong = Bank1.readPort(); //  read 16 bits
    Serial.print("Bank1: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
    Bank1.digitalWrite(i, 0);
  }
}

void CyclePins2() {

  Serial.print("\n -- CyclePins2() --"); Serial.println("\n");

  // Set all Bank2 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank2.pinMode(i, OUTPUT);

  // clear Bank2 using writePort
  uint16_t valueLong = 0x0000;
  Bank2.writePort(valueLong);

  // Read the Bank2 Port pin values as a combined long value
  for (int8_t i = -1; i <= 15; i++) {
    if (i >= 0) Bank2.digitalWrite(i, 1); // i=-1 allows print of 0 data
    valueLong = Bank2.readPort(); // read 16 bits
    Serial.print("Bank2: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
  }

  Serial.println("");

  for (int8_t i = 15; i >= -1; i--) { // i=-1 allows print of 0 data
    // decrement after the read while reading down the values
    valueLong = Bank2.readPort(); //  read 16 bits
    Serial.print("Bank2: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
    Bank2.digitalWrite(i, 0);
  }
}

void CyclePins3() {

  Serial.print("\n -- CyclePins3() --"); Serial.println("\n");

  // Set all Bank3 pins to be OUTPUT
  for (uint8_t i = 0; i <= 15; i++)
    Bank3.pinMode(i, OUTPUT);

  // clear Bank3 using writePort
  uint16_t valueLong = 0x0000;
  Bank3.writePort(valueLong);

  // Read the Bank3 Port pin values as a combined long value
  for (int8_t i = -1; i <= 15; i++) {
    if (i >= 0) Bank3.digitalWrite(i, 1); // i=-1 allows print of 0 data
    valueLong = Bank3.readPort(); // read 16 bits
    Serial.print("Bank3: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
  }

  Serial.println("");

  for (int8_t i = 15; i >= -1; i--) { // i=-1 allows print of 0 data
    // decrement after the read while reading down the values
    valueLong = Bank3.readPort(); //  read 16 bits
    Serial.print("Bank3: Port A & B BIN: "); print16Bits(valueLong);
    Serial.print("  HEX: "); crPrintHEX(valueLong, 4);
    Serial.print("  DEC: "); Serial.println(valueLong, DEC);
    Bank3.digitalWrite(i, 0);
  }
}


void loop() {

  // Change LED On/Off with button push
  RWpins01();
  RWpins23();

  // Read and Write Port pins as a group
  RWport0();
  RWport1();
  RWport2();
  RWport3();

  // Read the Bank0 Port pin values as a combined long value
  RWlongPortValue0();
  RWlongPortValue1();
  RWlongPortValue2();
  RWlongPortValue3();

  // Read and Write Chip 16 pins as a group
  RWlongPort0();
  RWlongPort1();
  RWlongPort2();
  RWlongPort3();

  // Cycle through OUTPUT pins with count-up count-down
  CyclePins0();
  CyclePins1();
  CyclePins2();
  CyclePins3();

  delay(1000);

}

// print 8-bit byte as 8 bit binary string
void print8Bits(uint8_t myByte) {
  for (byte mask = 0x80; mask; mask >>= 1) {
    if (mask  & myByte)
      Serial.print('1');
    else
      Serial.print('0');
  }
}

// print 16-bit word as 16 bit binary string
void print16Bits(uint16_t myWord) {
  for (uint16_t mask = 0x8000; mask; mask >>= 1) {
    if (mask  & myWord)
      Serial.print('1');
    else
      Serial.print('0');
  }
}


//---------------------------------------------------------------------------------
// crPrintHEX print value as hex with specified number of digits
//---------------------------------------------------------------------------------

void crPrintHEX(unsigned long DATA, unsigned char numChars) {
  unsigned long mask  = 0x0000000F;
  mask = mask << 4 * (numChars - 1);
  Serial.print("0x");
  for (unsigned int i = numChars; i > 0; --i) {
    Serial.print(((DATA & mask) >> (i - 1) * 4), HEX);
    mask = mask >> 4;
  }

  Serial.print("  ");
}

Useful references -

(Dec 12, 2016)