Not sure if I'm posting this in the correct place (admin feel free to move it if necessary).
After reading through many different sketches on plenty of sites (including this one), I failed to find a compact and reliable method of debouncing a switch input.
So, I thought outside the box and came up with this......
Rather than fighting against the inherent instability of a switch activation, I chose to go with the flow, by reading the pin multiple times, then averaging the results.
It seems to work, but I would like input on the idea and would also like to maybe turn it into a library (not sure if this is the right term).
The core of the idea is in the Debounce() function. All else is just for testing.
Thanks in advance.
int intBut[2];
void setup() {
pinMode(10, INPUT);
pinMode(13, OUTPUT);
Serial.begin(9600);
}
int Debounce(int intPin) {// determine actual state of (intPin)
int intCount = 0;//cycles of "while" loop
int intCalc = 0;//accumulates results of pin read
unsigned long ulETime = (millis() + 20);//sets duration of "while" loop
while (millis() <= ulETime) {//loops until preset duration has elapsed
intCalc += digitalRead(intPin);//adds up all results of pin read
++intCount;//increments counter
}
if (intCalc > (intCount / 2)) {//if the sum of all the results is greater than half the number of cycles
return 1;// the button is ON
} else {
return 0;// the button is OFF
}
}
void loop(){
intBut[0] = Debounce(10);
if (intBut[0]!=intBut[1]){
intBut[1] = intBut[0];
if (intBut[0] == 1){
Serial.println("ON");
}
if (intBut[0] == 0){
Serial.println("OFF");
}
}
}
Cool idea! If I was using this, though, I'd probably make the very minor modification to pass the factor you use to determine minimum number of digitalRead()'s (in your case, 2) as an argument, just so I can change the tolerance (I guess you'd call it that?) or use a #define to make it's significance more clear.
However, I don't think there is good reason to make this a library... it makes more sense to just use a Debounce() function as you are now, I think, or using the debounce library that already exists, which has greater capability.
This idea can backfire. I once worked in QA. We had a product with a sluggish keyboard and users were wearing it out by punching it harder in frustration. I went to visit the software chief. He was quite proud of the debounce routine... like yours, it eschewed simple state change timing, instead used averaging and thresholds. So proud, he refused to even consider changing it. In the absence of noise, it's inefficient, slow, and unnecessarily complex.
There are two fundamental algorithms that work excellently with push button switches, they are not complex or inefficient either.
Sample the input as often as is feasible. If the state changes from inactive to active, immediately register a key. Ignore any subsequent state changes for a debounce time interval.
Sample the input as above. If the state changes from inactive to active, allow the debounce time interval to elapse. If the state is still active, register a key.
#1 has the advantage of an instant response. #2 has the advantage of being able to reject noise pulses.
All things considered, it's not rocket science. However it often confuses people. I think it's because they think it must be more complicated than it really is.
I did some simple research on switch bounce, and never measured a bounce longer than 1ms for a small switch. (Of course a big relay or contactor would have more inertia and hence more bounce)
I also looked at Thomas Ouellet Fredericks Bounce2 library, and documented the various approaches; and added a simple debounce strategy using an exponential moving average.
Before you consider debouncing a switch for a critical application you need to know the bounce characteristics for that switch and the speed of response that is required.
However I still contend that for most purposes the addition of a single capacitor and resisitor will debounce the switch effectively without adding complexity to your code.
Thanks everyone for your thoughts. As a novice programmer, it's very easy to accidently put a lot of time and effort into re-inventing the wheel.
Perhaps someone can point me to the most compact (no bells and whistles) code for debouncing, as I fear my current project will be stretching the capabilities of my uno.
I'm using this code on an ESP32. You can change the debounce time depending on how often you may want the sytem to respond to a button change
void configInterrupt() {
// Set intPin as interrupt, assign interrupt function updateCount() and set RISING mode
attachInterrupt(digitalPinToInterrupt(intPin), ISR, RISING);
/*There are 5 different modes:
LOW: to trigger the interrupt whenever the pin is LOW;
HIGH: to trigger the interrupt whenever the pin is HIGH;
CHANGE: to trigger the interrupt whenever the pin changes value – for example from HIGH to LOW or LOW to HIGH;
FALLING: for when the pin goes from HIGH to LOW;
RISING: to trigger when the pin goes from LOW to HIGH.
*/
}
// detect and count up pulses
void IRAM_ATTR ISR() {
// The IRAM_ATTR attribute places compiled code in the (faster) Internal RAM (IRAM) of the ESP32.
tNow = millis();
if (tNow - tLast > response) { // handle bounce by preventing a rapid response - "response" is in msec.
pCount++; //increment count
}
tLast = tNow;
}
At one stage I was looking for a debounce algorithm that did not use a timed interval and came across "Switch debounce using Edge Detection & Resistor-Capacitor Digital Filter" decribed at http://www.ganssle.com/tem/tem366.html .
The Digital filter mimics (in software) an analogue RC filter with first-order recursive low pass filter. It has good EMI filtering and quick response, with a nearly continuous output like an analogue circuit. Importantly for me, it is independent of 'millisecond' timers and therefore responds adaptively to the amount of switch bounce.
My implementation of this method is found in the MD_UISwitch library, also available through the IDE Library Manager.
@tentimes what Arduino are you using? How many buttons are you denouncing?
As you have discovered, you have added 1 to approximately 4094 ways to denounce a switch… nothing wrong with reinvent the wheel, as you put it, what is unfortunately sadly usually true when noobs do it is that they come up with one that is inferior if not actually square. You had fun, learnt something &c.
I would recommend continuing your research and FWIW there is no good reason to complicate the matter further by using interrupts.
There are some good solutions using external components of a broad range; again, for most cases software can be made to do.
This may be the article I followed when I was over-thinking denouncing recently as part of another project, mostly out of curiosity. I did get my own take on this concept to work.
This can be a nice solution when coupled with direct port access (easy! fun!) if you have, e.g. 5 pushbuttons.
Hi tentimes
it was necessary to make very condensed code for the microcontrollers of the last century, which had little memory. I still use this one, since it worked well on small processors, it must do the same on bigger ones ! difficult to make shorter, 8 buttons or even 16 without changing the code. Ask if you are interested in auto-repeat or press counting
//**************************************************
void loop()
{
//is it time check the switches ?
if(millis() - switchMillis >= 50)
{
//restart the TIMER
switchMillis = millis();
checkSwitches();
}
//Other non blocking code
} //END of loop()
//**************************************************
void checkSwitches()
{
byte switchStatus;
//****************************************
//1st switch
switchStatus = digitalRead(BUTTON_OPEN);
//was there a change in switch state ?
if (lastSwitchOneStatus != switchStatus)
{
//update to the new state
lastSwitchOneStatus = switchStatus;
//************
//did the switch get pushed ?
if (switchStatus == switchPushed)
{
//do something
}
} //END of if (lastSwitchOneStatus != switchStatus)
//****************************************
//2nd switch
switchStatus = digitalRead(BUTTON_CLOSE);
//was there a change in switch state ?
if (lastSwitchTwoStatus != switchStatus)
{
//update to the new state
lastSwitchTwoStatus = switchStatus;
//************
//did the switch get pushed ?
if (switchStatus == switchPushed)
{
//do something
}
} //END of if (lastSwitchTwoStatus != switchStatus)
} //END of checkSwitches()
Here is my test implementation of the approach #1 that I explained in my previous post:
/*
Debounce
Each time the input pin goes from LOW to HIGH (e.g. because of a push-button
press), the output pin is toggled from LOW to HIGH or HIGH to LOW. There's a
minimum delay between toggles to debounce the circuit (i.e. to ignore noise).
The circuit:
- LED attached from pin 13 to ground
- pushbutton attached from pin 2 to ground
- Note: On most Arduino boards, there is already an LED on the board connected
to pin 13, so you don't need any extra components for this example.
created 21 Nov 2006
by David A. Mellis
modified 30 Aug 2011
by Limor Fried
modified 28 Dec 2012
by Mike Walters
modified 30 Aug 2016
by Arturo Guadalupi
This example code is in the public domain.
modified 1 Jun 2020
by Ken Willmott
http://www.arduino.cc/en/Tutorial/Debounce
*/
// Constants:
const int BUTTON_PIN = 2; // the number of the pushbutton pin
const int LED_PIN = 13; // the number of the LED pin
const int DEBOUNCE_DELAY = 500; // the debounce time in milliseconds; increase if the output flickers
// Variables:
int ledState = HIGH; // the current state of the output pin
int debouncedButtonState = HIGH; // the debounced reading from the input pin
bool debouncing = false; // add this new variable to control debounce state
// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
// set initial LED state
digitalWrite(LED_PIN, ledState);
}
void loop() {
// check to see if you just pressed the button
// (i.e. the input went from LOW to HIGH), and you've waited long enough
// since the last press to ignore any noise:
if (debouncing) { // wait for debounce interval to elapse
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
debouncing = false;
}
}
else // check for an input change and process it if there is one
{
// read the state of the switch into a local variable:
int reading = digitalRead(BUTTON_PIN);
// previously, we said this:
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state:
// now, it's different:
// look for a reading that is different than the debounced reading
// and take it as the actual current state.
// The debounce interval will follow, not precede this.
// if the button state has changed, update the button state
// and process button change events:
if (reading != debouncedButtonState) {
debouncedButtonState = reading; // remember the new input state
lastDebounceTime = millis(); // reset and enable the debouncing timer
debouncing = true;
// process any change events here:
// only toggle the LED if the new button state is LOW
if (debouncedButtonState == LOW) { // key press
ledState = !ledState;
digitalWrite(LED_PIN, ledState); // set the LED
}
else
{
// key release
}
}
}
}
You may notice a 500 ms debounce delay. That is a test value. Load the original debounce sketch and try it. You will see that it is a terrible failure because then the key has to be pressed down that long. With this approach, the key press is still registered instantly; the only limitation is that the frequency of key strokes is limited to 2 Hz. In practice, a 20ms delay covers almost every switch ever made. Yes, some people use 5ms and it still works with almost every switch.