Hardware Debounce - Schmitt Trigger and INPUT_PULLUP

Hi All,

I have now accepted the fact that I bought terrible mini button from the web.
They have horrible bounce which is not playing well with my interrupts and my code.

I am looking at doing some hardware debounce, I read and saw various Schmitt trigger circuits.
(see the one attached that I plan on using)

What I am not clear on, and need help with, is…

  1. In my code I currently have all my buttons with “INPUT_PULLUP” is this still necessary if I am using the Schmitt trigger circuit - which already seems to have the resistors (I could be wrong) ??

  2. Is there a better circuit than the one attached that makes use of the internal pull up resistor of the arduino pro micro (in case I am correct at question 1) ??

  3. Any opinion on the component values of the circuit is more than welcomed. Currently my button code uses a 275ms delay to filter out the bounce.

Thanks

Hard to believe you need >200ms

Did you try a .1uf cap on input to GND on the Arduino.

Let’s see your code.

Use CTRL T to format your code.
Attach your complete sketch between code tags
[code]Paste your sketch here[/code]

  1. No you don't need the pull-up resistors in your code if you use this circuit.
  2. What Laddyd suggested.
  3. Component values look reasonable.
  4. Do you do realise that this circuit inverts the input, so you need to modify your code accordingly?
  5. If the buttons are crap wouldn't it be better to fix the actual problem and buy some better buttons?

Perry - yes, simpler to replace the buttons, but I would not have known that Schmitt triggers even exist if I had gone the simple route. I enjoy learning new things. And yes, inverts the logic, which I will flip when I have sorted out the components - needed to place an order.

Larry - code is below, I had posted it on the programming section of the forum to get help on the ISR routine since it does not work well, but I think (am guessing) that my issues come for the button bounce.
I will eventually want to add an if statement so that I only put the arduino & xbee to sleep after 4 seconds of button activity. Something like :

unsigned long currentMillis = millis();
  if (currentMillis - previousMillis > AwakeDuration) {
    sleepNow();
    previousMillis = currentMillis;
  }

but I am not sure if this will actually work - not sure of what happens to the “millis” when interrupts happen, will it screw up the 4 second period (my AwakeDuration value) because they keep “ticking” while the interupt is busy doing other things than the main loop.

#include <avr/sleep.h>

// Declare pin numbers for feedback LEDs 1 thru 4
const int LED1_pin = 10;
const int LED2_pin = 16;
const int LED3_pin = 14;
const int LED4_pin = 15;

// Declare pin numbers for bell and light buttons and power
const int BTN_Bell = 7;
const int BTN_Light_ON = 2;
const int BTN_Light_OFF = 3;
const int Xbee_Pwr_Toggle = A1; 
int BTN_Light_state = HIGH;    
int BTN_Bell_state = HIGH;    
int previousBTN_Bell_state = LOW;    



void setup()
{
  pinMode(LED1_pin, OUTPUT);
  pinMode(LED2_pin, OUTPUT);
  pinMode(LED3_pin, OUTPUT);
  pinMode(LED4_pin, OUTPUT);
  pinMode(Xbee_Pwr_Toggle, OUTPUT);
  
  pinMode(BTN_Bell, INPUT_PULLUP);       
  pinMode(BTN_Light_ON, INPUT_PULLUP);
  pinMode(BTN_Light_OFF, INPUT_PULLUP);

  
// Open Serial com ports to send data via Xbee board,
// rate matches your XBee setting (9600 is default).
  Serial1.begin(9600); 
}


void loop()
{
  // button send portion to remote units
  // on button press turn remote light (relay) on or off
  sleepNow();
  BTN_Light_state = digitalRead(BTN_Light_ON);
  if (BTN_Light_state == LOW) {         
       delay(50);                               // pause to give it time to wake up
       Serial1.write('L');               // light btn is pressed
       delay(275);                              // long delay to wait out the bounce
  }     

  BTN_Light_state = digitalRead(BTN_Light_OFF);
  if (BTN_Light_state == LOW) {         
       delay(50);                               // pause to give it time to wake up
       Serial1.write('Z');               // light btn is pressed
       delay(275);                              // long delay to wait out the bounce
  }     
  
  BTN_Bell_state = digitalRead(BTN_Bell);  
  if (BTN_Bell_state != previousBTN_Bell_state) {          
    delay(10);                               // pause to give it time to wake up
    if (BTN_Bell_state == LOW) {
        Serial1.write('B');                        // bell btn is pressed
    }
    else {
         Serial1.write('O');                       // bell btn is released
    }
    previousBTN_Bell_state = BTN_Bell_state;
    delay(50);
   }
  
  
  // feedback section from remote units - who got the message ?
  // turn corresponding LED on or off to know that remote light is on or off
  if (Serial1.available() > 0) { 
      char Msg = Serial1.read();
      switch (Msg) {
        case '1':                         // If received '1' station 1 at OFF
          digitalWrite(LED1_pin, LOW);
          break;
        case '2':                         // If received '2' station 2 at OFF
          digitalWrite(LED2_pin, LOW);
          break;
        case '3':                         // If received '3' station 3 at OFF
          digitalWrite(LED3_pin, LOW);
          break;
        case '4':                         // If received '4' station 4 at OFF
          digitalWrite(LED4_pin, LOW);
          break;
        case '5':                         // If received '5' station 1 at ON
          digitalWrite(LED1_pin, HIGH);
          break;
        case '6':                         // If received '6' station 2 at ON
          digitalWrite(LED2_pin, HIGH);
          break;
        case '7':                         // If received '7' station 3 at ON
          digitalWrite(LED3_pin, HIGH);
          break;  
        case '8':                         // If received '8' station 4 at ON
          digitalWrite(LED4_pin, HIGH);
          break;  
      }
      delay(50);        
  }   
}


void sleepNow()         
{
  digitalWrite(Xbee_Pwr_Toggle, LOW);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  attachInterrupt(digitalPinToInterrupt(BTN_Light_ON), wakeUp, LOW);
  attachInterrupt(digitalPinToInterrupt(BTN_Light_OFF), wakeUp, LOW);
  attachInterrupt(digitalPinToInterrupt(BTN_Bell), wakeUp, LOW);
   sleep_mode();
}


void wakeUp()
{
  sleep_disable();
  detachInterrupt(digitalPinToInterrupt(BTN_Light_ON));
  detachInterrupt(digitalPinToInterrupt(BTN_Light_OFF));
  detachInterrupt(digitalPinToInterrupt(BTN_Bell));
  digitalWrite(Xbee_Pwr_Toggle, HIGH);
}

Since you already have a 14 pin IC in your circuit ( HC14 ), try a 74C914 Hex Schmitt Inverter chip. Same pinout and you won't need the external components. Yes, it is an older part, but they really work well !! I have been using them for input debouncing for ages.
Tom

I think there’s no need to use 74HC14 and you should make R2 = 1k ohm. In that case there’s no need to use INPUT_PULLUP, its external pullup.

Tom - do I understand that the chip you are suggesting does not invert the logic ? Crap !! I just received my Digi-Key order of the 74HC14

I don't understand how 74C914 does debouncing without the external components. I don't think it does.

Also I think the HC14 is not really needed - Arduino has a small hysteresis too. If you remove the HC14 you likely do not need R1 - internal pullup should be enough. And maybe R2 is not needed either. You will be shorting C1 to ground but I am not sure if there is enough energy stored to cause a real problem.

dan_movie:
I have now accepted the fact that I bought terrible mini button from the web.
They have horrible bounce which is not playing well with my interrupts and my code.

So, the problem is that you are using interrupts.

Quite a waste of time trying to get around such a fundamental mistake.


Hang on!

If that is your code, it seems that you are only using interrupts to wake which is appropriate, not to read the buttons which is a singularly inappropriate use of interrupts. So what is actually the problem?

Paul_B, not sure I follow, would you care expand for a newbie like me.

OK, for two reasons you do not use interrupts do detect keypresses.

One reason is it is simply not necessary; you just poll for the keys in the part of the code where it is relevant. Not necessary means it is more complicated to use interrupts, so why make things more complicated?

The second reason is debounce. The bounce would generate a barrage of interrupts over a few milliseconds. They may be very close together which may in itself be difficult to manage and in any case, servicing multiple interrupts actually wastes time when something else may be important (or even another interrupt!). And you need to implement the debounce; that is, to determine that the key has been pressed consistently for a reasonable period of time and that it is not in the process of being released.

The debounce timing can be performed in an interrupt procedure using millis() but this adds complications; it is far easier to do it in the main loop().

Now as far as I can figure, you are using the interrupt function only to wake from sleep and polling the respective keys in the main loop. As such, I cannot see what the trouble is with bounce?


Ah! I thought you had implemented it with timing (delay()) but in fact on closer inspection you have not actually de-bounced at all, so yes, you do not need hardware debounce; you simply need a "state change" routine incorporating de-bouncing. :grinning:

Paul, thanks for going into more detail for me.

Some general thoughts / comments that I could use clarification on....

What I understood from the various sites or forums I have read is that the only way to wake-up the Arduino and also my Xbee explorer module from power down is by an interrupt, so I have tried to implement that in my code.

Where I am most confused is how to software debounce a button but not have the interrupt trigger a bunch of times while I am trying to deal with the real button press just once. To that I need to handle 2 "different" type of button presses.... the light on & light off buttons are intended to be pressed once and released BUT the bell button is meant to be pressed and held for the duration that a used wants to bell to ring, once released the bell stop ringing.

So my thought was to clean the button bounce in hardware to know that I have an actual button press, then in code figure out what to do about waking up the arduino and keeping it awake long enough to send the data and receive the confirmation response. The code that is in my original post does not take into account a hardware debounce, it just has a bunch of delays to wait out the bounce - which I think do not work with interrupts because as Paul mentioned they trigger several times during the bounce. I also thought (read somewhere) that the use of millis() in an ISR does not work because the time does not increment, so I don't understand how to build code to debounce within the interrupt routine.

Clever hardware may solve many programming problems. Clever software may solve many hardware problems. Often it is hard to say if SW or HW solution is better. Despite in general hardware debouncing of human operated buttons may be considered wasteful I think it is not so bad option if you are wealthy and can afford paying extra $0.01 for the "unneeded" cap. Interrupts may be misleading and difficult for beginners but since you need them anyway I think you may at least try HW debouncing - it is easy and will remove some (small) complexity from your program.

You are right millis(), delay(), Serial.print and many other functions rely on interrupts and should not be used in an ISR (or when interrupts are disabled for another reason) unless you know what to do. IIRC millis may be read in an ISR but will not increment until you leave the ISR while delay will be shorter than expected. Serial.print is safe until you fill the Tx buffer. And so on.

dan_movie:
The code that is in my original post does not take into account a hardware debounce, it just has a bunch of delays to wait out the bounce - which I think do not work with interrupts because as Paul mentioned they trigger several times during the bounce.

OK, as I understand it, your code enables an interrupt to wake it from sleep. As soon as it wakes, it disables the interrupt and proceeds with the rest of the code, polling the buttons to see what they say, but not doing a particularly good job of sorting out bounce or the key remaining pressed.

This is indeed how it should be done, but as I have not attempted to do the sleep and wake on interrupt myself I cannot see where the problems are. I do have code to de-bounce button presses with extreme prejudice which is to say, it registers a state change - only once - when the button is pressed for long enough for the bouncing to cease - generally 5 to 10 milliseconds as you choose. If interrupts have been effectively disabled, they have no involvement in this process.

dan_movie:
I also thought (read somewhere) that the use of millis() in an ISR does not work because the time does not increment, so I don't understand how to build code to debounce within the interrupt routine.

That is actually quite easy. Or not!

Because alternate interrupts are not serviced within an interrupt routine, you cannot wait within the interrupt routine for millis() to change, or execute delay() because you have cancelled timer interrupts. But that would in any case be foolish even if it were possible as the whole purpose of an interrupt is to take virtually no time at all.

You can however, make a note of millis() on each interrupt and compare it to a reference value - what it was on the very first interrupt.

But this does not help you at all, because the successive interrupts tell you that it has been bouncing, but not when it has stopped bouncing! You simply cannot determine which is the "final" switch closure (because it is only "final" when it does not open again)! So indeed, interrupts and de-bouncing are incompatible.