One hit, one action · 08 of 10

Counting hits

Believe a change only once the pin has held still for 20 ms. Contact bounce never holds still that long, so each hit counts once; a glancing hit that holds the lever in for 50 ms still counts. It takes one timestamp and one comparison, and no library.

Wait for it to hold still

Bounce is a burst of changes a few milliseconds long. A real hit is a change that then stays put for at least a few tens of milliseconds while the robot is in contact. So when the pin changes, the sketch does not believe it yet. It starts a clock. If the pin changes again, it starts the clock again. Only when the reading has held still for long enough does it believe it.

Counting hits
Debounce window
Hits
2
Counted
2
Believed after
20 ms still
A 20 ms window: a change is believed only once the pin has held still for 20 ms. The bounce never holds still that long, so each hit is counted once, 20 ms after it settles. A robot driving at 10 cm a second covers 2 mm in that time.

Try the three windows. With 0 ms every change is believed, and two hits count as six: the counter from the last article. With 20 ms, the bounce keeps restarting the clock until it stops, and each hit counts once, 20 ms after it settles. With 80 ms, the glancing brush in the figure is over before the clock runs out, and it is never counted at all.

The bounce and the hit lengths in the figure are illustrative. The counts are the sketch's own logic, run step by step on that waveform.

Two variables, one clock

The sketch is the push button's debounce with the names changed, and that article goes through it line by line. In short: reading is what the pin says now, bounce and all, and state is what the sketch has decided, which only changes once reading has not moved for DEBOUNCE_MS. A change of state to HIGH is one hit.

Choosing the window for a bumper

The window has to be longer than the bounce and shorter than the shortest hit you want to count. For a button the short end is a quick tap. For a bumper it is a glancing hit, where the robot brushes an obstacle and the lever is only in for a moment, and missing that one is how a robot ends up wedged against a table leg.

So keep the window short. 20 ms is the starting point, with nothing measured on this switch behind it. If a hit still counts twice, raise it to 30 ms before anything longer, and if the doubles are tens of milliseconds apart, look at the robot rebounding rather than at the switch.

The code

The counter from the last article with a debounce window. reading is what the pin says now; state is what the sketch has decided the bumper is. state only changes once reading has held still for DEBOUNCE_MS.

collision_count_debounced.ino
/*
  Collision Sensor - counting hits, debounced            TK17 / /p/tk17

  Wiring. Count from the square pad on the TinkerBlock board, switch
  at the top, header at the bottom:

    GND    -> GND
    VCC    -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
              (on a hit, 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 BUMPER_PIN = 4;
const unsigned long DEBOUNCE_MS = 20;   // longer than bounce lasts

int lastReading = LOW;          // what the pin said last time round
int state = LOW;                // what we have decided the bumper is
unsigned long lastChange = 0;   // millis() when the pin last moved
unsigned long hits = 0;

void setup() {
  Serial.begin(115200);
  pinMode(BUMPER_PIN, INPUT);   // the block has its own pull-down
}

void loop() {
  int reading = digitalRead(BUMPER_PIN);
  unsigned long now = millis();

  if (reading != lastReading) { // the contacts moved: restart the clock
    lastReading = reading;
    lastChange = now;
  }

  // Held still for DEBOUNCE_MS, and different from what we believed?
  if (now - lastChange >= DEBOUNCE_MS && reading != state) {
    state = reading;
    if (state == HIGH) {        // one real hit
      hits++;
      Serial.print("hits: ");
      Serial.println(hits);
    }
  }

  // Steering, motors, other sensors go here. None of it waits.
}

lastChange restarts every time the pin moves, so during a bounce it keeps restarting and nothing is believed. 20 ms after the last bounce, state catches up, and a change to HIGH counts once. loop() never waits.

When it does not work

It still counts twice now and then.

Raise DEBOUNCE_MS to 30 and try again. Bounce varies between switches and grows as a switch wears, and 20 ms is a starting point rather than a figure measured on this part. If the doubles are tens of milliseconds apart, it is the robot rebounding into the obstacle, which the next article's sketch deals with by backing away.

Glancing hits are being missed.

The window is too long. A hit is only believed after the pin has been HIGH for the whole window, so a touch shorter than it never counts. Bring DEBOUNCE_MS down towards 20 ms, and check the obstacle is meeting the lever rather than sliding past its tip.

Why not just delay(50) after a hit?

It works for a bumper on its own, and it stops everything else for 50 ms each time: the motors keep doing whatever they were doing, and a second sensor goes unread. The millis() version debounces without waiting, so the same loop can also steer.

Should I use a library such as Bounce2?

You can, and it does the same thing. Writing it once yourself is worth it: it is a few lines, it shows exactly what the library is doing, and the window is one number you can see and change when a glancing hit goes missing.

Where this goes next

A robot that reverses and turns away from what it hits, without ever waiting.

Stop and back off

Edit this page — content/books/collision-sensor/counting-hits.mdx

Community

Questions about this product

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

Ask a question ↗

Collision Sensor

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