Secret Tap Detector

HARDWARE REQUIRED:

  • PICUNO Microcontroller board
  • 1 × HW-480 Two-colour LED Module
  • 1 × SG90 Servo Motor
  • 1 × HW-494 Touch sensor Module
  • 1 × 4xAA Battery Pack (For external supply)
  • Jumper wires
  • USB cable

DESCRIPTION:

This project creates a lock that opens not with a key, but with a secret rhythm. The user must tap a specific pattern on the touch sensor (e.g., two quick taps, a pause, then another two quick taps). The program measures the time intervals between the taps and compares them to a pre-defined secret pattern. If the rhythm is correct, a servo unlocks and a green LED flashes. If the rhythm is wrong or too slow, a red LED indicates failure.

CIRCUIT DIAGRAM:

Secret Tap Detector
  • Servo Motor:
    • Connect the Servo's VCC (Red) pin to the Positive (+) terminal of the 4xAA Battery Pack.
    • Connect the Servo's GND (Brown) pin to GND.
    • Connect the Servo's Signal (Yellow) pin to GPIO 7.
    • Connect the negative terminal of the 4xAA Battery Pack to Common GND on breadboard.
  • Two-Colour LED Module:
    • Connect the Green (G) pin to GPIO 9.
    • Connect the Red (R) pin to GPIO 8.
    • Connect the GND (-) pin to GND.
  • Touch Sensor Module:
    • Connect the GND (-) pin to GND pin.
    • Connect the VCC (+) pin to 3.3V.
    • Connect the Digital Output (DO) pin to GPIO 6.

    SCHEMATIC:

    Servo Motor:

    VCC (Red Wire) → 4xAA Battery Pack (+)

    GND (Brown Wire) → GND

    Signal (Yellow Wire) → GPIO 7

    Two-colour LED Module:

    Green LED (G) → GPIO 9

    Red LED (R) → GPIO 8

    GND (-) → GND

    Touch Sensor Module:

    VCC / (+) → 5V

    GND / (-) → GND

    Digital Output (DO) → GPIO 6

    Common Ground Connection:

    4xAA Battery Pack (-) → PICUNO Board GND

    CODE -- C:

    #include <Servo.h>

    const int SENSOR_PIN = 6;
    const int GREEN_LED_PIN = 9;
    const int RED_LED_PIN = 8;
    const int SERVO_PIN = 7;

    Servo myServo;

    // --- Secret Tap Pattern ---
    // 1 = short delay, 2 = long delay. This is for tap, (short), tap, (long), tap
    const int secretPattern[] = {1, 2, 1};
    const int patternLength = 3;

    // --- Timing Thresholds (in milliseconds) ---
    const int shortDelayMax = 400;
    const int longDelayMin = 500;
    const int longDelayMax = 1500;
    const long sequenceTimeout = 3000;

    // --- State Variables ---
    int tapCount = 0;
    unsigned long lastTapTime = 0;
    bool tapState = false;
    bool lastTapState = false;

    void setup() {
      Serial.begin(9600);
      pinMode(SENSOR_PIN, INPUT);
      pinMode(GREEN_LED_PIN, OUTPUT);
      pinMode(RED_LED_PIN, OUTPUT);
      
      myServo.attach(SERVO_PIN, 500, 2500);

      resetSequence();
    }

    void loop() {
      lastTapState = tapState;
      tapState = digitalRead(SENSOR_PIN);

      if (tapState == HIGH && lastTapState == LOW) {
        unsigned long currentTime = millis();
        
        if (tapCount == 0) {
          startSequence();
        } else {
          long interval = currentTime - lastTapTime;
          int tapType = 0;

          if (interval < shortDelayMax) tapType = 1; // Short
          else if (interval > longDelayMin && interval < longDelayMax) tapType = 2;

          if (tapType == secretPattern[tapCount - 1]) {
            Serial.print("Correct tap "); Serial.println(tapCount);
            lastTapTime = currentTime;
            tapCount++;
          } else {
            failSequence();
          }
        }
      }

      if (tapCount == patternLength) {
        successSequence();
      }
      
      if (tapCount > 0 && millis() - lastTapTime > sequenceTimeout) {
        Serial.println("Sequence timed out.");
        failSequence();
      }
    }

    void startSequence() {
      Serial.println("Sequence started...");
      lastTapTime = millis();
      tapCount = 1;
    }

    void successSequence() {
      Serial.println("ACCESS GRANTED!");
      digitalWrite(GREEN_LED_PIN, LOW); // Turn off green
      for (int i=0; i<5; i++) {
        digitalWrite(GREEN_LED_PIN, HIGH); delay(100);
        digitalWrite(GREEN_LED_PIN, LOW); delay(100);
      }
      myServo.write(90); // Unlock
      delay(5000);
      resetSequence();
    }

    void failSequence() {
      Serial.println("ACCESS DENIED!");
      digitalWrite(GREEN_LED_PIN, LOW); // Turn off green
      for (int i=0; i<3; i++) {
        digitalWrite(RED_LED_PIN, HIGH); delay(250);
        digitalWrite(RED_LED_PIN, LOW); delay(250);
      }
      resetSequence();
    }

    void resetSequence() {
      myServo.write(1);
      tapCount = 0;
      digitalWrite(RED_LED_PIN, LOW);
      digitalWrite(GREEN_LED_PIN, HIGH);
      Serial.println("Ready for new sequence.");
    }
    secretPattern array - This array stores your secret rhythm. 1 represents a short pause and 2 represents a long pause between taps.

    Rising Edge Detection - The logic if (tapState == HIGH && lastTapState == LOW) detects the exact moment a new tap begins, preventing one long touch from being counted multiple times.

    millis() - This function gets the current time from the Arduino's internal clock. It's used to measure the precise time interval between each tap to check if the rhythm is correct.

    Timing Thresholds - Variables like shortDelayMax define what the code considers a "short" or "long" pause, allowing you to calibrate the required rhythm.

    tapCount variable - This acts as the program's memory, keeping track of how many correct taps have been entered in the current sequence.

    Helper Functions - The code is organized into functions like successSequence() and failSequence() to keep the main loop clean and make the different outcomes easy to manage.

    CODE -- PYTHON:

    from machine import Pin, PWM
    from time import sleep_ms, ticks_ms, ticks_diff

    SENSOR_PIN = 6
    GREEN_LED_PIN = 9
    RED_LED_PIN = 8
    SERVO_PIN = 7

    # --- Component Objects ---
    touch_sensor = Pin(SENSOR_PIN, Pin.IN)
    green_led = Pin(GREEN_LED_PIN, Pin.OUT)
    red_led = Pin(RED_LED_PIN, Pin.OUT)
    servo_pwm = PWM(Pin(SERVO_PIN))
    servo_pwm.freq(50)

    # --- Secret Tap Pattern ---
    # 1 = short delay, 2 = long delay. This is for tap, (short), tap, (long), tap
    secret_pattern = [1, 2]
    pattern_length = 3 # Total number of taps required

    # --- Timing Thresholds (in milliseconds) ---
    SHORT_DELAY_MAX = 400
    LONG_DELAY_MIN = 500
    LONG_DELAY_MAX = 1500
    SEQUENCE_TIMEOUT = 3000

    # --- State Variables ---
    tap_count = 0
    last_tap_time = 0
    last_sensor_state = 0

    # --- Helper Functions ---
    def set_servo_angle(angle):
        duty = ((angle / 180) * (8350 - 1350)) + 1350
        servo_pwm.duty_u16(int(duty))

    def reset_sequence():
        global tap_count
        set_servo_angle(1) # Lock
        red_led.low()
        green_led.high() # Ready state
        tap_count = 0
        print("Ready for new sequence.")

    def success_sequence():
        print("ACCESS GRANTED!")
        green_led.low()
        for i in range(5):
            green_led.high(); sleep_ms(100)
            green_led.low(); sleep_ms(100)
        set_servo_angle(90) # Unlock
        sleep_ms(5000)
        reset_sequence()

    def fail_sequence():
        print("ACCESS DENIED!")
        green_led.low()
        for i in range(3):
            red_led.high(); sleep_ms(250)
            red_led.low(); sleep_ms(250)
        reset_sequence()

    # --- Initial State ---
    reset_sequence()

    # --- Main Loop ---
    while True:
        current_sensor_state = touch_sensor.value()
        
        # Check for a new tap (a rising edge from 0 to 1)
        if current_sensor_state == 1 and last_sensor_state == 0:
            current_time = ticks_ms()
            
            if tap_count == 0: # First tap
                print("Sequence started...")
                last_tap_time = current_time
                tap_count = 1
            else: # Subsequent taps
                interval = ticks_diff(current_time, last_tap_time)
                tap_type = 0

                if interval < SHORT_DELAY_MAX: tap_type = 1 # Short
                elif interval > LONG_DELAY_MIN and interval < LONG_DELAY_MAX: tap_type = 2 # Long

                if tap_type == secret_pattern[tap_count - 1]:
                    print(f"Correct tap {tap_count}")
                    last_tap_time = current_time
                    tap_count += 1
                else:
                    fail_sequence()
                    
        # Check for success
        if tap_count == pattern_length:
            success_sequence()
            
        # Check for timeout
        if tap_count > 0 and ticks_diff(ticks_ms(), last_tap_time) > SEQUENCE_TIMEOUT:
            print("Sequence timed out.")
            fail_sequence()
            
        last_sensor_state = current_sensor_state
        sleep_ms(20)
    Rising Edge Detection - The logic if current_sensor_state == 1 and last_sensor_state == 0: is crucial. It ensures the code only registers the exact moment your finger first touches the sensor, preventing a long touch from being counted multiple times.

    ticks_ms() and ticks_diff() - These are MicroPython's robust tools for measuring time intervals, which are perfect for checking the rhythm of the taps.

    State Management - The tap_count variable is the core of the state machine, keeping track of how far along the user is in the secret sequence.

    secret_pattern list - Stores the secret rhythm. 1 represents a short pause between taps, and 2 represents a long pause.