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.
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 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.
The same debounce in MicroPython: ticks_ms is the clock and ticks_diff subtracts two readings of it. state only changes once the reading has held still for DEBOUNCE_MS.
"""
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 -> 3V3 (never 5V: on a hit, 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 and time are built in.
"""
from machine import Pin
import time
# The GPIO number SIGNAL is wired to. ESP32: 25. ESP32-S3: 4. Pico: 15.
BUMPER_PIN = 4
DEBOUNCE_MS = 20 # longer than bounce lasts
bumper = Pin(BUMPER_PIN, Pin.IN) # no pull: the block has its own
last_reading = 0 # what the pin said last time round
state = 0 # what we have decided it is
last_change = time.ticks_ms()
hits = 0
while True:
reading = bumper.value()
now = time.ticks_ms()
if reading != last_reading: # the contacts moved: restart
last_reading = reading
last_change = now
# Held still for DEBOUNCE_MS, and different from what we believed?
if (time.ticks_diff(now, last_change) >= DEBOUNCE_MS
and reading != state):
state = reading
if state == 1: # one real hit
hits += 1
print("hits:", hits)Use ticks_diff, never a plain subtraction: ticks_ms wraps round, and ticks_diff gives the right answer across the wrap. The logic is line for line the Arduino sketch's.
When it does not work
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.
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.
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.
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.
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
Questions about this product
See what other owners have asked, and read their solutions.
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.