Simultaneous changes on multiple pins in pin change interrupt

I am using pin change interrupts on an Arduino Uno. The mask allows for several pins to trigger the interrupt. I understand that I can check the state of the respective port against a saved state and figure out which pins caused the interrupt.
My question is: If two (or more) pins change simultaneously, will the ISR be called multiple times (if so in what order) or just once? In other words, in my ISR, do I need to check for all possible pin changes or is it guaranteed to only have one pin that has changed?
Thanks!

Right.

If each pin would trigger its own interrupt then you had to check all pins for changes with each interrupt. Multiple work for no gain :frowning:

Why do you think that you need so many interrupt pins?

Hello physguy

Welcome to the worldbest Arduino forum ever.

Post your sketch, well formated, with well-tempered comments and in so called
code tags "< code >" and schematic to see how we can help.

Have a nice day and enjoy coding in C++.

Thanks for the welcome and the info! I want to toggle the positions of 4 servos between two positions. The positions should track 4 digital input pins (low->pos1, high->pos2, one pin per servo). Here's a skimmed down version of the core functionality:

#include <avr/interrupt.h>

#define PCINT_MASK  0b11110000 // PCINT20-23 (pins 4-7)

static volatile uint8_t _pinStateNew = 0;
static uint8_t _pinStateOld = 0;

void setup()
{
  PCICR |= 0b00000100;  // Enables port D Pin Change Interrupts (PCINT16 - PCINT23)
  PCMSK2 |= PCINT_MASK; // only look at the relevant pins
  _pinStateNew = _pinStateOld = PIND & PCINT_MASK; // initialize
  Serial.begin(9600);
}

void loop()
{
  // a buch of other stuff (serial, I2C, and SPI communication, etc.)
  
  checkInputPins();
}

ISR(PCINT2_vect){
  _pinStateNew = PIND;
  _pinStateNew &= PCINT_MASK; // only record the desired pins  
}

void checkInputPins(void)
{
  uint8_t changedPins; // changed bits

  changedPins = _pinStateNew ^ _pinStateOld; // mark changed bits
  if (changedPins) {
    switch (changedPins){ // this assumes one pin change at a time
      case (1 << PIND4):
        if (_pinStateNew & 0b00010000) {
          Serial.println("PIND4 changed to high");
        } else {
          Serial.println("PIND4 changed to low");
        }
        break;
      case (1 << PIND5):
        if (_pinStateNew & 0b00100000) {
          Serial.println("PIND5 changed to high");
        } else {
          Serial.println("PIND5 changed to low");
        }
        break;
      // similar case staements for the other pins ...
    }
    _pinStateOld = _pinStateNew; 
  }
}

This code has several issues:

  1. The digital inputs could occur at any time (several pins could change at the same time or in short succession). The often-used switch statements only deals with one pin change. Looks like I'll have to switch to a bunch of if statements instead to allow for multiple changes.

  2. Occasionally I see two falling or rising transitions on the same pin in series. Likely this is due to a bouncing switch. I can't do a hardware de-bounce, so eventually I have to include a software de-bounce on each pin. In the meantime I found the Ganssle page on de-bouncing with code for de-bouncing an entire port - I'll try that soon. That one uses timers instead of pin change interrupts. Any advice?

Debounce and interrupts don't fit together. Also check the StateChange example.

Remember that within your ISR you can make changes to the interrupts enabled. So if you find that one bit has changed, you can change the mask register to disable further interrupts from that pin, and set some flag variable in your code to indicate that you've done that. Then in the main loop after passage of a debounce period, you could re-enable interrupts on that bit.

But debouncing is an imperfect art. If an interrupt is triggered because one pin changes, the processor then has to push the program counter onto the stack, then enter the ISR. Then the Arduino IDE pushes a bunch of registers to the stack that you don't know about, and only then lets your ISR code read the port to see which bit changed. During that time, the bit may still be bouncing, so reading the port may be like a box of chocolates.

Classes are your friend here

Create a class for your servos. An array to store them. Assign each instance its own input and outputpin pin.

Itterate through each instance in the array calling its update method. This method reads an input pin, and outputs on the output pin.

No switch statement required and no repetitive code. Adding another servo just means creating another instance and assigning it pins.

99% of the time you dont need interrupts. Your loop will run probably in the thousands of times per second

I'm going to throw out a scenario here, even though it's low probability it's the kind of thing we have to code around.

When a pin on the port changes it sets the interrupt flag and fires the interrupt. On entry the flag is cleared. If another pin changes while the interrupt is being serviced, then the flag will be set again and after the interrupt exits there will be one cycle of code and then the interrupt will fire again for the other pin.

Where this can be an issue is if the second pin changes before you read the port in the interrupt. There is some overhead time entering the interrupt routine and if the second pin changes during that time then you will read the port and see two pins changed. You react to two pins changed. And then you get a second interrupt for the second pin that you already dealt with.

I don't know what happens if the two pins change at exactly the same moment. My assumption since there is only one interrupt flag is that you only see one interrupt.

You see at least one interrupt for multiple pins changed at about the same time. Multiple changes of the same pin will cause multiple interrupts, just as I would expect.

A little capacitor solves the bouncing problem.

Your code could accept as a valid result an interrupt that has no pins changed. That would just be an indication that what you describe has taken place, and you've already dealt with the second pin change. Another possibility is to clear the flag in software immediately after reading the port. But I'm not sure that works. I don't remember whether the flag can be set inside the ISR while interrupts are disabled.

However, if you have multiple pins generating changes, all of which may be bouncing, that's going to be a challenge to handle with pin change interrupts unless you disable further interrupts for a period of time on the pin that just interrupted.

But remember that on the Uno you have three ports, each of which could have its own single interrupting pin. Plus you could use the hardware interrupts on D2 and D3. You'd have lots of ISR code, but you would have five lines, each generating its own interrupt. So you wouldn't have to read the port. Still have to deal with bouncing though.

Hello physguy

What is the reason for a hardware interrupt as opposed to a state-change detection software solution?

Thanks for all the info! I think I understand the pin change interrupt concept a lot better now.
I was originally going for an interrupt to get a as quick a response as possible. My main loop is checking a lot of other stuff (touch screen input, serial comm), so I figured an interrupt would respond quicker than constant checking of the pins. However, as @DrDiettrich pointed out, debounce and interrupts are somewhat at odds - or at least debounce and fast response (to wait until a bouncing switch has settled and rejecting noise spikes by definition requires not reacting at the first state change).
Maybe here's a hybrid scheme to avoid constantly checking the pins: Set up an pin change interrupt on the port, where the ISR then disables the interrupt and starts a timer that checks for pin states and debounces them. After all the pins have settled, stop the timer and enable the interrupt again. This way I only check after something has changed. Maybe the constant checking is not a big deal and the hybrid approach is overkill?

1 Like

What's connected to the inputs? The nature of the signal is important. If the signal is a human pressing a button or something then it is surely overkill. You wouldn't be able to tell if the servo started getting the signal 50ms earlier. I think people tend to underestimate just how fast code runs.

If the signal is an encoder click from a fast spinning motor that only lasts for a few hundred microseconds then an interrupt may be entirely appropriate.

Great point! Unfortunately it could be as clean as a TTL pulse from a function generator (that one won't bounce) or a mechanical switch. However, in any case the transition will be a step, not a short pulse. Considering the servos will ultimately be the time-limiting elements anyway, I think I'll settle for a timer-based debouncing code.

Best thing you can do is profile your code. At the start of your loop
Double time = millis();

At the end of your loop
Serial.println(millis() - time);

Then on a calculator, 1000 / printed answer.

Thats how many times a second your loop is running.

I think you can have fast response. You can react to the first state change immediately. Just don't permit detection of any further state changes until the debounce time has expired, or other debounce algorithm has completed.

Hardware debouncing:

"Garbage in, garbage out...": why processing a noisy signal if you can feed a clean one?

Agreed, hardware debouncing is definitely an option (I wanted to see how far I get with software).

Excellent point! I guess it gets a bit tricky if I have to keep track of several pins (I don't want to miss a pin change on a pin just because a prior change on another disabled my interrupt). I guess I could mask off pins that had a change and keep the interrupt running for the other pins. A bit involved to keep track of that.

Thanks to the discussions here I can think of a few options, but here's the approach I am using right now (seems to work so far). I have a pin change interrupt that looks at all pins. As soon as it fires it disables the PCI and starts a timer that monitors the pins and waits until all of them have settled. It then turns itself off and starts the PCI again. This approach is completely idle most of the time, unless something changes. Not the fastest response time (waits for all pins to stop bouncing), but fast enough. I am including the code below, maybe someone will find it useful (or provide improvements!)
The timer approach is essentially the approach from Ganssle.

#include <avr/interrupt.h>

#define CHECK_INTERVAL_MS 5   // interval in ms for the bounce check
#define MAX_CHECKS        10  // number of intervals in a bounce check cycle

#define PCINT_MASK        (bit(PCINT20)|bit(PCINT21)|bit(PCINT22)|bit(PCINT23)) // mask for the PC interrupt (pins 4-7)
#define PIN_SETTLED       0   // bit in the status flag to indicate the end of bouncing

static volatile uint8_t _status = 0; // status flag (here only bounce finished bit) 
static volatile uint8_t _lowState, _highState; // debounced state

static uint8_t _state[MAX_CHECKS];
static uint8_t _index;

void setup()
{
  noInterrupts();
  // set up pin change interrupt
  PCICR |= bit(PCIE2);  // enable PCI on port 2 (port D, PCINT16 - PCINT23)
  PCMSK2 |= PCINT_MASK; // marks the pins to monitor
  // set up the timer1 interrupt
  TCCR1A = TCCR1B = 0;  // clear register
  TCNT1 = 0; // initialize counter value to 0
  TCCR1B |= bit(CS12) | bit(CS10); // set CS12 and CS10 bits for 1024 prescaler 
  OCR1A = (uint16_t)(CHECK_INTERVAL_MS*15.625 - 1); // set compare match register (must be <65536)
  TCCR1B |= bit(WGM12); // turn on CTC mode
  interrupts();
  
  Serial.begin(9600);
}

void loop()
{
  uint8_t flag;
  uint8_t hs;

  // copy and clear status flag
  noInterrupts();
  flag = _status;
  _status = 0;
  hs = _highState;
  interrupts();

  if (flag & bit(PIN_SETTLED)) {
    Serial.print("state = "); printBin(hs); Serial.println("");
  }
}

// pin change interrupt gets called when any of the monitored pins change
ISR(PCINT2_vect){
  // stop PC interrupt
  PCICR &= ~bit(PCIE2);  // disable PCI on port 2
  // start timer
  TCNT1 = 0; // reset the timer to zero
  TIMSK1 |= bit(OCIE1A); // enable timer compare interrupt

  // this ensures that the debounce check runs one full cycle
  _state[_index] = ~(PIND & PCINT_MASK);
  _index++;
  if(_index>=MAX_CHECKS) _index=0;

}

// timer interrupt gets call when count is met
ISR(TIMER1_COMPA_vect)
{
  uint8_t z;
  uint8_t low, high;

  _state[_index] = PIND & PCINT_MASK; // read the port
  _index++;
  // check if the pins have settled (all the entries match, all pins high or all low)
  high = low = 0xFF;
  for (z=0; z<MAX_CHECKS;z++) {
    high = high & _state[z];  // check for all bits high
    low = low & ~_state[z];  // check for all bits low
  }
  _highState = high;
  _lowState = low;
  if(_index>=MAX_CHECKS) _index=0;

  if ( (high | low) == 0xFF ) { // all pins have settled either high or low
    _status |= bit(PIN_SETTLED); // set the flag that the pins have finished bouncing
    // stop timer
    TIMSK1 &= ~bit(OCIE1A); // disable timer compare interrupt
    // start PC interrupt
    PCIFR = bit(PCIF2);  // clear the interrupt flag
    PCICR |= bit(PCIE2);  // enable PCI on port 2
  }
}

// Thanks J-M-L!
// forum.arduino.cc/t/how-can-i-serial-println-a-binary-output-that-will-give-all-the-leading-zeros-of-a-variable/962247/2
void printBin(byte aByte) {
  for (int8_t aBit = 7; aBit >= 0; aBit--)
    Serial.write(bitRead(aByte, aBit) ? '1' : '0');
}