One knock is many pulses
The spring rings, so one knock is a burst of pulses a few milliseconds apart. Count the first change to HIGH and ignore every other for 100 ms: longer than the spring rings, shorter than the gap between two knocks. It takes one timestamp and one comparison.
A burst, not a pulse
The last sketch printed several lines for one knock because it printed at every change to HIGH, and the spring makes several: it touches the rod, bounces off, swings across and touches again. Every touch is a real pulse on SIGNAL. Nothing is faulty, and no filter on the board removes it; the capacitor smooths the flicker within a touch, not the gaps between touches.
So the sketch has to decide what a knock is. The simplest rule that works: the first pulse is the knock, and every pulse for the next 100 ms is the same knock, still ringing.
Choosing the hold-off
Two knocks, a little over a quarter of a second apart, each a burst of five pulses. With 0 ms, every pulse counts and two knocks become ten: the first read. With 100 ms, the first pulse of each knock counts and the ringing falls inside the grey band, so two knocks count as two. With 400 ms, the band from the first knock swallows the second one.
The hold-off has to be longer than the spring rings and shorter than the gap between two knocks you want to count separately. The ringing in the figure is illustrative, over in about ten milliseconds; a hard knock on a hollow door may ring for longer. Knocks a person makes one after another are usually well over 100 ms apart, even when they are quick. 100 ms sits between the two, with nothing measured on this switch behind it.
One timestamp, one comparison
The sketch keeps two things from the first read, the last reading and a
count, and adds one: lastKnock, the time of the last knock it counted. On
each change to HIGH it compares now - lastKnock with HOLD_OFF_MS. Older
than that, and it is a new knock: count it and move lastKnock to now.
Younger, and it is the ringing: do nothing.
lastKnock moves only when a knock is counted. If it moved on every pulse,
a spring that kept ringing would keep pushing the window forward, and a fast
second knock could be swallowed too. Knock twice and you should see:
knocks: 1
knocks: 2The code
The first read with a hold-off. On each change to HIGH it asks one question: has it been HOLD_OFF_MS since the last knock it counted? If yes, this is a new knock; if not, it is the spring still ringing.
/*
Knock Sensor - counting knocks, with a hold-off TK28 / /p/tk28
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
(during a knock, 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 KNOCK_PIN = 4;
const unsigned long HOLD_OFF_MS = 100; // longer than the spring rings
int last = LOW; // the reading last time round
unsigned long lastKnock = 0; // millis() of the last counted knock
unsigned long knocks = 0;
void setup() {
Serial.begin(115200);
pinMode(KNOCK_PIN, INPUT); // the block has its own pull-down
}
void loop() {
int level = digitalRead(KNOCK_PIN);
unsigned long now = millis();
if (level == HIGH && last == LOW) { // a pulse starts
if (now - lastKnock >= HOLD_OFF_MS) { // not the same knock
knocks++;
lastKnock = now;
Serial.print("knocks: ");
Serial.println(knocks);
}
}
last = level;
// Other work goes here. None of it may wait.
}lastKnock only moves when a knock is counted, so the ringing cannot keep pushing it forward. The loop never waits; anything else your sketch does goes at the end of loop(), as long as it is quick.
The same hold-off in MicroPython: ticks_ms is the clock and ticks_diff subtracts two readings of it. A change to 1 counts only if the last counted knock is HOLD_OFF_MS old.
"""
Knock Sensor - counting knocks, with a hold-off TK28 / /p/tk28
Wiring. Count from the square pad on the TinkerBlock board, switch
at the top, header at the bottom:
GND -> GND
VCC -> 3V3 (never 5V: during a knock, 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.
KNOCK_PIN = 4
HOLD_OFF_MS = 100 # longer than the spring rings
knock = Pin(KNOCK_PIN, Pin.IN) # no pull: the block has its own
last = 0 # the reading last time round
last_knock = time.ticks_add(time.ticks_ms(), -HOLD_OFF_MS)
knocks = 0
while True:
level = knock.value()
now = time.ticks_ms()
if level == 1 and last == 0: # a pulse starts
if time.ticks_diff(now, last_knock) >= HOLD_OFF_MS:
knocks += 1 # not the same knock
last_knock = now
print("knocks:", knocks)
last = levelUse 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
A hard knock, or a board on something springy, rang for longer than 100 ms. Raise HOLD_OFF_MS to 150 and try again. The 100 ms is a starting point rather than a figure measured on this switch: how long it rings depends on the knock and on what the board is fixed to.
The hold-off is longer than the gap between your knocks, so a knock arrives while the sketch is still ignoring the last one. Quick knocking can bring two knocks within 200 ms of each other, so keep HOLD_OFF_MS well under the gap you actually knock at.
It counts correctly on its own, and it stops everything else for 100 ms each time: an LED you are blinking freezes, and a second sensor goes unread. The millis() version ignores the pulses without waiting, so the same loop can do other work.
It is the same idea with a different rule. A debounced button waits for the pin to hold still before believing it. A knock never holds still, so this sketch believes the first pulse at once and then ignores the rest for a fixed time.
Counting knocks while loop() is busy doing something else.
Catching it with an interrupt →Edit this page — content/books/knock-sensor/one-knock-is-many-pulses.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Knock 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.