To build a reliable Raspberry Pi motion detector, wire an HC-SR501 PIR sensor's VCC to the Pi's 5V pin, GND to Ground, and route the OUT pin through a voltage divider to GPIO 17 (Physical Pin 11), then poll the pin state using Python. While the HC-SR501 is a $2 component, cheap clones frequently output 5V on the trigger pin instead of the expected 3.3V, which will permanently fry your Pi's SoC. This guide covers the safe hardware build, robust Python polling, and exact debugging steps for common failures.

Project Spec Sheet and Target Hardware

Difficulty Rating: 2/5 (Basic wiring and Python scripting)
Estimated Time: 45 minutes
Target Board Variant: Raspberry Pi 4 Model B (4GB or 8GB). Note: If using a Raspberry Pi 5, the BCM2712 chip requires the rpi-lgpio library or an updated RPi.GPIO fork, as the legacy library will throw architecture errors.

Required Parts List

ComponentExact Variant / SpecEstimated Cost
MicrocontrollerRaspberry Pi 4 Model B (Running Raspberry Pi OS Bookworm or Bullseye)$55.00
PIR SensorHC-SR501 Passive Infrared Sensor (with BISS0001 chip)$2.50
Resistors (Voltage Divider)1x 2.2kΩ and 1x 3.3kΩ (1/4W, 5% tolerance)$0.10
WiringFemale-to-Female and Male-to-Female jumper wires (22 AWG)$3.00
PrototypingHalf-size solderless breadboard$4.00

Wiring the HC-SR501 with a Protective Voltage Divider

The HC-SR501 requires a 5V power supply to operate its internal voltage regulator and Fresnel lens circuitry reliably. According to Adafruit's PIR sensor documentation, the OUT pin is supposed to drop to roughly 3.3V when triggered. However, bench testing of off-brand HC-SR501 modules frequently reveals a full 5V output on the OUT pin. Sending 5V into the Raspberry Pi's 3.3V-tolerant GPIO pins will destroy the BCM2711 processor. We use a simple voltage divider to guarantee safety.

Pin Mapping and Voltage Divider Table

HC-SR501 PinConnection TargetPi Physical PinWire Color
VCC (Left)Pi 5V RailPin 2 or 4Red
GND (Right)Pi Ground RailPin 6Black
OUT (Middle)2.2kΩ Resistor (Series)N/AYellow
Voltage Divider Node3.3kΩ Resistor to GNDN/AN/A
Voltage Divider OutputPi GPIO 17Pin 11Green

Step-by-Step Wiring Procedure

  1. De-energize the Pi: Shut down the Raspberry Pi via the OS and disconnect the USB-C power cable. Never wire GPIO pins while the board is powered.
  2. Power the Sensor: Connect the HC-SR501 VCC pin to Physical Pin 2 (5V) and the GND pin to Physical Pin 6 (Ground).
  3. Build the Divider: Insert the 2.2kΩ resistor into the breadboard. Connect one end to the HC-SR501 OUT pin. Connect the 3.3kΩ resistor from the other end of the 2.2kΩ resistor to the ground rail.
  4. Route the Signal: Connect a jumper wire from the junction of the two resistors to Physical Pin 11 (GPIO 17) on the Pi.
  5. Tune the Potentiometers: On the back of the HC-SR501, turn the Delay Time potentiometer (left) fully counter-clockwise for a minimal ~3-second trigger duration. Turn the Sensitivity potentiometer (right) to the 12 o'clock position for medium range (~3 meters).
  6. Verify Jumper Cap: Ensure the small plastic jumper cap on the bottom right of the sensor is set to H (High Trigger). This ensures the OUT pin stays HIGH for the duration of the delay time once motion is detected.

Python Motion Detection Script with Error Handling

This script targets the Raspberry Pi 4 using the legacy RPi.GPIO library, which provides explicit hardware-level error strings useful for debugging. For modern deployments, the gpiozero library is recommended, but RPi.GPIO remains the standard for understanding low-level pin state failures.

import RPi.GPIO as GPIO
import time
import sys

# --- Pin Definitions ---
PIR_PIN = 17  # BCM GPIO 17 (Physical Pin 11)

# --- Hardware Setup ---
GPIO.setmode(GPIO.BCM)
# Enable internal pull-down to prevent floating state when sensor is disconnected
GPIO.setup(PIR_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

def motion_detected(channel):
    print(f"[ALERT] Motion detected on GPIO {channel} at {time.strftime('%H:%M:%S')}")
    # Add payload logic here (e.g., trigger camera, send MQTT message)

def motion_stopped(channel):
    print(f"[CLEAR] Motion cleared on GPIO {channel} at {time.strftime('%H:%M:%S')}")

try:
    print("Raspberry Pi Motion Detector initialized. Waiting for triggers...")
    # Add event detection for both rising (motion start) and falling (motion end) edges
    GPIO.add_event_detect(PIR_PIN, GPIO.BOTH, callback=None, bouncetime=200)
    
    # Fallback polling loop if interrupts fail or for simple logging
    while True:
        current_state = GPIO.input(PIR_PIN)
        if current_state == 1:
            print("Status: ACTIVE (Motion)")
        else:
            print("Status: IDLE (No Motion)")
        time.sleep(1)

except RuntimeError as e:
    print(f"[FATAL HARDWARE ERROR] {e}")
    sys.exit(1)
except KeyboardInterrupt:
    print("\n[INFO] Script terminated by user.")
finally:
    # Critical: Always clean up to release GPIO locks
    GPIO.cleanup()
    print("[INFO] GPIO pins cleaned up successfully.")

Debugging: Exact Error Strings and the First Three Physical Checks

When your Raspberry Pi motion detector fails, the issue is almost always power delivery or GPIO state locking. Before rewriting code, check these exact error strings and physical faults.

The First Three Things to Check When It Fails:
  1. 5V Rail Brownout: The HC-SR501 draws up to 65mA during initialization. If your Pi's power supply is marginal, the sensor will cause a brownout, resetting the Pi or causing erratic GPIO reads. Check dmesg | grep -i voltage for under-voltage warnings.
  2. Floating Pin Noise: If the sensor is disconnected but the script triggers randomly, GPIO 17 is floating. Ensure the pull_up_down=GPIO.PUD_DOWN argument is in your setup, or add a physical 10kΩ pull-down resistor.
  3. Sensor Warm-up Time: The HC-SR501 requires 30 to 60 seconds on boot to calibrate its ambient infrared baseline. If you test it immediately after plugging in the Pi, it will trigger continuously.

Common Python Error Strings and Ranked Causes

Error String: RuntimeError: No access to /dev/mem. Try running as root!

  • Cause 1 (Most Likely): You are running the script as a standard user without GPIO group permissions. Fix: Run with sudo python3 motion.py or add your user to the gpio group via sudo usermod -aG gpio $USER.
  • Cause 2: You are using a heavily restricted container (Docker) without passing the --privileged flag or mapping /dev/gpiomem.

Error String: RuntimeError: This channel is already in use, continuing anyway. Use GPIO.setwarnings(False) to disable warnings.

  • Cause 1 (Most Likely): A previous execution of your script crashed or was killed before reaching GPIO.cleanup(), leaving the pin locked in the kernel. Fix: Run a dummy cleanup script (import RPi.GPIO as GPIO; GPIO.cleanup()) or reboot the Pi.
  • Cause 2: Another background service (like a Home Assistant GPIO integration or a cron job) is currently polling GPIO 17.

Extending and Simplifying the Build

How to Simplify: If you want to eliminate boilerplate and avoid manual interrupt handling, swap RPi.GPIO for the gpiozero library. Using from gpiozero import MotionSensor and pir = MotionSensor(17) reduces the code to three lines and handles debouncing and pull-down resistors automatically in the background via the Raspberry Pi GPIO configuration framework.

How to Extend:

  • Add Vision: Wire a Raspberry Pi Camera Module 3 to the CSI port. Modify the motion_detected() callback to execute libcamera-still -o /home/pi/intruder.jpg via the subprocess module.
  • Smart Home Integration: Install the paho-mqtt Python library. Inside the callback, publish a JSON payload {"state": "ON", "sensor": "pir_living_room"} to an MQTT broker, allowing Home Assistant to trigger automations natively.

Frequently Asked Questions

Why is my Raspberry Pi motion detector triggering randomly without movement?

Random triggers are usually caused by three environmental or electrical factors. First, the Fresnel lens is detecting rapid changes in ambient heat; ensure the sensor is not pointed at HVAC vents, radiators, or direct sunlight. Second, pets (especially cats jumping on furniture) easily trigger the 7-10 micron wavelength detection of the BISS0001 chip. Third, electrical noise from nearby switching power supplies or long, unshielded jumper wires can induce voltage spikes on the OUT pin. Keep the wire run between the sensor and the Pi under 12 inches, and use the voltage divider outlined above to filter high-frequency noise.

Can I power the HC-SR501 from the Raspberry Pi 3.3V pin?

No. The HC-SR501 contains an onboard LDO (Low Dropout) voltage regulator designed to step 5V down to 3.3V for the internal BISS0001 logic chip. If you feed it 3.3V directly, the regulator will starve the chip, resulting in a sensor that either never triggers or triggers continuously with a solid HIGH output. You must power the VCC pin from the Pi's 5V rail. The voltage divider on the OUT pin is what safely steps the return signal back down to the Pi's 3.3V logic level.

How do I integrate this Raspberry Pi motion detector with Home Assistant?

The most robust method is using MQTT. Install the Mosquitto broker on your Pi or Home Assistant server. In your Python script, import the paho.mqtt.client library and publish to a topic like homeassistant/binary_sensor/pir_motion/state with payloads of ON or OFF. In Home Assistant, add an MQTT Binary Sensor to your configuration.yaml pointing to that exact topic. This avoids the latency and polling overhead of trying to expose the Pi's raw GPIO pins directly over the network via REST APIs.