Starting to use interrupts, seem to be stuck

Hi all,
I have a circuit using the PCF8574 I2C port expander, a breadboarded Arduino and 4 switches.
I started by just reading the states of the switches via the I2C bus, which worked very well.

Now I want to use a state change on any of the switches to trip an ISR.
I confirmed with my DMM that when I press a button, that the INT output (active LOW) on the PCF8574 drops to 0V (although its normal voltage when no buttons have been pressed is about 0.9V, not sure if its supposed to be that voltage?)

I followed the attachInterrupt() guide http://arduino.cc/en/Reference/attachInterrupt

The INT output of the PCF8574 is connected to the INT0 (pin 4 - digital2) of the ATmega328P.

I wrote some code that first read the switches via the I2C bus and turned on a LED when a switch is pressed, which worked without any problems.
I then put that code into the readButtons() method and used the Interrupt to call that method, but the LED never lights up.

Here is my code (I put the LCD hello world code in the loop, so it could be interrupted, wasn’t sure if loop()n could be empty)

Thanks in advance :slight_smile:

#include <Wire.h>
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(4, 3, 5, 6, 7, 8);

volatile byte x = 0;
int ledPin = 9;    // LED on pin 9

void setup()  
{
  Serial.begin(9600);
  Wire.begin();
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("hello, world!");
  attachInterrupt(0, readButtons, CHANGE);

}

void loop()
{
   lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis()/1000);
}

void readButtons()
{
  Wire.requestFrom(32,1);
  if(Wire.available())       
  {
    x = Wire.read();
    
    if ((x == 1) || (x ==2) || (x==4) || (x==8))
    {
      digitalWrite(ledPin, HIGH);

    }
    else 
    {
      digitalWrite(ledPin, LOW);
    }  
  }

}

Hi,

rosscopicotrain: I have a circuit using the PCF8574 I2C port expander, a breadboarded Arduino and 4 switches. Now I want to use a state change on any of the switches to trip an ISR.

Buttons + interrupt. Why? Will a robot be pushing the buttons?

void readButtons()
{
  Wire.requestFrom(32,1);
  if(Wire.available())       
  {

Using the Wire library in an interrupt service routine. Seems like a bad idea. This looks helpful... http://forum.arduino.cc/index.php?topic=59007.0

Re the 0.9V value on the INT pin... The INT pin on that chip is open-drain, meaning that you need to provide a pullup or pulldown resistor. From the data sheet:

The PCF8574/74A provides an open-drain output (INT) which can be fed to a corresponding input of the microcontroller (see Figure 10). As soon as a port input is changed, the INT will be active (LOW) and notify the microcontroller

Try a pinMode(2, INPUT_PULLUP); in setup()

As a general rule it’s can be a real problem wiring a manual push button to an interrupt pin due to contact bounce creating multiple interrupts per push or release of the switch. And trying to add decent contact debouncing inside a ISR is cumbersome (at least I haven’t seen a method that looks good to me) and time consuming (of course time consumption for manual switch in not a real issue). Some form of external debouncing should be used for ‘dry contact’ attached to interrupt pins.

Lefty

Thank you for your replies.

I have got it working now, except each time I press a button, I get 2 Serial prints of the button pressed, instead of 1.
Apart from debouncing, what could be causing this?
I thought it might be because the ISR was set to CHANGE (each button press and release would be 2 state changes), so I tried HIGH instead, with the same result.

I have done debouncing in hardware with 1uF capacitor and a Schmitt trigger on each switch, and previously tested it successfully with no bouncing.

Again, thanks in advance! :slight_smile:

Here is my modified code

#include <Wire.h>
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(4, 3, 5, 6, 7, 8);

volatile byte x = 0;
int ledPin = 9;    // LED on pin 9
volatile int flag = false;

void setup()  
{
  Serial.begin(9600);
  pinMode(2, INPUT_PULLUP);
  Wire.begin();
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("hello, world!");
  attachInterrupt(0, testFlag, CHANGE);

}

void loop()
{
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis()/1000);

  if (flag)      // if flag has been set to true (ie interrupt flag has been tripped)
  {
    flag = false;         // reset it again;
    readButtons();        // begin readbuttons method
  }
}

void readButtons()
{
  Wire.requestFrom(32,1);
  if(Wire.available())       
  {
    x = Wire.read();

    switch(x)
    {
      case(1):
      Serial.println("ENT pressed");
      break;

      case(2):
      Serial.println("ESC pressed");
      break;

      case(4):
      Serial.println("R pressed");
      break;

      case(8):
      Serial.println("L pressed");
      break;
    }
  }
}

void testFlag()      // method to test status of interrupt flag
{                  // if interrupt has been tripped
  flag = true;    // set flag to true
}

Does not solve the bouncing, but for using a button with an interrupt you should choose either:

RISING to trigger when the pin goes from low to high, FALLING for when the pin goes from high to low.

Depending on if you are using a pull-up or pull-down with the button. Simplest is to enable the internal pull-up for the pin and then use FALLING to detect when the button is being pushed.

That did the trick! Thank you Lefty!

lar3ry: Re the 0.9V value on the INT pin... The INT pin on that chip is open-drain, meaning that you need to provide a pullup or pulldown resistor. From the data sheet:

The PCF8574/74A provides an open-drain output (INT) which can be fed to a corresponding input of the microcontroller (see Figure 10). As soon as a port input is changed, the INT will be active (LOW) and notify the microcontroller

Try a pinMode(2, INPUT_PULLUP); in setup()

Open-drain/collector outputs must have a pull*up* resistor. A pulldown won't work.

Out of curiosity, can an external pullup resistor be used? Or does it need to be the internal one? Would there be any difference between the two from the Arduino's point of view?

rosscopicotrain: Out of curiosity, can an external pullup resistor be used? Or does it need to be the internal one?

No difference, but internal is free and doesn't take up space.

Would there be any difference between the two from the Arduino's point of view? No difference, but internal is free and doesn't take up space.

Using the Wire library in an interrupt service routine. Seems like a bad idea.

Not just "seems like". "is".

The Wire library relies on interrupts that are disabled during an ISR.

I’ve noticed something really weird happening when using the PCF8574 Interrupt pin with Arduino. When I power on the Arduino, which in turn is supplying the PCF8574 with power, the Interrupt does not trip when I am pressing any buttons that are wired to the parallel inputs of the PCF8574.

If I disconnect and then reconnect the wire between the INT pin of the PCF8574 and INT0 pin of the Arduino, then the interrupt is tripped when I press buttons. Once this has been done I can reset the Arduino and it all still works, but if I power cycle it, then it does not work.

I have tried replacing the wire between the PCF8574 and Arduino Interrupt pin, tried replacing the internal pullup with an external pullup, tried using a Walwart as the Arduino power supply instead of USB power from my computer.

I have also tried replacing the PCF8574 and the ATmega328 with the same results. I have metered out all of the switches, everything is working as expected, except for the interrupt pin on the PCF8574.

Just in case its something to do with my code, here it is. Thanks in advance!

#include <Wire.h>
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(4, 3, 5, 6, 7, 8);

volatile byte x = 0;
int ledPin = 9;    // LED on pin 9
volatile int flag = false;

void setup()  
{
  Serial.begin(9600);
  //pinMode(2, INPUT_PULLUP);  // disabled while using external pullup
  Wire.begin();
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Rancilio Silvia");
  attachInterrupt(0, testFlag, FALLING);

}

void loop()
{
  
  if (flag)      // if flag has been set to true (ie interrupt flag has been tripped)
  {
    flag = false;         // reset it again;
    readButtons();        // begin readbuttons method
  }
}

void readButtons()
{
  Wire.requestFrom(32,1);
  if(Wire.available())       
  {
    x = Wire.read();

    switch(x)
    {
      case(1):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("ENT pressed");
      Serial.println("ENT pressed");
      break;

      case(2):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("ESC pressed");
      Serial.println("ESC pressed");
      break;

      case(4):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("R pressed");
      Serial.println("R pressed");
      break;

      case(8):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("L pressed");
      Serial.println("L pressed");
      break;

      case(16):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW1 pressed");
      Serial.println("SW1 thrown");
      break;

      case(32):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW2 pressed");
      Serial.println("SW2 thrown");
      break;   

      case(64):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW3 pressed");
      Serial.println("SW3 thrown");
      break;

      case(128):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW4 pressed");
      Serial.println("SW4 thrown");
      break;  

    }
  }
}

void testFlag()      // method to test status of interrupt flag
{                  // if interrupt has been tripped
  flag = true;    // set flag to true
}

I did some more testing this morning, I removed the wire between the INT pin of the PCF8574 and the Arduino INT0.
I found that the PCF8574 INT pin is going LOW (as expected) when one of the buttons on the parallel inputs is pressed.
However when the wire link is replaced and the Arduino is power cycled, the Arduino fails to detect the interrupt (unless the wire is removed and replaced while the Arduino is running).
I even tried replacing the ATmega328p with a brand new one after burning a Duemilanove boot loader onto it.

This has me completely stumped, any help would be greatly appreciated.

It might be enlightening to move the INT wire to pin 3, and use INT1. This will tell you if pin2 is not working.

I moved to INT1 and had the same result, works when I first upload the code, does not work when I power cycle the Arduino.

I double checked that I had changed the attachInterrupt for INT0, pin 3

Here is my current code

#include <Wire.h>
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(10, 9, 5, 6, 7, 8);

volatile byte x = 0;
int ledPin = 9;    // LED on pin 9
volatile int flag = false;

void setup()  
{
  Serial.begin(9600);
  pinMode(3, INPUT_PULLUP);  // disable when using external pullup
  Wire.begin();
  lcd.begin(16, 2);
  lcd.print("Rancilio Silvia");
  attachInterrupt(1, testFlag, FALLING);

}

void loop()
{

  if (flag)      // if flag has been set to true (ie interrupt flag has been tripped)
  {
    flag = false;         // reset it again;
    readButtons();        // begin readbuttons method
  }
}

void readButtons()
{
  Wire.requestFrom(32,1);
  if(Wire.available())       
  {
    x = Wire.read();

    switch(x)
    {
      case(1):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("ENT pressed");
      Serial.println("ENT pressed");
      break;

      case(2):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("ESC pressed");
      Serial.println("ESC pressed");
      break;

      case(4):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("R pressed");
      Serial.println("R pressed");
      break;

      case(8):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("L pressed");
      Serial.println("L pressed");
      break;

      case(16):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW1 pressed");
      Serial.println("SW1 thrown");
      break;

      case(32):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW2 pressed");
      Serial.println("SW2 thrown");
      break;   

      case(64):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW3 pressed");
      Serial.println("SW3 thrown");
      break;

      case(128):
      lcd.setCursor(0,1);
      lcd.print("                ");
      lcd.setCursor(0,1);
      lcd.print("SW4 pressed");
      Serial.println("SW4 thrown");
      break;  

    }
  }
}

void testFlag()      // method to test status of interrupt flag
{                  // if interrupt has been tripped
  flag = true;    // set flag to true
}

EDIT - I just tried checking the voltage on the INT pin of the PCF8574 when I power cycle the Arduino (which is supplying the PCF8574 with power), while there is a wired connection between the INT pin of PCF8574 and INT1 pin of Arduino.

On power up, the INT pin is held LOW, suggesting one of the parallel pins is HIGH (I even tried tying all parallel pins to GND, with the same result).
If I disconnect and immediately reconnect the INT and INT1 pins, then it gets pulled HIGH and begins working as expected.

EDIT 2 - If I power up the Arduino first by itself, then power up the PCF8574 immediately afterwards, that seems cure the problem.
Still, pretty weird!

Hmm… sounds like t an artifact of the powering up.

The data sheet says:

Power-on reset
When power is applied to VDD, an internal Power-On Reset (POR) holds the PCF8574/74A in a reset condition until VDD
has reached VPOR. At that point, the reset condition is released and the PCF8574/74A registers and I2C-bus/SMBus state machine will initialize to their default states of all I/Os to inputs with weak current source to VDD.
Thereafter VDD must be lowered below VPOR and back up to the operation voltage for power-on reset cycle.

You might solve your problem by following this. The idea would be to delay the application of power to the chip until the Arduino is actually running. If you go this route, you should use either a relay or transistor to manipulate the power to the chip, as the power requirements for the chip will exceed the pin current available from the Arduino.

On the other hand, it might be worth a try to actually read the chip on power up. Place a single read in setup(), and ignore the result. You may have to delay a while for this to work, but only testing will tell.

Thank you Lar3ry, I missed that when reading the datasheet. I was focused on the section about the Interrupt.

You might solve your problem by following this. The idea would be to delay the application of power to the chip until the Arduino is actually running. If you go this route, you should use either a relay or transistor to manipulate the power to the chip, as the power requirements for the chip will exceed the pin current available from the Arduino.

I tried using a NPN transistor as an emitter follower, which gives me about 4.3V at the Vdd pin of the PCF8574. However after 1 or 2 button presses the INT gets stuck LOW.

On the other hand, it might be worth a try to actually read the chip on power up. Place a single read in setup(), and ignore the result. You may have to delay a while for this to work, but only testing will tell.

By this do you mean to call readButtons() in setup? I tried that & it allowed about 8-10 button presses before freezing. Currently I have a 3 second delay in setup() before switching pin 12 HIGH, which is driving a 2N3904 as common collector (emitter follower).

As always, thank you in advance!

rosscopicotrain:
I tried using a NPN transistor as an emitter follower, which gives me about 4.3V at the Vdd pin of the PCF8574. However after 1 or 2 button presses the INT gets stuck LOW.

I wonder if 4.3V is enough. I would have thought it would work. I do think something else is at work here. The datasheet says the interrupt does positive again either a read() happens or an input changes state. Do you have pullups (or puldowns) on the switches themselves? I’m running out of ideas.