[SOLVED] Dealing with a noisy interrupt signal

I don't want to waste CPU cycles on 1000 interrupts if I can reduce it to 200 - the Atmega 328 will be working at 8MHz and it has other stuff to do.

Some options?

  • don't use interrupts
  • somehow turn off the pin2 interrupt only during bounce intervals
  • use different sensor with debounced output
  • modify the existing sensor (see below)

I'm not clear if your diagrams of the bouncing are to scale, and if so, what is the scale. The bouncing seems to take up a huge proportion of the cycle time.

Its to illustrate a few points. The scale of the bouncing doesn't matter very much if it occurs on each end of the pulse. If your pulses are much more sharp with only 10µs bounce at each end and you have 50% duty cycle, you'll still need a debounce interval > 50% of the signal's period (same as for the diagram). What if the duty is 60/40, 70/30 or higher?

I'm not sure how you've designed your optic sensor, but if you made the black/blocking portion or clear portion much greater than the other so you get (for example) 25% duty, then software debounce would be more practical and easier.

Also, if the signal is open-collector or open-drain type, then there should be an external pullup resistor somewhere. Adding a very physically small capacitor from the signal to GND might be all that's needed. In this case, FALLING mode is more appropriate.

It is too early for either of us to speculate about the possible causes of too many interrupts on my project. Your little diagram would require two extra components - I have very limited space so I want to avoid that if I can.

Why not use an analog input and run the Serial Plotter to get a picture of the signal?

dlloyd:
Why not use an analog input and run the Serial Plotter to get a picture of the signal?

It does not exist in IDE 1.5.6 AFAIK

You seem to be doing what I suggested not to do " too early for either of us to speculate about the possible causes ..."

It turned out that the entire problem was caused by a lack of supression capacitors on my motor which was causing electrical rather than optical interference.

...R

EDIT:

Initial test results for pulseConditioner(DEBOUNCE) measured on Saleae LogicPro8 Logic Analyzer.
Arduino Pro Mini uploaded with debounce code, Arduino UNO generates the signal.

With 1.255kHz input frequency, 17.92µs latency, 12,550 interrupts per second.
With 10.629kHz input frequency, 31.36µs latency, 63,776 interrupts per second.

For now, I'm going to continue working with CHANGE mode to improve the logic and latency. What I'd like to accomplish is having an efficient function for this that eliminates all noise. I'll also investigate using a simple method of monitoring CPU load.

I know it seems impractical using a dirty signal like this, but as long as the code can handle the noise correctly, then a simple solution for high CPU loading would be to use an RC filter in combination with the debounce code.

  • The RC filter could be set around 10x the max input frequency to deal with noise while not filtering out low duty cycle signals (i.e. 10% duty).
  • The debounce code could deal with anomalies using logic and timing ... anomalies that are impossible to filter out externally without destroying the signal.An advantage of CHANGE mode is that duty cycle information is preserved and timing slow pulse rates with 50% duty can be done in 1/2 cycle.

... any comments welcomed!

As far as I can tell, this debouncer in software prevents wasted CPU cycles caused by noisy interrupts. It uses timer2 with extended compare to give a debounce interval range of 1µs to 240 seconds. The main loop is empty. Test by touching ground with a jumper wire connected to pin2 and the built-in LED will toggle once per touch.

const byte inputPin = 2;
volatile word extendedCompare, extendedCounter;
unsigned long stableWidth = 125000; // µs (debounce interval)

ISR(TIMER2_COMPA_vect)
{
  extendedCounter++;
  if (extendedCounter >= extendedCompare)
  {
    TIMSK2 &= ~(1 << OCIE2A);  // disable timer compare interrupt
    EIFR = (1 << INTF0);       // clear pending inputPin interrupt
    EIMSK |= (1 << INT0);      // enable inputPin interrupts
    extendedCounter = 0;
  }
}

void inputPin_ISR()
{
  PINB |= _BV (5);          // toggle pin 13
  EIMSK &= ~(1 << INT0);    // disable inputPin interrupt
  TCNT2  = 0;               // reset timer counter
  TIFR2 = (1 << OCF2A);     // clear pending timer interrupt
  TIMSK2 |= (1 << OCIE2A);  // enable timer compare interrupt
}

void timer2Init() {
  stableWidth = constrain(stableWidth, 1, 240000000); // 1µs-4min
  const float clockResolution = 1000000.0 / F_CPU;
  unsigned long timerCycles, prescaledCycles;
  word prescaler;
  timerCycles = (stableWidth / clockResolution) - 1;
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0; // reset counter
  TCCR2A |= (1 << WGM21); // turn on CTC mode
  if (timerCycles < 2048) { // 1-128µs, 1µs resolution
    TCCR2B |= (0 << CS22) | (1 << CS21) | (0 << CS20);
    prescaler = 8;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 16384) { // 129-1024µs, 4µs resolution
    TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);
    prescaler = 64;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 65536) { // 1025-4096µs, 16µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (0 << CS20);
    prescaler = 256;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 262144) { // 4097-16384µs, 64µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 1048576) {  // 16385-65536µs, 256µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    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);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 15;
    extendedCompare = prescaledCycles >> 4;
  } else if (timerCycles < 16777216) { // 262145-1048576µs, 4096µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 63;
    extendedCompare = prescaledCycles >> 6;
  } else { // 1048577-240000000µs, 16384µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 255;
    extendedCompare = prescaledCycles >> 8;
  }
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
}

void setup()
{
  pinMode(inputPin, INPUT_PULLUP);
  DDRB |= _BV (5);  // pinMode (13, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(inputPin), inputPin_ISR, CHANGE);
  timer2Init();
}

void loop()
{
}

Results on UNO with clean input signal:

  • Maximum input frequency: 32µs period, 31.25kHz (the signals drop out at higher frequencies).
  • Interrupts per second: 125,000 (4 interrupts per cycle, 2 timer + 2 input)
  • Latency: 3.1 µs
  • Interrupt mode: CHANGE
  • Loop: empty

Results on UNO with noisy input signal:

  • Tested input frequency: 41.4µs period, 24.33kHz.
  • State changes/sec: 291,960 (12 per cycle, 2 timer + 10 input)
  • Interrupts/sec : 97,320 (4 interrupts per cycle, 2 timer + 2 input)
  • Interrupts eliminated/sec: 194,640
  • Latency: 3.1 µs
  • Interrupt mode: CHANGE
  • Loop: empty

Another test:

  • Tested input frequency: 22.86µs period, 43.74kHz.
  • State changes/sec: 524,880 (12 per cycle, 2 timer + 10 input)
  • Interrupts/sec : 174,960 (4 interrupts per cycle, 2 timer + 2 input)
  • Interrupts eliminated/sec: 349,920
  • Latency: 3.1 µs
  • Signal stable high 5.14µs, signal stable low 4.48µs, stableWidth setting = 3µs
  • Interrupt mode: CHANGE
  • Loop: empty

dlloyd:
As far as I can tell, this debouncer in software prevents wasted CPU cycles

Is that using the concept I suggested in Reply #36 ?

...R

@Robin2: Yes, thanks, your post got me researching the possibility of using a timer to temporarily turn off the input interrupt. Then I stumbled upon this thread that was attempting a similar approach but was incomplete. However, I needed a wider timing range and needed to use the timer as a monostable multivibrator (one-shot timer). More specifically, a retriggerable one-shot timer because the debounce timing re-starts after each invalid interrupt. This is why I called the timing interval "stableWidth" as it waits for a period of stability in the signal.

I have bookmarked your Reply #43 for future reference.

...R

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
}

Analog signal connected to input test: PWM (490.5 Hz) with 50% duty, RC filter = 1K/0.1µF.
Software set to FALLING mode, 400 µs stableWidth.

Note - can use: stableWidth (µs) = 200,000 / 490

Note: the trigger level on the analyzer does not match the input pin trigger level.

Input zoomed in on each edge to reveal transients:

Output pin 13 zoomed:

This is an attempt to see what the top end input frequency could be for debouncing CHANGE mode interrupts.

In the previous code, I focused more on expanding the range of the 8-bit timer, providing automatic pin and mode detection and on providing debouncing for all modes. The maximum input frequency was 25kHz with 3.6µs latency.

To find the top end frequency, I've stripped out all conditional logic and bit shifting. Discovered an improved way to work with the timer - starting and stopping the clock rather than enabling and disabling the timer2 compare match interrupt.

Clean signal for testing highest input frequency and measuring latency:

Input signal with 22 transitions per cycle having "bouncing" on leading and trailing edge of signal:

Input signal with 34 transitions per cycle having "bouncing" on leading and trailing edge and also during high and low states of the signal:

This exceeded my expectations ... 53kHz input frequency with about 3.2µs latency. Incredibly messy signal completely recovered!

Note: timer0 interrupt was disabled. When enabled, could still get 53kHz with the odd dropout of a pulse, or around 42kHz without any dropout when the timer0 interrupt fires.

Pattern Generator:

#include <SPI.h>
//byte pattern[] = {B01010000, B00000010, B10010110, B10111111, B11110101, B11111111, B11110101, B01010101, B01000000, B00000000}; // 34 transitions
//byte pattern[] = {B00000000, B00000010, B10010110, B10111111, B11111111, B11111111, B11111111, B01010101, B01000000, B00000000}; // 22 transitions
byte pattern[] = {B00000000, B00000000, B11111111, B11111111, B11111111, B11111111, B11111111, B00000000, B00000000, B00000000};   // 2 transitions

void setup() {
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.setDataMode(SPI_MODE1);
}

void loop() {
  for (int i = 0; i < 10; i++) {
    SPI.transfer(pattern[i]);
  }
}

Debouncer Code:

const byte intPin = 2;
byte stableWidth = 3;  // 2-128µs interval

void setup()
{
  pinMode(intPin, INPUT_PULLUP);
  pinMode (LED_BUILTIN, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(intPin), inputPin_ISR, CHANGE);
  stabilizerInit();
}

void loop()
{
}

ISR(TIMER2_COMPA_vect)
{
  TCCR2B = 0;        // stop timer counter
  TCNT2 = 0;         // clear timer counter
  EIFR = 1;          // clear pending inputPin interrupt
  EIMSK |= 1;        // enable inputPin interrupt
}

void inputPin_ISR()
{
  // your code here
  PINB |= _BV (5);   // toggle pin 13
  // required code
  TCCR2B = 2;        // start timer counter with prescaler = 8
  TCNT2 = 0;         // clear timer counter
  EIMSK &= 0;        // disable inputPin interrupt
}

void stabilizerInit() {
  const float clockResolution = 1000000.0 / F_CPU;
  unsigned long timerCycles, prescaledCycles;
  byte prescaler;
  TIMSK0 = 0; // turn off timer0 (optional)
  stableWidth = constrain(stableWidth, 2, 128);
  timerCycles = (stableWidth / clockResolution) - 1;
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0; // reset counter
  TCCR2A |= (1 << WGM21); // turn on CTC mode
  TCCR2B |= (0 << CS22) | (1 << CS21) | (0 << CS20);
  prescaler = 8;
  prescaledCycles = timerCycles / prescaler;
  OCR2A = prescaledCycles;
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
}

Code and waveforms for post 47 have been updated.

Updated performance: Up to 38kHz external interrupt signal with severe noise can be fully recovered. Works for all modes (CHANGE, FALLING, RISING) and INT0 or INT1. Only 3.6µs latency. Debouncing done in hardware (uses timer2) and ISRs, main loop is empty.

To test, use stableWidth = 200000/Maximum Input Hz (µs)