Built-in project · Arduino Uno R3

Secret knock lock on an Arduino Uno

An SW-420 vibration sensor on the door listens for a secret rhythm, three quick knocks and one after a pause: the right one opens the servo bolt, anything else flashes red. The whole build, an Arduino Uno and 6 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate About 25 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $34. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Detecting a knock with a vibration sensor
  • Debouncing by ignoring input for 100 ms
  • Comparing rhythm instead of exact timing
  • Recording times in an array
  • Moving a servo as a lock
  • Difficulty: Intermediate
  • Time: About 25 minutes
  • Board: Arduino Uno R3
  • Parts: 8

How it works

An SW-420 vibration sensor on pin 2 listens for knocks. The secret is a rhythm: knock, knock, knock, pause, knock. The sketch records the time of each knock and waits for 1.5 seconds of silence to know you are done.

Then it compares the gaps. Each gap is scaled so the longest is 100, and each must be within 25 of 50, 50, 100. A match opens the servo bolt and lights green for 4 seconds. A miss flashes the red LED three times.

Parts list

8 parts, plus the jumper wires. They retail for about $34; on Mokxi they're free.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
Hobby servo pin PWMArduino Uno R3 pin 9
Resistor, 220 Ω (1) pin 1Arduino Uno R3 pin 7
Resistor, 220 Ω (2) pin 1Arduino Uno R3 pin 6
Vibration sensor, SW-420 pin DOArduino Uno R3 pin 2
Vibration sensor, SW-420 pin VCCArduino Uno R3 pin 5V
Hobby servo pin VCCArduino Uno R3 pin 5V
Vibration sensor, SW-420 pin GNDArduino Uno R3 pin GND
Hobby servo pin GNDArduino Uno R3 pin GND
LED, green pin CArduino Uno R3 pin GND
LED, red pin CArduino Uno R3 pin GND
LED, green pin AResistor, 220 Ω (1) pin 2
LED, red pin AResistor, 220 Ω (2) pin 2

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Secret knock lock: an SW-420 vibration sensor taped to the door, a servo
// that slides the bolt, and two lamps.
//
//   pin 2   the SW-420's DO (low when still, pulses high on a knock)
//   pin 9   the servo's signal wire (its red wire on 5 V, brown on GND)
//   pin 7   through 220 ohm to the green LED
//   pin 6   through 220 ohm to the red LED
//
// The secret is a rhythm, not a count: knock, knock, knock, pause, knock. The
// gaps are short, short, long, the long one twice the others. Knocking faster
// or slower is fine, because the sketch compares the gaps with the longest
// one, not with a clock.
//
// # Hearing one knock
//
// A knock is not one clean pulse on DO. The switch inside the can bounces for
// a few tens of milliseconds, so DO pulses high several times. The sketch
// takes the first edge as the knock and ignores DO for 100 ms after it.
//
// # The rhythm
//
// After 1.5 s with no knock the sequence is over. Each gap is scaled so the
// longest is 100, and each has to be within 25 of the secret's.

#include <Servo.h>

const int SENSOR = 2;
const int BOLT = 9;
const int GREEN_LED = 7;
const int RED_LED = 6;

const int LOCKED = 0;
const int OPEN = 90;

const int MAX_KNOCKS = 10;
const unsigned long DEBOUNCE_MS = 100;
const unsigned long DONE_MS = 1500;
const int TOLERANCE = 25;

// The secret, as gaps scaled to the longest: short, short, long.
const int SECRET[] = {50, 50, 100};
const int SECRET_GAPS = sizeof(SECRET) / sizeof(SECRET[0]);

Servo bolt;
unsigned long knockAt[MAX_KNOCKS];
int knocks = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: secret knock lock");
  pinMode(SENSOR, INPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  bolt.attach(BOLT);
  bolt.write(LOCKED);
}

bool rhythmMatches() {
  if (knocks - 1 != SECRET_GAPS) return false;
  unsigned long longest = 1;
  for (int i = 1; i < knocks; i++) {
    unsigned long gap = knockAt[i] - knockAt[i - 1];
    if (gap > longest) longest = gap;
  }
  for (int i = 1; i < knocks; i++) {
    int scaled = (int)((knockAt[i] - knockAt[i - 1]) * 100 / longest);
    Serial.print("  gap ");
    Serial.print(scaled);
    Serial.print(" (want ");
    Serial.print(SECRET[i - 1]);
    Serial.println(")");
    int off = scaled - SECRET[i - 1];
    if (off < -TOLERANCE || off > TOLERANCE) return false;
  }
  return true;
}

void unlock() {
  Serial.println("unlocked");
  digitalWrite(GREEN_LED, HIGH);
  bolt.write(OPEN);
  delay(4000);
  bolt.write(LOCKED);
  digitalWrite(GREEN_LED, LOW);
  Serial.println("locked");
}

void refuse() {
  Serial.println("wrong knock");
  for (int i = 0; i < 3; i++) {
    digitalWrite(RED_LED, HIGH);
    delay(150);
    digitalWrite(RED_LED, LOW);
    delay(150);
  }
}

void loop() {
  unsigned long now = millis();
  if (digitalRead(SENSOR) == HIGH) {
    if (knocks == 0 || now - knockAt[knocks - 1] > DEBOUNCE_MS) {
      if (knocks < MAX_KNOCKS) {
        knockAt[knocks] = now;
        knocks++;
        Serial.print("knock ");
        Serial.println(knocks);
      }
    }
  }
  if (knocks > 0 && now - knockAt[knocks - 1] > DONE_MS) {
    if (rhythmMatches()) {
      unlock();
    } else {
      refuse();
    }
    knocks = 0;
  }
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

Sets the sensor and LED pins, attaches the servo and moves the bolt to the locked position at 0 degrees.

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: secret knock lock");
  pinMode(SENSOR, INPUT);
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  bolt.attach(BOLT);
  bolt.write(LOCKED);
}

rhythmMatches sketch.ino

Checks there are exactly four knocks, finds the longest gap, and scales every gap against it. Each scaled gap must be within TOLERANCE of the secret, so knocking faster or slower still works.

bool rhythmMatches() {
  if (knocks - 1 != SECRET_GAPS) return false;
  unsigned long longest = 1;
  for (int i = 1; i < knocks; i++) {
    unsigned long gap = knockAt[i] - knockAt[i - 1];
    if (gap > longest) longest = gap;
  }
  for (int i = 1; i < knocks; i++) {
    int scaled = (int)((knockAt[i] - knockAt[i - 1]) * 100 / longest);
    Serial.print("  gap ");
    Serial.print(scaled);
    Serial.print(" (want ");
    Serial.print(SECRET[i - 1]);
    Serial.println(")");
    int off = scaled - SECRET[i - 1];
    if (off < -TOLERANCE || off > TOLERANCE) return false;
  }
  return true;
}

unlock sketch.ino

Prints a message, lights the green LED and turns the servo to OPEN. After 4 seconds it moves the bolt back and turns the LED off.

void unlock() {
  Serial.println("unlocked");
  digitalWrite(GREEN_LED, HIGH);
  bolt.write(OPEN);
  delay(4000);
  bolt.write(LOCKED);
  digitalWrite(GREEN_LED, LOW);
  Serial.println("locked");
}

loop sketch.ino

Notes each knock, ignoring any within DEBOUNCE_MS of the last. When 1.5 seconds pass with no knock, it calls rhythmMatches and then unlock or refuse, and starts over.

void loop() {
  unsigned long now = millis();
  if (digitalRead(SENSOR) == HIGH) {
    if (knocks == 0 || now - knockAt[knocks - 1] > DEBOUNCE_MS) {
      if (knocks < MAX_KNOCKS) {
        knockAt[knocks] = now;
        knocks++;
        Serial.print("knock ");
        Serial.println(knocks);
      }
    }
  }
  if (knocks > 0 && now - knockAt[knocks - 1] > DONE_MS) {
    if (rhythmMatches()) {
      unlock();
    } else {
      refuse();
    }
    knocks = 0;
  }
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • Every knock is refused This project
    You need exactly four knocks, with the long pause before the last one. The Serial Monitor shows each scaled gap.
  • One knock counts as many This project
    The switch bounces. The 100 ms ignore time handles it, so do not make DEBOUNCE_MS smaller.
  • The sequence never ends This project
    The sketch waits 1.5 seconds after the last knock. Stop knocking and wait.
  • It reads the same all the time, or false triggers. A pot sets the comparator threshold and the sensor is a very sensitive switch. Vibration sensor
    Turn the pot until the LED just goes off at rest, then a tap trips it. Read the digital output DO, and count one edge per knock instead of a level.
  • The output is wired to a 5 V logic level on a 3.3 V board, or power is missing. Vibration sensor
    Power the module from 3.3 V or 5 V to match your board's logic level. Join VCC and GND, then connect DO to a digital input. Use an interrupt for fast knocks.
  • The servo jitters, twitches or resets the board when it moves. Hobby servo
    Power it from its own 5 V supply, not the Arduino 5 V pin, and join the grounds. A hobby servo can pull several hundred milliamps. Add a 100 uF to 470 uF capacitor across its supply.
  • The servo hums at the end of its travel or will not reach full angle. Hobby servo
    Stay inside the pulse range, about 1000 to 2000 us for most hobby servos. Pushing past the end stop makes it strain. Calibrate the min and max pulse for your servo.
  • The LED is dim, burns out, or the pin reads wrong because the resistor value is off. Resistor
    Read the color bands again. A 220 ohm to 330 ohm resistor suits a typical LED on 5 V. 10k is a common pull-up or pull-down value. Check with a multimeter if unsure.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.