Setup
I am using a SparkFun ProMicro clone along with two WHDTS MCP23017-E/SS I2C Interface 16 Channel IO Expansion Modules to get 16 input/output channels for button presses.
Intended Function
Once a button is pressed in this circuit a keystroke is transmitted over USB and an LED on the button is lit for 1 second. One button press should correspond to one keystroke.
Problem
During single button presses, or even many button presses at nearly the same time my program works as expected (around 2-3 presses a second). However, if many button presses are going on at the same time (think spamming buttons during a video game speed) the program crashes and must be reset by pulling the USB power/data connection.
What I have tried
I have tried to implement delays in the code to account for single debounce, along with some hardware debouncing circuits to help (see the capacitor/resistor pair across the switch) since I had originally suspected this was the cause of my issue. While these fixes did help get a single button press working, and seemingly helped the number of button presses it could handle the problem still exists.
I can crash the program by spamming two different buttons (e.g. around 4-5 presses a second on each), it does not have to be more than that.
Another strange phenomena that is occurring is that occasionally when hitting the buttons fast an led on the wrong channel will momentarily light up, maybe something goofy going on in the register of the port expander?
Any ideas what could be causing this strange behavior?
Resources I've used
Admittedly the MCP23017 is weird how it handles interrupts. Here is the link to MCP23017 resource I used to make most of the interrupts work:
Switches used:
Link to specific MCP23017 board:
#include <Wire.h>
#include <Keyboard.h>
#include <Adafruit_MCP23017.h>
#include <Centipede.h>
int counter[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
long ledTime[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
unsigned long previousMillis[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
const long intervl = 1000;
int ledState[] = {LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW,LOW};
int pinNumber;
String array[] = {
"Button A Pressed",
"Button B Pressed",
"Button C Pressed",
"Button D Pressed",
"Button E Pressed",
"Button F Pressed",
"Button G Pressed",
"Button H Pressed",
"Button I Pressed",
"Button J Pressed",
"Button K Pressed",
"Button L Pressed",
"Button M Pressed",
"Button N Pressed",
"Button O Pressed",
"Button P Pressed"
};
String array2 = "ABCDEFGHIJKLMNOP";
// MCP23017 setup
// Register bits
#define MCP_INT_MIRROR true // Mirror inta to intb.
#define MCP_INT_ODR false // Open drain.
// Arduino pins
#define INTPIN 7 // Interrupt on this Arduino Uno pin.
#define controlArduioInt attachInterrupt(digitalPinToInterrupt(INTPIN),isr,FALLING)
Adafruit_MCP23017 mcp1;
Adafruit_MCP23017 mcp2;
/////////////////////////////////////////////
void setup(){
Serial.begin(9600);
Keyboard.begin();
mcp1.begin(); // use default address 0
mcp2.begin(1); // use address 1
pinMode(INTPIN,INPUT);
for (int i = 0; i <= 15; i++) {
mcp2.pinMode(i, OUTPUT);
mcp2.digitalWrite(i,ledState[i]);
mcp1.pinMode(i, INPUT);
mcp1.pullUp(i, HIGH);
mcp1.setupInterruptPin(i,CHANGE);
}
mcp1.setupInterrupts(MCP_INT_MIRROR, MCP_INT_ODR, LOW); // The mcp output interrupt pin.
// On interrupt, polariy is set HIGH/LOW (last parameter).
//mcp.setupInterruptPin(MCP_INPUTPIN,CHANGE); // The mcp action that causes an interrupt.
mcp1.readGPIOAB(); // Initialize for interrupts.
controlArduioInt; // Enable Arduino interrupt control.
}
/////////////////////////////////////////////
// The interrupt routine handles LED1
// This is the button press since this is the only active interrupt.
void isr(){
uint8_t p,v;
//static uint16_t ledState=0;
noInterrupts();
// Debounce. Slow I2C: extra debounce between interrupts anyway.
// Can not use delay() in interrupt code.
delayMicroseconds(2000);
// Stop interrupts from external pin.
detachInterrupt(digitalPinToInterrupt(INTPIN));
interrupts(); // re-start interrupts for mcp
p = mcp1.getLastInterruptPin();
// This one resets the interrupt state as it reads from reg INTCAPA(B).
v = mcp1.getLastInterruptPinValue();
pinNumber = p;
// Here either the button has been pushed or released.
if ( p==p && v == 1) { // Test for release - pin pulled high
//if (v == 1) { // Test for release - pin pulled high
ledState[p] = HIGH;
ledTime[p] = millis();
counter[p]++;
}
mcp2.digitalWrite(p, ledState[p]);
controlArduioInt; // Reinstate interrupts from external pin.
}
//////////////////////////////////////////////PPPPPPPPPMIIIJJOO
void loop(){
delay(250);
// Serial.println(pinNumber);
//Serial.println( counter[0]);
//
// mcp.digitalWrite(MCP_LEDTOG1, HIGH);
// mcp.digitalWrite(MCP_LEDTOG2, LOW);
//
// delay(100);
//
// mcp.digitalWrite(MCP_LEDTOG1, LOW);
// mcp.digitalWrite(MCP_LEDTOG2, HIGH);
//Serial.println(counter);
for (int i = 0; i <= 15; i++) {
if ((millis()- ledTime[i]) > intervl){
ledState[i] = LOW;
mcp2.digitalWrite(i, ledState[i]);
}
if (counter[i] >= 1){
//Serial.println(array[i]);
//Serial.println(array2.charAt(i));
Keyboard.write(array2.charAt(i));
counter[i]--;
}
else{
counter[i] = 0;}
}
}
//
Circuit Diagram attached: