Motion Detection Security System using Raspberry pi pico Wokwi Simulator

Build a professional motion detection security system using Raspberry Pi Pico and PIR (Passive Infrared) sensor. Detect human movement, trigger alarms, activate lights, and display alerts on an LCD screen. Perfect for learning security system design and motion sensing without buying physical components - test everything in Wokwi simulator first!


Key Features

  • Real-time motion detection using PIR sensor (detects humans up to 7 meters)
  • Arm/Disarm functionality with push button control
  • Visual alerts: Red LED blinks when motion detected
  • Audible alarm: Buzzer sounds with warning pattern
  • System status display on 16x2 LCD screen
  • Armed indicator: Green LED shows system is active
  • Detection counter: Tracks total number of intrusions
  • Cooldown period: Prevents false alarm spam (3-second reset)
  • Automatic alarm duration: 5-second alarm cycle
  • Serial monitoring: Real-time status updates
  • Debounced button: Professional button handling

Components Required

  • Raspberry Pi Pico (simulated in Wokwi)
  • HC-SR501 PIR Motion Sensor (Passive Infrared)
  • 16x2 LCD Display (I2C connection)
  • Red LED (Alert/Motion detected indicator)
  • Green LED (System armed indicator)
  • Buzzer (Alarm/Alert sound)
  • Push Button (Arm/Disarm control)
  • 2x 220Ω Resistors (for LEDs)
  • Breadboard and jumper wires
  • MicroPython firmware on Pico

Circuit Connections

PIR Motion Sensor (HC-SR501):

  • VCC → 5V (VBUS Pin 40)
  • GND → GND
  • OUT → GPIO15 (Digital output - HIGH when motion detected)

LCD Display (I2C):

  • SDA → GPIO0 (I2C0 SDA)
  • SCL → GPIO1 (I2C0 SCL)
  • VCC → 5V (VBUS)
  • GND → GND

Red LED (Alert Indicator):

  • Anode (+) → GPIO16 (through 220Ω resistor)
  • Cathode (-) → GND

Green LED (Armed Indicator):

  • Anode (+) → GPIO17 (through 220Ω resistor)
  • Cathode (-) → GND

Buzzer:

  • Positive → GPIO14
  • Negative → GND

Push Button (ARM/DISARM):

  • One terminal → GPIO18
  • Other terminal → 3.3V
  • Internal pull-down resistor enabled in code
Code:
diagram.json:
{
  "version": 1,
  "author": "Motion Detection Security System",
  "editor": "wokwi",
  "parts": [
    {
      "type": "wokwi-pi-pico",
      "id": "pico",
      "top": 0,
      "left": 0,
      "attrs": {}
    },
    {
      "type": "wokwi-pir-motion-sensor",
      "id": "pir1",
      "top": -60,
      "left": 300,
      "attrs": {}
    },
    {
      "type": "wokwi-lcd1602",
      "id": "lcd1",
      "top": 150,
      "left": 300,
      "attrs": {
        "pins": "i2c"
      }
    },
    {
      "type": "wokwi-buzzer",
      "id": "buzzer1",
      "top": -100,
      "left": 450,
      "attrs": {}
    },
    {
      "type": "wokwi-led",
      "id": "led1",
      "top": -100,
      "left": -150,
      "attrs": {
        "color": "red",
        "label": "ALERT"
      }
    },
    {
      "type": "wokwi-led",
      "id": "led2",
      "top": -50,
      "left": -150,
      "attrs": {
        "color": "green",
        "label": "ARMED"
      }
    },
    {
      "type": "wokwi-pushbutton",
      "id": "btn1",
      "top": 50,
      "left": -150,
      "attrs": {
        "color": "blue",
        "label": "ARM/DISARM"
      }
    },
    {
      "type": "wokwi-resistor",
      "id": "r1",
      "top": -90,
      "left": -220,
      "attrs": {
        "value": "220"
      }
    },
    {
      "type": "wokwi-resistor",
      "id": "r2",
      "top": -40,
      "left": -220,
      "attrs": {
        "value": "220"
      }
    }
  ],
  "connections": [
    [
      "pico:GP0",
      "lcd1:SDA",
      "green",
      [
        "v0"
      ]
    ],
    [
      "pico:GP1",
      "lcd1:SCL",
      "blue",
      [
        "v0"
      ]
    ],
    [
      "lcd1:VCC",
      "pico:VBUS",
      "red",
      [
        "v0"
      ]
    ],
    [
      "lcd1:GND",
      "pico:GND.8",
      "black",
      [
        "v0"
      ]
    ],
    [
      "pir1:VCC",
      "pico:VBUS",
      "red",
      [
        "v0"
      ]
    ],
    [
      "pir1:GND",
      "pico:GND.3",
      "black",
      [
        "v0"
      ]
    ],
    [
      "pir1:OUT",
      "pico:GP15",
      "orange",
      [
        "v0"
      ]
    ],
    [
      "buzzer1:1",
      "pico:GP14",
      "purple",
      [
        "v0"
      ]
    ],
    [
      "buzzer1:2",
      "pico:GND.3",
      "black",
      [
        "v0"
      ]
    ],
    [
      "r1:1",
      "pico:GP16",
      "red",
      [
        "v0"
      ]
    ],
    [
      "r1:2",
      "led1:A",
      "",
      [
        "v0"
      ]
    ],
    [
      "led1:C",
      "pico:GND.1",
      "black",
      [
        "v0"
      ]
    ],
    [
      "r2:1",
      "pico:GP17",
      "green",
      [
        "v0"
      ]
    ],
    [
      "r2:2",
      "led2:A",
      "",
      [
        "v0"
      ]
    ],
    [
      "led2:C",
      "pico:GND.1",
      "black",
      [
        "v0"
      ]
    ],
    [
      "btn1:1.l",
      "pico:GP18",
      "blue",
      [
        "v0"
      ]
    ],
    [
      "btn1:1.r",
      "pico:3V3",
      "red",
      [
        "v0"
      ]
    ]
  ],
  "dependencies": {}
}

Built-in LED (Status):

  • Automatically connected to GPIO25 (blinks when armed)
Code:
Main.py:
"""
Motion Detection Security System
Raspberry Pi Pico with PIR Sensor
Wokwi Simulator Compatible

Components:
- Raspberry Pi Pico
- PIR Motion Sensor (HC-SR501)
- LCD Display (I2C 16x2)
- Buzzer (Alarm)
- Red LED (Alert indicator)
- Green LED (System armed indicator)
- Push Button (Arm/Disarm system)
"""

from machine import Pin, I2C
import time

# ============ PIN CONFIGURATION ============
PIR_PIN = 15              # PIR sensor output
BUZZER_PIN = 14           # Buzzer/Alarm
LED_ALERT = 16            # Red LED - Motion detected
LED_ARMED = 17            # Green LED - System armed
BUTTON_PIN = 18           # Push button to arm/disarm
LED_STATUS = 25           # Built-in LED - Status indicator

# Alarm settings
ALARM_DURATION = 5        # Alarm sounds for 5 seconds
COOLDOWN_TIME = 3         # 3 seconds cooldown between detections

# ============ HARDWARE INITIALIZATION ============
# Initialize PIR sensor
pir = Pin(PIR_PIN, Pin.IN)

# Initialize outputs
buzzer = Pin(BUZZER_PIN, Pin.OUT)
led_alert = Pin(LED_ALERT, Pin.OUT)
led_armed = Pin(LED_ARMED, Pin.OUT)
led_status = Pin(LED_STATUS, Pin.OUT)

# Initialize button with pull-down
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_DOWN)

# Initialize I2C for LCD (GP0=SDA, GP1=SCL)
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)

# ============ LCD 16x2 I2C CLASS ============
class LCD_I2C:
    def __init__(self, i2c, addr=0x27, rows=2, cols=16):
        self.i2c = i2c
        self.addr = addr
        self.rows = rows
        self.cols = cols
       
        # LCD Commands
        self.LCD_CLEARDISPLAY = 0x01
        self.LCD_RETURNHOME = 0x02
        self.LCD_ENTRYMODESET = 0x04
        self.LCD_DISPLAYCONTROL = 0x08
        self.LCD_FUNCTIONSET = 0x20
        self.LCD_SETDDRAMADDR = 0x80
       
        # Flags
        self.LCD_DISPLAYON = 0x04
        self.LCD_CURSOROFF = 0x00
        self.LCD_BLINKOFF = 0x00
        self.LCD_ENTRYLEFT = 0x02
        self.LCD_ENTRYSHIFTDECREMENT = 0x00
        self.LCD_4BITMODE = 0x00
        self.LCD_2LINE = 0x08
        self.LCD_5x8DOTS = 0x00
       
        self.LCD_BACKLIGHT = 0x08
        self.LCD_NOBACKLIGHT = 0x00
       
        self.backlight_state = self.LCD_BACKLIGHT
       
        # Initialize display
        self.init_display()
   
    def write_byte(self, byte, mode):
        """Write a byte to the LCD"""
        high_bits = mode | (byte & 0xF0) | self.backlight_state
        low_bits = mode | ((byte << 4) & 0xF0) | self.backlight_state
       
        # Write high nibble
        self.i2c.writeto(self.addr, bytearray([high_bits]))
        self.toggle_enable(high_bits)
       
        # Write low nibble
        self.i2c.writeto(self.addr, bytearray([low_bits]))
        self.toggle_enable(low_bits)
   
    def toggle_enable(self, byte):
        """Toggle enable bit"""
        time.sleep_us(1)
        self.i2c.writeto(self.addr, bytearray([byte | 0x04]))
        time.sleep_us(1)
        self.i2c.writeto(self.addr, bytearray([byte & ~0x04]))
        time.sleep_us(50)
   
    def init_display(self):
        """Initialize LCD display"""
        time.sleep_ms(50)
       
        # Put LCD into 4-bit mode
        self.write_byte(0x03, 0)
        time.sleep_ms(5)
        self.write_byte(0x03, 0)
        time.sleep_us(150)
        self.write_byte(0x03, 0)
        self.write_byte(0x02, 0)
       
        # Function set
        self.write_byte(self.LCD_FUNCTIONSET | self.LCD_4BITMODE |
                       self.LCD_2LINE | self.LCD_5x8DOTS, 0)
       
        # Display control
        self.write_byte(self.LCD_DISPLAYCONTROL | self.LCD_DISPLAYON |
                       self.LCD_CURSOROFF | self.LCD_BLINKOFF, 0)
       
        # Clear display
        self.clear()
       
        # Entry mode
        self.write_byte(self.LCD_ENTRYMODESET | self.LCD_ENTRYLEFT |
                       self.LCD_ENTRYSHIFTDECREMENT, 0)
   
    def clear(self):
        """Clear the display"""
        self.write_byte(self.LCD_CLEARDISPLAY, 0)
        time.sleep_ms(2)
   
    def set_cursor(self, row, col):
        """Set cursor position"""
        row_offsets = [0x00, 0x40]
        self.write_byte(self.LCD_SETDDRAMADDR | (col + row_offsets[row]), 0)
   
    def print(self, text, row=0, col=0):
        """Print text at specified position"""
        self.set_cursor(row, col)
        for char in str(text):
            self.write_byte(ord(char), 1)
   
    def backlight(self, state):
        """Turn backlight on/off"""
        if state:
            self.backlight_state = self.LCD_BACKLIGHT
        else:
            self.backlight_state = self.LCD_NOBACKLIGHT
        self.i2c.writeto(self.addr, bytearray([self.backlight_state]))

# Initialize LCD
try:
    lcd = LCD_I2C(i2c)
    lcd_available = True
    print("LCD initialized successfully")
except:
    lcd_available = False
    print("LCD not found - using Serial Monitor only")

# ============ GLOBAL VARIABLES ============
system_armed = False
motion_detected = False
detection_count = 0
last_detection_time = 0

# ============ HELPER FUNCTIONS ============
def startup_sequence():
    """Display startup message and test components"""
    print("\n" + "="*50)
    print("     MOTION DETECTION SECURITY SYSTEM")
    print("     Raspberry Pi Pico + PIR Sensor")
    print("="*50)
    print("\nInitializing components...")
   
    if lcd_available:
        lcd.clear()
        lcd.print("Security System", 0, 0)
        lcd.print("Initializing...", 1, 0)
   
    # Test LEDs
    print("Testing LEDs...")
    led_alert.on()
    led_armed.on()
    time.sleep(0.5)
    led_alert.off()
    led_armed.off()
   
    # Test buzzer
    print("Testing buzzer...")
    buzzer.on()
    time.sleep(0.2)
    buzzer.off()
   
    time.sleep(1)
   
    if lcd_available:
        lcd.clear()
        lcd.print("Press Button", 0, 0)
        lcd.print("to ARM system", 1, 0)
   
    print("\nSystem ready!")
    print("Press button to ARM/DISARM")
    print("="*50 + "\n")

def toggle_system():
    """Toggle system armed/disarmed state"""
    global system_armed, detection_count
   
    system_armed = not system_armed
   
    if system_armed:
        led_armed.on()
        detection_count = 0
        print("\n🔒 SYSTEM ARMED - Monitoring for motion...")
        if lcd_available:
            lcd.clear()
            lcd.print("SYSTEM ARMED", 0, 2)
            lcd.print("Monitoring...", 1, 1)
       
        # Beep to confirm
        buzzer.on()
        time.sleep(0.1)
        buzzer.off()
       
    else:
        led_armed.off()
        led_alert.off()
        buzzer.off()
        print("\n🔓 SYSTEM DISARMED - Standing by...")
        if lcd_available:
            lcd.clear()
            lcd.print("DISARMED", 0, 3)
            lcd.print("Press to ARM", 1, 1)
       
        # Double beep to confirm
        for _ in range(2):
            buzzer.on()
            time.sleep(0.1)
            buzzer.off()
            time.sleep(0.1)

def check_motion():
    """Check PIR sensor for motion"""
    return pir.value() == 1

def trigger_alarm():
    """Trigger alarm when motion is detected"""
    global detection_count, last_detection_time
   
    detection_count += 1
    current_time = time.time()
   
    print("\n" + "!"*50)
    print("⚠️  MOTION DETECTED! ⚠️")
    print(f"Detection #{detection_count}")
    print(f"Time: {current_time}")
    print("!"*50)
   
    if lcd_available:
        lcd.clear()
        lcd.print("!! ALERT !!", 0, 2)
        lcd.print(f"Motion #{detection_count}", 1, 2)
   
    # Activate alarm
    led_alert.on()
   
    # Sound alarm with pattern
    alarm_start = time.time()
    while time.time() - alarm_start < ALARM_DURATION:
        buzzer.on()
        led_status.on()
        time.sleep(0.2)
        buzzer.off()
        led_status.off()
        time.sleep(0.2)
   
    led_alert.off()
    last_detection_time = time.time()
   
    print(f"Alarm stopped. Total detections: {detection_count}")
   
    if lcd_available:
        lcd.clear()
        lcd.print("SYSTEM ARMED", 0, 2)
        lcd.print(f"Detects: {detection_count}", 1, 2)

def display_status():
    """Display current system status"""
    if lcd_available:
        if system_armed:
            lcd.clear()
            lcd.print("ARMED - Ready", 0, 1)
            lcd.print(f"Alerts: {detection_count}", 1, 2)
        else:
            lcd.clear()
            lcd.print("DISARMED", 0, 3)
            lcd.print("Press to ARM", 1, 1)

def print_status():
    """Print status to serial monitor"""
    status = "ARMED" if system_armed else "DISARMED"
    motion = "DETECTED" if check_motion() else "None"
   
    print(f"\rStatus: {status} | Motion: {motion} | Detections: {detection_count}", end="")

# ============ MAIN PROGRAM ============
def main():
    global motion_detected, last_detection_time
   
    # Startup
    startup_sequence()
   
    # Turn off all outputs initially
    led_alert.off()
    led_armed.off()
    buzzer.off()
    led_status.off()
   
    last_button_state = 0
    last_button_time = 0
    status_update_time = 0
   
    print("System running. Monitoring for button press and motion...\n")
   
    while True:
        try:
            current_time = time.time()
           
            # Button debouncing and handling
            button_state = button.value()
            if button_state == 1 and last_button_state == 0:
                if current_time - last_button_time > 0.3:  # 300ms debounce
                    toggle_system()
                    last_button_time = current_time
            last_button_state = button_state
           
            # Check for motion only if system is armed
            if system_armed:
                if check_motion():
                    # Check cooldown period
                    if current_time - last_detection_time > COOLDOWN_TIME:
                        trigger_alarm()
                        time.sleep(COOLDOWN_TIME)  # Wait before next detection
               
                # Blink status LED slowly when armed
                if int(current_time * 2) % 2 == 0:
                    led_status.on()
                else:
                    led_status.off()
            else:
                led_status.off()
           
            # Update status display every 2 seconds
            if current_time - status_update_time > 2:
                print_status()
                status_update_time = current_time
           
            time.sleep(0.1)  # Small delay to reduce CPU usage
           
        except KeyboardInterrupt:
            print("\n\nSystem stopped by user")
            # Turn off all outputs
            led_alert.off()
            led_armed.off()
            buzzer.off()
            led_status.off()
            if lcd_available:
                lcd.clear()
                lcd.print("System Stopped", 0, 0)
            break
        except Exception as e:
            print(f"\nError: {e}")
            time.sleep(1)

# Run the program
if __name__ == "__main__":
    main()

Applications
  • Home Security: Detect intruders entering rooms, hallways, or doorways
  • Office Security: Monitor restricted areas after business hours
  • Smart Lighting: Turn on lights automatically when people enter
  • Elderly Care: Alert caregivers when elderly person moves (fall detection)
  • Retail Stores: Customer entry detection and counting
  • Warehouse Security: Monitor unauthorized access to storage areas
  • Smart Parking: Detect vehicle presence in parking spaces
  • Museum Security: Protect valuable exhibits from unauthorized approach
  • Automatic Doors: Trigger door opening when people approach
  • Energy Saving: Turn off lights/AC when room is empty for extended period
  • Pet Detection: Monitor pet activity when away from home
  • Wildlife Monitoring: Detect animal movement in nature (camera trigger)

What You'll Learn

  • PIR sensor operation and passive infrared detection principles
  • Interrupt-driven programming for responsive motion detection
  • State machine design (Armed/Disarmed states)
  • Button debouncing techniques for reliable input
  • I2C communication for LCD displays
  • GPIO digital input reading and processing
  • GPIO digital output control for LEDs and buzzer
  • Alarm pattern generation with timing control
  • Event counting and tracking
  • User interface design for security systems
  • Cooldown timers to prevent false alarms
  • System status feedback (visual and audible)
  • Using Wokwi simulator for security system prototyping

Code Structure

Your MicroPython code will include:

  1. Import necessary libraries (machine, time, LCD)
  2. Initialize PIR sensor on GPIO15 as digital input
  3. Initialize LCD on I2C pins (GPIO0, GPIO1)
  4. Initialize outputs: LEDs (GPIO16, 17), Buzzer (GPIO14)
  5. Initialize button on GPIO18 with pull-down resistor
  6. Define system states:
    • DISARMED: System inactive, no monitoring
    • ARMED: System active, monitoring for motion
  7. Create toggle_system() function:
    • Switch between armed/disarmed
    • Update LED indicators
    • Provide audio feedback
  8. Create check_motion() function:
    • Read PIR sensor output
    • Return TRUE if motion detected
  9. Create trigger_alarm() function:
    • Activate buzzer with pattern
    • Flash alert LED
    • Update detection counter
    • Display alert on LCD
    • Run for 5 seconds
  10. Main loop:
    • Check button for arm/disarm
    • If armed, monitor PIR sensor
    • If motion detected, trigger alarm
    • Respect cooldown period
    • Update status display


Comments