8-Bit I/O Expander I2C hanging

I am building an AM/FM receiver based upon the SI4730 family and I am experiencing issues with my control interface. After some random amount of hitting attached buttons, it simply hangs up and will no longer respond. Hitting the reset on the MC clears the issue ... until it happens again. I hit the buttons in random order and random amount of times.
I am using two MCP23008 with interrupts tied and feeding the D2 pin on a 5Volt mini. Momentary switches to ground are the inputs. 10K resistors in the I2C lines. I am also using a 20X4 LCD display for troubleshooting as I will be using that in the final project. No issues with the display.
I have considered isolating the expanders for individual testing but wanted to see what other ideas might be out there.
Here is the code for the interface functionality stripped of other stuff. This version is not written for efficiency. Also, for anyone testing it, feel free to strip out the LCD stuff for the serial line interface.

#include <pu2clr_mcp23008.h>
#include <LiquidCrystal_I2C.h>

#define ARDUINO_INTERRUPT_PIN 2
#define IO_RESET_PIN 3

#define DISPLAY_ADDRESS 0x27
#define DISPLAY_COLUMNS 20
#define DISPLAY_ROWS 4

#define MCP0_ADDRESS 0X20
#define MCP1_ADDRESS 0X21

MCP mcp0, mcp1;
uint8_t intcap0, intcap1;
int button;
bool isBank0 = true;
volatile bool button_event = LOW;
LiquidCrystal_I2C lcd(DISPLAY_ADDRESS, DISPLAY_COLUMNS, DISPLAY_ROWS);

void setMCP()
{
  button_event = true;
}

void initializeButtons(){
  // Ensure that the I/O chips are reset upon powerup/reset
  digitalWrite(IO_RESET_PIN, HIGH);
  delay(10);
  digitalWrite(IO_RESET_PIN, LOW);
  delay(10);
  digitalWrite(IO_RESET_PIN, HIGH);
  delay(10);

  mcp0.setup(MCP0_ADDRESS, 0B01111111);
  mcp1.setup(MCP1_ADDRESS, 0B01111111);
  mcp0.setInterrupt(INTERRUPT_INTPOL_ACTIVE_HIGH, INTERRUPT_ODR_OPEN_DRAIN); // Defines the behaviour of the interrupt
  mcp1.setInterrupt(INTERRUPT_INTPOL_ACTIVE_HIGH, INTERRUPT_ODR_OPEN_DRAIN);

  for(int i = 0; i < 8; i++){
    mcp0.pullUpGpioOn(i);          // Enables internal pullup resistor on gpio pin 1
    mcp0.interruptGpioOn(i, HIGH); // Sets the GPIO pin 1 to deal with interrupt. The pin 1 will be compared with the value 1 (HIGH). It will be launch an interrupt if the pin 1 goes to level 0 (LOW).
  }
  for(int i = 0; i < 8; i++){
    mcp1.pullUpGpioOn(i);
    mcp1.interruptGpioOn(i, HIGH);
  }

  pinMode(ARDUINO_INTERRUPT_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ARDUINO_INTERRUPT_PIN), setMCP, CHANGE);
}

// Button functions
bool checkInput(int mcp0, int mcp1){
  if(mcp0 == 127 && mcp1 == 127)  // Initial value - not wanted
    return false;
  if(mcp0 == 255 && mcp1 == 255)  // After register reset - not wanted
    return false;
  return true;
}

int getButton(int intcap){
  for(int i = 0; i < 8; i++){
      if(!bitRead(intcap, i)){
        return i;
      }
    }
    return -1;
}

void checkButtons(){
  // Get I/O registers.  Also resets registers and interrupt pin.
  intcap0 = mcp0.getRegister(REG_INTCAP);
  intcap1 = mcp1.getRegister(REG_INTCAP);

  if(checkInput(intcap0, intcap1)){
    if(intcap0 != 255){
      isBank0 = true;
      button = getButton(intcap0);
    }else{
      isBank0 = false;
      button = getButton(intcap1);
    }
    
    lcd.clear();
    lcd.setCursor(0,0);
  
    if(isBank0){
      switch(button){
        case 0:
          lcd.print("volumeDown");
          break;

        case 1:
          lcd.print("volumeUp");
          break;

        case 2:
          lcd.print("increaseSweepRate");
          break;

        case 3:
          lcd.print("changeSweepDirection");
          break;

        case 4:
          lcd.print("OPEN");
          break;

        case 5:
          lcd.print("saveSettings");
          break;
        
        case -1:
          lcd.print("ERROR!");
          break;
      }
    }else{
      switch(button){
        case 0:
          lcd.print("decreaseSweepRate");
          break;

        case 1:
          lcd.print("bandChange");
          break;

        case 2:
          lcd.print("indexBacklight");
          break;

        case 3:
          lcd.print("setScanPause");
          break;

        case -1:
          lcd.print("ERROR!");
          break;
      }
    }
 }
  button_event = false;
}

void setup() {
  pinMode(IO_RESET_PIN, OUTPUT);
  /* Initialize display */
  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("INITIALIZING...");
  
  initializeButtons();
}

void loop() {
  if (button_event){
      checkButtons();
  }
}

Again, any assistance would be greatly appreciated.
Thanks.

PS: I can also provide a (somewhat messy) image of the schematic upon request.

I suspect a schematic will be needed

2 Likes

I seem to have solved the immediate issue, but not the underlying problem.

I tried a different I2C library with a timeout feature for bus lockup with no change, so I decided to forego using hardware interrupt from the MCP23008 array and do simple polling of the input pin on the M/C with great success. The button input timing is not that critical to the operation. I'm simple reading the state of the input pin to determine if the interrupt signal from the 23008s has been triggered. Since it's a simple logic check and the I/O chip seems to mitigate the need for button debounce, there is little overhead.

  pinMode(ARDUINO_INTERRUPT_PIN, INPUT_PULLUP);
  // attachInterrupt(digitalPinToInterrupt(ARDUINO_INTERRUPT_PIN), setMCP, CHANGE);

void loop() {
  // if (button_event){
  //     checkButtons();
  // }
  if(!digitalRead(ARDUINO_INTERRUPT_PIN)){
    checkButtons();
  }
}

At this point, for this project, the bus lockup is rather academic. However, I am still curious as to why it would randomly lock up in conjunction with hardware interrupt enabled.

Any useful comments are more than welcome to include those on tightening/tidying my code.

Thanks

Where are your pull up resistors on the I2C bus. With these missing you can experience what you are describing.

10K right off of the M/C.
Here is a more complete schematic:

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