3
\$\begingroup\$

I would like to connect a mechanical switch to an interrupt pin on an Arduino Uno processor where the interrupt is configured for CHANGE interrupts (i.e., triggers on either a positive-going or negative-going signal).

I've found numerous approaches for implementation of debounce circuitry for positive or negative going signals, but I have not found suggestions for circuitry that can debounce either positive-going or negative-going signals.

Essentially, I want to use a single interrupt pin that can detect the opening or closing of an external switch, and I'd like to debounce the input using a hardware approach.


To be a bit more specific, the switch in question will open and close up to 200 times per second and I'd like to determine the amount of time it is closed each time it goes through an on/off cycle. I.e., I'm not talking about a button pushed by the user.

\$\endgroup\$
11
  • 2
    \$\begingroup\$ An RC filter should debounce both ways. \$\endgroup\$
    – Wesley Lee
    Feb 7, 2021 at 21:53
  • 3
    \$\begingroup\$ MCU IO pins may not like it when slowly changing signals such as RC filtered buttons spend a long time in the area of indeterminate level , so I recommend using a schmitt trigger to add hysteresis and drive the MCU with clean signals. Altough, I do have to wonder what is so important that user button needs to generate an interrupt, so if the interrupt is not necessary, the filtering is also not needed. \$\endgroup\$
    – Justme
    Feb 7, 2021 at 22:08
  • 2
    \$\begingroup\$ @JimLuby user287001's comment is germane. If you have any "a priori" information that can be applied, it's better to include that information than to remain blind to it. This is how optimal filters are designed, in fact. That includes Wiener and Kalman and Kalman/Bucy. Also, I am curious. Why exactly do you need to work out the effective duty cycle? What's going on, exactly? This seems important. Open your hand and let us see what you are clenching in there. \$\endgroup\$
    – jonk
    Feb 8, 2021 at 2:07
  • 2
    \$\begingroup\$ please post a link to the switch datasheet .... what kind of a mechanism operates the switch? \$\endgroup\$
    – jsotola
    Feb 8, 2021 at 3:39
  • 1
    \$\begingroup\$ So it is an actual mechanical switch? That can open and close up to 200 times per second? In other words it may stay in one state for as little as 5ms? I think RC followed by comparator with hysteresis. Or just use a schmitt trigger (which is nearly the same thing). But I am kind of feeling that this is very odd and possibly an XY problem. Also, some ARM processors have timers which are specifically designed to capture input duty cycle or pulse width. FYI. \$\endgroup\$
    – user57037
    Feb 8, 2021 at 4:22

5 Answers 5

3
\$\begingroup\$

To debounce either edge of a changing signal, use hysteresis. Many debouncing algorithms assume an active high or active low signal, but you need to detect both.

Here is the essence of the hysteresis algorithm:

    bool input_state = digitalRead(INPUT_PIN);
    unsigned long current_ms = millis();

    edge = rise = fall = false;

    // Hysteresis:
    //   If there is no change, reset the debounce timer.
    //   Else, compare the time difference with the debounce delay.
    if (input_state == output_state)
    {
      last_ms = current_ms;
    }
    else
    {
      if ((current_ms - last_ms) >= DEBOUNCE_DELAY_ms)
      {
        // Successfully debounced, so update the outputs.
        is_debounced = true;
        rise = input_state && !output_state;
        fall = !input_state && output_state;
        edge = rise || fall;
        output_state = input_state;
      }
      else
      {
          is_debounced = false;
      }
    }

This could be called from an interrupt service routine something like this:

ISR(TIMER0_COMPA_vect)
{
  static Debouncer button1(BUTTON_PIN, DEBOUNCE_DELAY_ms);
  static bool led_state = false;
  
  button1.UpdateISR();
  
  // Toggle the LED on either edge of the debounced signal.
  if (button1.Edge())
  {
    led_state = !led_state;
    digitalWrite(LED_PIN, led_state);
  }
}

Here is a link the full code of the hysteresis debouncer on GitHub which uses the millis() timer, but you might need to change it to use the micros() timer and possibly increase the interrupt frequency for greater accuracy in your application. You also need to figure out the length of DEBOUNCE_DELAY. Use an oscilloscope to see what the switch bounce noise looks like.

\$\endgroup\$
0
\$\begingroup\$

If it is an physically latching switch, I usually use this type of code in 10ms loop with RC filtering on pin(not necessary). Of course, you can use a pin change interrupt, but it is a little tricky, because you cannot miss/forget to handle even one edge on input. Example, you detect rising edge in PCINT rutine, than you start debouncing ON state like counting some numbers of positive levels, but meantime you can switch button OFF so you detect negative edge, i.e. cancel the positive debouncing or at least think about this option. The usable thing about PCINT when hadling latching switch is to hold microcontroller "idle" if no interrupt come and if interrupt come enable something like my rutine in main().

if(SWITCHED) {

    switched_on_count++;  
    switched_off_count = 0;

    if(switched_on_count > 10) {
        switched_on_count = 0;

        if(BUTTON == OFF) {                                             
                BUTTON = ON;        
        }                                       
    }
                                                    
} else {

    switched_off_count++;           
    switched_on_count = 0;

    if(switched_off_count > 10) {
        switched_off_count = 0;

        if(BUTTON == ON) {                                              
                BUTTON = OFF;       
        }                   
    }

}
\$\endgroup\$
1
  • \$\begingroup\$ OP said that the input can switch up to 200x per second and OP wants to capture pulse duration. 200x implies a duration of 5ms only. All that taken together seems to require that the switch be de-bounced externally in hardware to avoid overwhelming the system with interrupts. From what I can tell from the OP, polling at 10ms is not even remotely going to work (although normally for buttons polling at 10ms is a great approach). \$\endgroup\$
    – user57037
    Feb 8, 2021 at 9:37
0
\$\begingroup\$

A simple way to do this is with an interating algorith.

It is similar to the algorithm proposed by @Michal Podmanický however you do not need to maintain a counter for both ON and OFF positions. You simply use an iterator variable, that damps the oscillating input (i.e. bouncing switch) and changes the reading state only when the threshold is reached, either when the iterator is back to 0 for low state, or when it reached the positive threshold for high state.

#define MAXIMUM (10) // set this value depending on how quicly your fct is called
// and how bouncy your hardware is
uint8_t iterator = 0; // starts low

bool debounce(bool input)
{
  bool now_debounced_input;
    if (input == 0)
    {
        if (iterator > 0)
            iterator--;
    }
    else if (iterator < MAXIMUM)
        iterator++;

    if (iterator == 0)
        now_debounced_input = 0;
    else if (iterator >= MAXIMUM)
    {
        iterator = MAXIMUM; // Defensive code
        now_debounced_input = 1;
    }
return now_debounced_input ;
}
\$\endgroup\$
4
  • \$\begingroup\$ You would run that inside an ISR that is getting called 100's or 1000's of times per second? \$\endgroup\$
    – user57037
    Feb 8, 2021 at 4:23
  • \$\begingroup\$ No, a flag is preferable over methods in an ISR \$\endgroup\$
    – JCSB
    Feb 8, 2021 at 13:11
  • \$\begingroup\$ Right but if the contact is bouncing, the flag will be getting set perhaps at 1kHz rate or even faster. And the OP is trying to extract duration of low time and/or duration of high time of the IO pin. So the OP needs to accurately detect positive and negative edges and measure elapsed time between them. \$\endgroup\$
    – user57037
    Feb 8, 2021 at 17:53
  • \$\begingroup\$ Nvm I didnt realize it wasnt for a user button -.- \$\endgroup\$
    – JCSB
    Feb 10, 2021 at 15:50
0
\$\begingroup\$

In broad terms, what you need is to get the logical output of the switch through a Low-Pass Filter. There are several possibles implementations : hardware (RC-filter into a µC pin, more complex ones) ; or software (the answers of JCSB and Michal P. for example). The best ones will depends on the specifics on the circuit you're designing. If you need to sample the value at 1 kHz, hardware implementations may be preferable to avoid software load, especially if your µC is not fast.

It's important to note that you may need to take into the delay induced by the Low-Pass Filter if your application needs to be especially reactive (although it shouldn't be a problem @ 1kHz).

I would also advise to use an hysteresis filter (a Schmitt trigger for example) between the LP-Filter output and the input of your system to avoid too much variation of the value : the solutions of JCSB and Michal P. intrinsically provide this function, software-side.

\$\endgroup\$
0
\$\begingroup\$

All - I decided to digitize the switch waveform. Given that I can determine duty cycle and other things in post-processing. I appreciate everyone's input and plan to try some of the suggestions later on as I would like to better understand switch debouncing and interrupt handling. Thanks again! - Jim

\$\endgroup\$
2
  • \$\begingroup\$ You mean digitize as in feed it to an ADC instead of an interrupt input? \$\endgroup\$
    – user57037
    Feb 10, 2021 at 17:31
  • 1
    \$\begingroup\$ Yes, that is what I meant. \$\endgroup\$
    – Jim Luby
    Feb 12, 2021 at 6:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.