knock sensor/Reading it/07. One knock is many pulses
Reading it · 07 of 10

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

Counting a knock once
Hold-off
Knocks
2
Pulses
10
Counted
2
A 100 ms hold-off: the first pulse of a knock is counted, and everything for the next 100 ms is ignored. The spring has stopped ringing long before that, and the next knock comes well after it, so two knocks count as two.

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: 2

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

When it does not work

It still counts one knock twice now and then.

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.

Fast knocking counts too few.

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.

Why not just wait with delay(100) after a knock?

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.

Is this the same as debouncing a button?

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.

Where this goes next

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

Community

Questions about this product

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

Ask a question ↗

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.

Browse Modules and blocks on the forum