Embedded Systems
How to debounce a button on Arduino
A button bounces: as its metal contacts close, they spring apart and touch again several times in a few milliseconds. An Arduino reads the pin thousands of times a second, so it sees each bounce as a separate press. To debounce it, ignore the pin for about 50 ms after it changes, or accept a new state only once it has held steady that long.
What bounce looks like
Wire the button from pin 2 to GND with INPUT_PULLUP, so released reads HIGH and pressed reads LOW. One press, seen up close, isn't one clean step down. It's a burst: LOW, HIGH, LOW, HIGH, LOW, all within a few milliseconds, and often a little more chatter on the way back up.
A counter that adds one for every HIGH-to-LOW change counts each of those edges. Press once and the count jumps by three or four.
Live experiment
One press, how many counts?
Press the button once, like a doorbell, and let go. With no debouncing the contacts chatter and the board counts every LOW it finds after a HIGH: one press, four counts. Switch to the delay(50) program and each press counts once.
“Digital input” is lesson 3 of 6 in The Core, Electronics World 8. Free with an account. Lessons unlock in order, so you’ll do the 51 before it first.
Fix 1: wait after each change
The simplest cure is to stop looking while the contacts settle. After the press is seen, wait 50 ms; after the release, wait again:
void loop() {
if (digitalRead(2) == LOW) {
presses = presses + 1;
delay(50); // let the press settle
while (digitalRead(2) == LOW) {} // wait for the release
delay(50); // let the release settle
}
}
It works, but the waits stop everything else. While the button is held, nothing else in loop() runs at all.
Fix 2: accept a change only once it holds
With millis(), loop() never waits. Note when the raw reading last changed, and only believe it once it has stayed put for 50 ms:
int raw = digitalRead(2);
if (raw != lastRaw) {
changedAt = millis(); // it moved: start the quiet timer again
lastRaw = raw;
}
if (millis() - changedAt >= 50 && raw != stable) {
stable = raw; // steady for 50 ms: believe it
if (stable == LOW) presses = presses + 1;
}
Every bounce restarts the timer, so the chatter never gets through. A real press holds far longer than 50 ms and is counted once.
How long to wait
Most small buttons stop bouncing within a few milliseconds, though cheap or worn ones can chatter longer. Anywhere from 20 to 50 ms covers the bounce and is still far quicker than a person can press twice, so nothing real is missed. Keyboards, game controllers and microwaves all debounce their buttons in much the same way.
Free lesson
Now build it yourself
Each lesson comes in three short parts: see the idea, try it on a live board, then build it yourself, with quick checks as you go.
“Digital input” is lesson 3 of 6 in The Core, Electronics World 8. Free with an account. Lessons unlock in order, so you’ll do the 51 before it first.
Or start from the very first lesson, no account neededPractice questions
Three quick questions from the lessons. Pick an answer to check it.
Show the answer
Answer: 10 ms. Longer than the bounce, so at most one tick lands in it; much shorter than a quick tap, so no press is missed. 1 ms would sample the bounce several times, and 2 s would miss quick taps.
- Why not “1 µs”? 1 µs is far shorter than the bounce, so many samples would land inside it and see the chatter.
- Why not “2 s”? 2 s is so slow that a quick tap could come and go between samples and be missed.
- Why not “1 ms”? 1 ms is still shorter than a 5 ms bounce, so several samples could catch it bouncing.
Show the answer
Answer: Thousands of times, once per pass of loop(). A press lasts many passes. Counting the change from not-pressed to pressed counts it once.
- Why not “Once”? Once is what counting the change gives. Counting while held adds one on every pass of loop().
- Why not “Twice”? Twice would be counting both the press and the release. Counting while held counts far more.
- Why not “Never”? A held button reads pressed on many passes, so it's counted far too often, not never.
Show the answer
Answer: the change from HIGH to LOW, not every LOW reading. The pin reads LOW on every pass for as long as the button is held, thousands of times. The change from HIGH to LOW happens once per press, so that is the moment to act.
- Why not “a delay(500) after the press”? A delay(500) slows everything down and, if the button is still held when it ends, the next LOW counts the same press again.
- Why not “every pass that reads LOW”? The pin reads LOW on every pass for as long as the button is held, so counting every LOW counts one press thousands of times.
- Why not “the release only”? The release happens once per press too, but it bounces as well and comes late, only when the finger lifts. The press is the moment to act.
“Digital input” is lesson 3 of 6 in The Core, Electronics World 8. Free with an account. Lessons unlock in order, so you’ll do the 51 before it first.
Questions people ask
Why does my Arduino count one button press several times?
The button's metal contacts bounce as they close and open, so for a few milliseconds the pin flips between HIGH and LOW. loop() reads the pin thousands of times a second, so it sees every flip and counts each one as a press.
How long should a debounce delay be?
Most buttons stop bouncing within a few milliseconds, so 20 to 50 ms is the usual choice. That outlasts the chatter, yet it's far quicker than anyone can press twice, so no real press is lost.
Can I debounce a button in hardware instead?
Yes. A resistor and a capacitor, around 10 kΩ and 1 µF, smooth the bounce into a slow ramp, and a Schmitt trigger input turns the ramp back into one clean edge. Software debouncing costs nothing, so most Arduino projects use that.
Should I count the press or the release?
Count the change from HIGH to LOW, the moment the button goes down, and ignore the pin while it stays LOW. Either edge counts each press once, but the press is when people expect something to happen.
Related topics
- What is a pull-up resistor?Why an input pin needs a pull-up, and why a pressed button reads LOW.
- How to blink an LED without delay() on ArduinoBlink an LED with millis() so loop() stays free for every other job.
- What is hysteresis, and how does it stop flicker?On below one line, off above another: the gap that stops a noisy reading flickering.
- millis() vs delay() on ArduinoWhy delay() freezes your sketch, and how millis() runs several jobs at once.