[SOLVED] Dealing with a noisy interrupt signal

The "debouncer" code now works automatically for all interrupt modes and external interrupt INT0 and INT1. The maximum input frequency for all modes is around 38 kHz when timer0 interrupt is disabled. With timer0 interrupt enabled, the maximum frequency is about 32kHz. Latency for all modes is about 3.6 µs.

Tested with an SPI pattern generator uploaded to an Arduino Pro Mini. The signal is squarewave with multiple transitions on each edge and other areas (34 total / cycle). The signal has frequency of 38.3 kHz and is connected to an UNO external interrupt pin (2 or 3).

For CHANGE mode, the clean output toggles.

For RISING and FALLING modes, the clean output toggles once for the required mode. Noise on both edges and in between is eliminated even though the opposite edge extends well beyond the stableWidth interval.

Noise on the opposite edge is a common problem that is difficult to eliminate. For a squarewave, a simple ignore interval will not work unless it extends beyond 50% of the waveform which severely limits the usable frequency range when debouncing. By knowing the previous stable state, this issue has been resolved.

Noise in between the signals is also ignored providing there is at least one high and one low stable interval.

FALLING mode:

]

RISING mode:

The code:

const byte intPin = 2; // can use pin 2 or 3
volatile byte intNum, notIntNum, intSense, intState, clockSelect;
volatile byte extendedCompare, extendedCounter;

unsigned long stableWidth = 125000; // 2-1000000µs interval

void setup()
{
  pinMode(intPin, INPUT_PULLUP);
  pinMode (LED_BUILTIN, OUTPUT);
  //pinMode (11, OUTPUT); // for monitoring OC2A output
  //TIMSK0 = 0;           // for testing with timer0 disabled
  attachInterrupt(digitalPinToInterrupt(intPin), inputPin_ISR, CHANGE);
  stabilizerInit();
}

void loop()
{
}

ISR(TIMER2_COMPA_vect)
{
  extendedCounter++;
  if (extendedCounter >= extendedCompare)
  {
    extendedCounter = 0;                    // reset extended counter
    TCCR2B = 0;                             // stop timer clock
    TCNT2 = 0;                              // reset timer counter
    intState = (PIND & _BV (intPin)) == 0;  // read intPin
    EIFR = intNum;                          // reset pending intPin interrupt
    EIMSK |= intNum;                        // enable intPin interrupt
  }
}

void inputPin_ISR()
{
  if (intSense == 1) {                      // if CHANGE mode
    // your code here
    PINB |= _BV (5);                        // toggle pin 13

  } else if (intSense == 2 && intState) {   // if FALLING mode and previously stable high)
    // your code here
    PINB |= _BV (5);                        // toggle pin 13

  } else if (intSense == 3 && !intState) {  // if RISING mode and previously stable low)
    // your code here
    PINB |= _BV (5);                        // toggle pin 13
  }
  TCCR2B = clockSelect;                     // start timer clock with required prescaler
  TCNT2 = 0;                                // reset timer counter
  EIMSK &= notIntNum;                       // disable intPin interrupt
  EIFR = intNum;                            // reset pending intPin interrupt
}

void stabilizerInit() {
  const float clockResolution = 1000000.0 / F_CPU;
  unsigned long timerCycles, prescaledCycles;
  word prescaler;

  intNum = digitalPinToInterrupt(intPin) + 1; // get interrupt mask
  notIntNum = ~intNum;
  intSense = (EICRA >> (intNum - 1) * 2) & 3; // get interrupt mode
  stableWidth = constrain(stableWidth, 2, 1000000); // 2-1000000µs
  timerCycles = (stableWidth / clockResolution) - 1;
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0; // reset counter
  extendedCounter = 0; // reset extended counter
  TCCR2A |= (1 << WGM21) | (1 << COM2A0); // CTC mode | toggle OC2A on compare match
  if (timerCycles < 2048) { // 1-128µs, 0.5µs resolution
    TCCR2B |= (0 << CS22) | (1 << CS21) | (0 << CS20);
    clockSelect = 2;
    prescaler = 8;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 16384) { // 129-1024µs, 4µs resolution
    TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);
    clockSelect = 4;
    prescaler = 64;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 65536) { // 1025-4096µs, 16µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (0 << CS20);
    clockSelect = 6;
    prescaler = 256;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 262144) { // 4097-16384µs, 64µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    clockSelect = 7;
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 1048576) {  // 16385-65536µs, 256µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    clockSelect = 7;
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 3;
    extendedCompare = prescaledCycles >> 2;
  } else if (timerCycles < 4194304) { // 65537-262144µs, 1024µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    clockSelect = 7;
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 15;
    extendedCompare = prescaledCycles >> 4;
  } else { // 262145-1000000µs, 4096µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    clockSelect = 7;
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 63;
    extendedCompare = prescaledCycles >> 6;
  }
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
}