MajenkoLibraries / MCP23S17

Arduino library for MCP23S17 IO Expanders
BSD 3-Clause "New" or "Revised" License
45 stars 16 forks source link

Interrupt example #11

Closed thijstriemstra closed 3 years ago

thijstriemstra commented 3 years ago

It would be nice to add an example that shows how to use the interrupts with one or more buttons (and leds).

@majenkotech if you can give me some pointers I can make a pull request for the example if you want.

majenkotech commented 3 years ago

Interrupts are a case of configuring the chip to indicate an interrupt and wiring the right INT pin to an interrupt pin on your MCU. I can't recall what support I have coded into the library for that of hand, I'll have to dig into it.

majenkotech commented 3 years ago

Everything you need should be in the manual.

https://github.com/MajenkoLibraries/MCP23S17/blob/master/latex/refman.pdf

thijstriemstra commented 3 years ago

thanks I'll take a look. Also opened #12 for browsable online html docs.

thijstriemstra commented 3 years ago

Not much luck with that PDF yet, but http://84park.blogspot.com/2016/12/arduino-and-mcp23s17-port-expander-part_29.html contains quite some useful info.

majenkotech commented 3 years ago

It should be a case of calling .enableInterrupt(...) to turn it on in the chip, then in your ISR you'd just call .getInterruptPins() to find what triggered it. You can call .setMirror(true) to make both interrupt pins act as one.

majenkotech commented 3 years ago

That link is very interesting though. I'm integrating their changes into the library.

majenkotech commented 3 years ago

Ok, changes integrated.

thijstriemstra commented 3 years ago

It should be a case of calling .enableInterrupt(...) to turn it on in the chip, then in your ISR you'd just call .getInterruptPins() to find what triggered it. You can call .setMirror(true) to make both interrupt pins act as one.

I'll try to create a minimal working example.

Here's the (modified) example from http://84park.blogspot.com/2016/12/arduino-and-mcp23s17-port-expander-part_29.html that seems to work well but (probably) has a lot of unneeded code. Tested with Arduino Nano v3 clone.

#include <Arduino.h>
#include <MCP23S17.h>
#include <SPI.h>

const uint8_t address = 0;
const uint8_t interruptPinA = 2;
const uint8_t chipSelectPin = 10;

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
volatile uint8_t flagMCPA = 0;
volatile uint8_t flagINTA = 0;
volatile uint8_t flagMCPB = 0;
volatile uint8_t flagINTB = 0;
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

MCP23S17 mcp23s17(&SPI, chipSelectPin, address);

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("  ");
}

// 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);

  // clear all interrupts on this Port Expander
  mcpReading = bank.getInterruptValue();
}

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

  setPin(mcp23s17);
  /*setPin(Bank1);
  setPin(Bank2);
  setPin(Bank3);*/
}

// 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);
  }
}

// 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);
  }
}

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()
}

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();
}

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

// 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("Process port A pin: ");
      Serial.println(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("Process port B pin: ");
      Serial.println(pin);
    }
  }
}

void setup() {
  mcp23s17.begin();
  /*Bank1.begin();
  Bank2.begin();
  Bank3.begin();*/

  pinMode(interruptPinA, INPUT_PULLUP);
  //pinMode(pinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(interruptPinA), interruptA, CHANGE);
  //attachInterrupt(digitalPinToInterrupt(pinB), interruptB, CHANGE);

  Serial.begin(115200);
  Serial.println(F("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;
}

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("loop flagMCPA");
    printInterruptHeader(mcp23s17, 0);
    processPins(mcp23s17, 0); // do some processing of each interrupted pin
    flagMCPA = 0;
  }

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

  //delay(10); // give loop something to do while idle
}
majenkotech commented 3 years ago

Great. Added it as an example.

thijstriemstra commented 3 years ago

You can call .setMirror(true) to make both interrupt pins act as one.

@majenkotech do I have to physically tie INTA and INTB together? how, any resistors needed?

majenkotech commented 3 years ago

No. This makes both respond in the same way as if they were both tied together inside the chip.

thijstriemstra commented 3 years ago

I wasn't sure how to update the Interrupt example to use a single Arduino interrupt for both port A and B, it gave unexpected results when using setMirror(true) so I ended up using 2 interrupt pins. According to the article:

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.

So I'll use 2 interrupt pins on a ESP32 for each MCP23S17 (2 in total) in my project = 4 ESP32 pins for 32 buttons. Not bad.

majenkotech commented 3 years ago

It depends how the interrupt pin is configured. If you set it to "Open Drain" mode then add a pullup all the interrupt pins can wire to the same Arduino INT pin. But then you have to cycle through all the chips to find out which one caused the interrupt.