push button/Reading it/06. Why one press counts as several
Reading it · 06 of 9

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

One press, three counts
Time since the contact touched
0.0 ms
Presses
0
Counted
0
The pin reads LOW: the button is up and the pull-down holds SIGNAL at 0 V. Press it.

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_count.ino
/*
  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.

When it does not work

It counts correctly most of the time. Is it really bounce?

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.

It counts hundreds for one press.

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.

Would an interrupt on D2 fix it?

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.

Is the switch worn out?

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.

Where this goes next

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

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

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.

Browse Modules and blocks on the forum