How to connect multiple pcf8574s

Hi everyone,
following this video
https://www.youtube.com/results?search_query=pcf8574

I started building a project which uses 3 pcf8574 to "read" 24 push buttons as inputs.
The basic idea is to be able to identify which button has been pressed in order to trigger a different action (which is out of the scope of the question).

Since pcf8574 is able to use interrupts I decided to implement my project using interrupts.
So, I have 3 digital pins on my Arduino 2009 which are meant to read the interrupts of the 3 different pcf8574s.

Now, all seems to work fine for the first pcf8574 (the one connected to digital PIN 3), however the other 2 seems to never trigger the interrupt when a button attached to them is pressed.

If I connect any of the other boards to PIN 3, then it works... so I was wondering if I'm making any mistakes in how the pcf8574 interrupt pins are connected to the arduino board.

First my code:

#include "Arduino.h"
#include "Wire.h"

#define PCF_1 0x26
#define PCF_2 0x23
#define PCF_3 0x24

#define INTERRUPT_PCF_1 3
#define INTERRUPT_PCF_2 4
#define INTERRUPT_PCF_3 5

unsigned long prevMillis = millis();
int ISR_changed_address = 0;

void setup() {
 Serial.begin(9600);

 // I2C Two Wire initialization
 Wire.begin();

 initPcf(PCF_1, INTERRUPT_PCF_1, ISRoutine_pcf1);
 initPcf(PCF_2, INTERRUPT_PCF_2, ISRoutine_pcf2);
 initPcf(PCF_3, INTERRUPT_PCF_3, ISRoutine_pcf3);

 Serial.println("Setup complete.");
}

// the loop function runs over and over again forever
void loop() {
 if (ISR_changed_address != 0) {
 byte abc = 0;
 Wire.requestFrom(ISR_changed_address, 1);
 if (Wire.available()) {
 abc = Wire.read();
 }
 Serial.print("Byte read from ");
 Serial.println(ISR_changed_address);
 Serial.println(abc);
 Serial.println("------------------");

 ISR_changed_address = 0;
 }
 delay(250);
}

void initPcf(uint8_t address, int interruptPin, void (*userFunc)(void)) {
 Wire.beginTransmission(address);
 Wire.write(0xFF);
 Wire.endTransmission();
 pinMode(interruptPin, INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(interruptPin), userFunc, CHANGE);
}

void ISRoutine_pcf1() {
 Serial.println("ISR called");
 ISRoutine(PCF_1);
}

void ISRoutine_pcf2() {
 Serial.println("ISR called");
 ISRoutine(PCF_2);
}

void ISRoutine_pcf3() {
 Serial.println("ISR called");
 ISRoutine(PCF_3);
}

void ISRoutine(int address) {

 // Only call this max once every 1/4 second
 if (millis() > prevMillis + 250) {
 Serial.flush();
 ISR_changed_address = address;

 // Update the time so that we don't re-trigger within 1/4 second
 prevMillis = millis();
 }
}

and secondly a sketched schematics just to give you an idea about how I connected the interrupts to the arduino

Hello there!

According to the information page on the Duemilanove, the only two pins that can accept an interrupt are digital pins 2 and 3. You have one pcf module connected to pin 3, and that is the one that is working as intended. The other two do not, as those pins cannot handle interrupts.

Hi, and thanks for the reply.
That makes sense, I should have checked it. Is there any way to have more than 2 interrupt pins on Arduino 2009?

Edit:
I found this library: GitHub - GreyGnome/EnableInterrupt: New Arduino interrupt library, designed for Arduino Uno/Mega 2
I'll give it a go tonight.

Thanks,
Gab

Or just poll it. No need for interrupts for buttons, they are slow anyway.

And if you really want an interrupt you could do with 1 after which you just real all three modules. Simply connect all interrupts lines in parallel.

But do note you're stepping in a LOT of pitfalls which come with interrupts :wink:

Hi septillion,
thanks for your reply.
When you say "in parallel", you mean something like this?

If that's the case, I'm don't get how you would differenciate between an interrupt coming from pcf8574 (1) or pcf8574 (2).

I'm sure I'm missing something here...

Regarding using interrupts, I'd like to give it a go, it's the first time I use them and I'd like to experiment a bit. Anything in particular I should be aware of?

Thanks,
Gab

That's indeed what septillion meant. You can't differentiate; once an interrupt happened, you know that one of the three PCF8574 caused an interrupt and you can read all three PCF8574s to get the state of all buttons.

An alternative is the use of pinchange interrupts instead of interrupts; that solution can be used with your original hardware design. In that case however you first need to determine the pin that caused the interrupt by reading the pins and next read the corresponding PCF8574.

sterretje:
That's indeed what septillion meant. You can't differentiate; once an interrupt happened, you know that one of the three PCF8574 caused an interrupt and you can read all three PCF8574s to get the state of all buttons.

Oh, I see! That sounds like a viable solution for me and would save me from using an external library. I will give it a go.

Thanks both for the help!
Gab

You also do not need the capacitor on the interrupt line. It is not going to bounce.

You do not need to use interrupts at all on the Arduino. Just connect to any three input pins and poll them.

With regards to interrupts, they can be tricky to implement, but can be very rewarding if used correctly. The number one thing is that while an ISR (interrupt service routine) is running, the main code is continuing to do what it was doing when the interrupt occurred. So if an RC car was turning right, it would turn in a circle until the ISR had exited. With that said, it is best practice to keep the length of the ISR to an absolute minimum. Below is code for a generic ISR.

void ISRcode()
{
  noInterrupts();
  setAFlagForTheCodeToCheck;
  interrupts();
}

The noInterrupts() commands stops other interrupts from triggering their ISRs while this ISR is running, and the interrupts() commands afterward restores the functionality of the other interrupts. Setting a flag means just setting an integer to 1 or something like that, which the main loop() code can handle at a later time.

For more help on interrupts, this is a good place to start. There are also links to the other commands at the bottom of the page.

Hey bos1714,
thanks for the useful info. I think I got the possible drawbacks of using interrupts. As you said, it might be very rewarding if used correctly so I'll keep using them for this simple project, hopefully it will help me to familiarize with them.

Thanks,
Gab

I don’t think you get it. If you have to pole the flag set by the interrupt routine you might as well just pole the pins you have connected the interrupt pins to. You have generated no advantage, it is not as if the interrupt flags are going to go away, they are not reset until you read the actual I2C device.

All you have done is put an unscheduled gap in your code. Much better to be in control yourself.

bos1714:

void ISRcode()

{
  noInterrupts();
  setAFlagForTheCodeToCheck;
  interrupts();
}

You do not need NoInterrupts() and Interrupts() in the ISR; the hardware disables interrupts when the processor jumps to the ISR and enables interrupts when the code returns from the ISR.

Mike observes:

Grumpy_Mike:
I don’t think you get it. If you have to pole the flag set by the interrupt routine you might as well just poll the pins you have connected the interrupt pins to.

In fact, you - OP - are referring to detecting pushbutton presses.

You do not seem to be “getting it” to start with. It is almost never of any value to use interrupts to service manual inputs or slow mechanical events up to about 100 Hz. Unfortunately, poorly conceived “tutorials” such as exist on this site and “instructables” suggest using interrupts for such inappropriate things because it seems to be a “simple” way to explain how to use interrupts so that they can claim they have “covered the subject”. Poorly.

Even allowing for the overhead of I2C, it is most improbable that your code will not have sufficient “spare time” to poll every millisecond. And to de-bounce button presses generally takes at least a further 5 ms, while contact bounce could cause a “blast” of interrupts that interferes with some other critical process. It is more difficult to de-bounce within an interrupt routine, so much easier to forget interrupts and stick with subroutines that assist with the polling.