Why one press counts as several
A counter that adds one each time the pin changes to HIGH will sometimes add two, three or more for a single press. The switch is not faulty: metal contacts bounce, making and breaking several times in a few milliseconds, and a fast loop sees every one.
Count the change, not the level
A press is not "the pin is HIGH". The pin is HIGH for as long as you hold the
button, and loop() comes round thousands of times in that time. Counting
every HIGH reading counts hundreds per press.
A press is the moment the pin changes to HIGH. So the sketch keeps the last reading, compares each new one with it, and counts only when they differ and the new one is HIGH. That is called edge detection, and it is correct. Run it and press slowly twenty times. The count will usually come out above twenty.
The metal bounces
Inside the switch, a springy brass contact is pushed onto another. It does not land once. It touches, springs apart, touches again and settles, all within a few milliseconds. The press lasts a fraction of a second, but its first few milliseconds look like several presses.
A person cannot see that and does not need to. A microcontroller reading the pin tens of thousands of times a second sees every touch, and the edge detector counts each one as a new press. Nobody has measured this particular switch, so the waveform in the figure is illustrative: a few milliseconds is the figure commonly quoted for small tactile switches, and it varies from one press to the next.
Letting go can bounce as well. It adds changes to LOW, which this counter ignores, but the brief LOW in the middle of a release bounce can be followed by a HIGH that it does count.
Why it is intermittent
How much a contact bounces depends on how hard, how fast and how squarely it is pressed. So the same counter counts some presses once and others twice or three times. A bug that only happens sometimes looks like bad hardware, and it is the reason beginners replace buttons that were never broken.
The fix is in software and it costs three lines: the next article.
The code
Counts presses by comparing each reading with the last one, and adding one when it changes to HIGH. No delay(), so it reads the pin tens of thousands of times a second. No library.
/*
Push Button - counting presses (no debounce yet) TK04 / /p/tk04
Wiring. Count from the square pad on the TinkerBlock board, button
up, header at the bottom:
GND -> GND
VCC -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
(pressed, SIGNAL gives your pin whatever VCC is)
NC -> nothing (unconnected on the board)
SIGNAL -> D2 on an Uno, GPIO 25 on an ESP32, GPIO 4 on an
ESP32-S3, GP15 on a Raspberry Pi Pico
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
*/
// The GPIO number SIGNAL is wired to.
// Uno: 2. ESP32: 25. ESP32-S3: 4. Pico: 15.
const int BUTTON_PIN = 4;
int lastReading = LOW; // what the pin said last time round
unsigned long presses = 0;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT); // the block has its own pull-down
}
void loop() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) { // the pin has changed
lastReading = reading;
if (reading == HIGH) { // ...to HIGH: a press, or a bounce
presses++;
Serial.print("presses: ");
Serial.println(presses);
}
}
}This sketch is deliberately naive: it counts a press more than once whenever the contacts bounce. Press slowly twenty times and compare the count with twenty. The next article adds the three lines that fix it.
The same naive counter in MicroPython: compare each reading with the last, and count a change to 1. No sleep, so the loop reads the pin as fast as it can.
"""
Push Button - counting presses, no debounce yet TK04 / /p/tk04
Wiring. Count from the square pad on the TinkerBlock board, button
up, header at the bottom:
GND -> GND
VCC -> 3V3 (never 5V: pressed, SIGNAL gives your pin VCC)
NC -> nothing (unconnected on the board)
SIGNAL -> GPIO 25 on an ESP32, GPIO 4 on an ESP32-S3,
GP15 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
Nothing to install: machine is built in.
"""
from machine import Pin
# The GPIO number SIGNAL is wired to. ESP32: 25. ESP32-S3: 4. Pico: 15.
BUTTON_PIN = 4
button = Pin(BUTTON_PIN, Pin.IN) # no pull: the block has its own
last_reading = 0
presses = 0
while True:
reading = button.value()
if reading != last_reading: # the pin has changed
last_reading = reading
if reading == 1: # ...to 1: a press, or a bounce
presses += 1
print("presses:", presses)MicroPython's loop is slower than compiled Arduino code, so it may miss some of the bounce and over-count less often. It still over-counts. Stop it with Ctrl-C in Thonny's shell.
When it does not work
Yes, that is what bounce looks like. How much a contact bounces changes from press to press, with how hard and how squarely you press it, so some presses count once and some count three times. A counter that is right most of the time is the classic sign.
That is not bounce. The sketch is counting every pass of loop() while the pin is HIGH, rather than the moment it changes to HIGH. Compare the reading with the previous one, as this sketch does, and count only when they differ and the new one is HIGH.
No, it makes it more obvious. An interrupt fires on every edge, bounce included, and it is faster than any loop. Interrupts are for events too short to poll; a button needs debouncing either way, and polling in loop() is simpler.
Not if it counts right some of the time. The switch's drawing rates it for tens of thousands of presses. Every mechanical contact bounces from new; a worn one bounces for longer, which a debounce window absorbs anyway.
Believe a change only once the pin has held still, and one press counts once.
Debouncing with millis() →Edit this page — content/books/push-button/why-one-press-counts-as-several.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Push Button
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.