For a standard room-security or automation project, the most reliable and cost-effective setup is an Arduino Uno R3 paired with an HC-SR501 PIR sensor. This combination gives you hardware-adjustable sensitivity and delay timers for under $30 total, while providing a clean 3.3V logic HIGH output that is perfectly safe for 5V Arduino digital inputs.

Quick Decision Path: Which Sensor Should You Buy?
  • If you need to detect motion through walls or non-metallic enclosures Choose the RCWL-0516 (Microwave radar).
  • If you need ultra-compact, low-power wearables Choose the AM312 (Mini PIR).
  • If you need standard room occupancy with adjustable delay/sensitivity Choose the HC-SR501 (Standard PIR). (This is our default pick and the focus of this guide).

Choosing Your PIR Sensor: HC-SR501 vs. AM312 vs. RCWL-0516

Passive Infrared (PIR) sensors detect changes in infrared radiation, but not all motion sensors use PIR. Before wiring up your breadboard, confirm you have the right module for your environment. Microwave sensors (RCWL) will trigger through drywall, which is a nightmare for apartment automation. Mini PIRs (AM312) lack hardware tuning, forcing you to handle all debouncing in software.

Criteria HC-SR501 (Standard PIR) AM312 (Mini PIR) RCWL-0516 (Microwave)
Detection Range Up to 7 meters (120° cone) Up to 3 meters (100° cone) Up to 9 meters (360° omnidirectional)
Technology Pyroelectric (IR heat signatures) Pyroelectric (IR heat signatures) Doppler Radar (Microwave reflection)
Adjustability Hardware pots for delay & sensitivity Fixed (software only) Fixed (hardware mods required)
Power Draw ~50 µA quiescent ~10 µA quiescent ~2.8 mA active
Enclosure Penetration No (requires Fresnel lens exposure) No (requires lens exposure) Yes (penetrates plastic/wood/drywall)

Parts List & Pin Mapping for the HC-SR501 Build

This build targets the Arduino Uno R3 (ATmega328P) or the Arduino Nano v3. The code and wiring are identical for both, as they share the same microcontroller and pin architecture. The HC-SR501 features an onboard 3.3V LDO voltage regulator, meaning you must power it with 5V, even though its logic output is 3.3V.

Required Components:

  • 1x Arduino Uno R3 or Nano v3 (~$22-$28)
  • 1x HC-SR501 PIR Motion Sensor module (~$2.50)
  • 1x 100µF electrolytic capacitor (for power decoupling)
  • 3x Male-to-Female jumper wires
  • 1x Breadboard (optional, but recommended for the capacitor)
HC-SR501 Pin Arduino Uno R3 Pin Notes
VCC 5V Do not use 3.3V; the onboard LDO needs ~4.5V minimum to regulate properly.
OUT Digital Pin 2 Outputs 3.3V HIGH when motion is detected. Safe for 5V Arduino inputs.
GND GND Ensure a common ground with the Arduino.

Wiring and Assembly Steps

  1. Set the Trigger Mode Jumper: On the bottom of the HC-SR501, locate the 3-pin header with a plastic jumper cap. Move the cap to the "L" (Single Trigger) position. In 'L' mode, the output goes HIGH for the duration set by the delay potentiometer, then goes LOW, even if motion continues. This is crucial for edge-detection in code.
  2. Adjust the Potentiometers: Using a small Phillips screwdriver, turn the Delay Time potentiometer (right side) fully counter-clockwise. This sets the hardware delay to the minimum (~3 seconds). Turn the Sensitivity potentiometer (left side) to the 12 o'clock position for a balanced ~4-meter range.
  3. Add the Decoupling Capacitor: The HC-SR501 is notorious for false triggers when powered by noisy USB ports. Insert the 100µF capacitor across the VCC and GND rails on your breadboard, as close to the sensor's power pins as possible. Observe polarity (stripe to GND).
  4. Connect the Signal Wire: Run a jumper from the sensor's OUT pin to Digital Pin 2 on the Arduino.
  5. Power Up: Connect the Arduino to your PC via USB. The PIR sensor requires a 30-to-60-second calibration period on boot to establish a baseline thermal map of the room. Do not move in front of it during this time.

Complete Arduino Code with Edge-Case Handling

Beginner tutorials often use a simple if (digitalRead(pirPin) == HIGH) loop. This is flawed because it spams the serial monitor and triggers downstream actions (like relays or MQTT payloads) hundreds of times per second while the pin remains HIGH. The code below uses state-change detection and a non-blocking warm-up timer to ensure your logic only fires exactly once per motion event.


/*
 * Motion Detection Arduino - HC-SR501 State-Change Implementation
 * Target Board: Arduino Uno R3 / Nano v3
 * Author: ElectricalFlux
 */

// Pin Definitions
#define PIR_PIN 2
#define LED_PIN 13 // Onboard LED for visual feedback

// Timing Constants
const unsigned long WARMUP_TIME_MS = 45000; // 45 seconds for PIR thermal calibration
const unsigned long DEBOUNCE_MS = 200;      // Ignore micro-fluctuations

// State Variables
int pirState = LOW;           // Current state of the PIR
int lastPirState = LOW;       // Previous state for edge detection
unsigned long bootTime;
unsigned long lastTriggerTime = 0;
bool isCalibrated = false;

void setup() {
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);
  
  bootTime = millis();
  Serial.println("[BOOT] HC-SR501 warming up. Stay clear of sensor for 45 seconds...");
  digitalWrite(LED_PIN, HIGH); // Visual indicator that we are in warmup
}

void loop() {
  // Handle Warm-up Phase
  if (!isCalibrated) {
    if (millis() - bootTime >= WARMUP_TIME_MS) {
      isCalibrated = true;
      digitalWrite(LED_PIN, LOW);
      Serial.println("[READY] Calibration complete. Monitoring motion.");
    }
    return; // Skip motion logic during warmup
  }

  // Read Sensor
  int currentPirState = digitalRead(PIR_PIN);

  // State-Change Detection with Debounce
  if (currentPirState != lastPirState) {
    if (millis() - lastTriggerTime > DEBOUNCE_MS) {
      pirState = currentPirState;
      lastTriggerTime = millis();

      if (pirState == HIGH) {
        Serial.println("[EVENT] Motion Detected");
        digitalWrite(LED_PIN, HIGH);
        // Insert trigger logic here (e.g., MQTT publish, relay toggle)
      } else {
        Serial.println("[EVENT] Motion Ended");
        digitalWrite(LED_PIN, LOW);
      }
    }
  }
  
  lastPirState = currentPirState;
}

Debugging: "PIR Always HIGH" and False Triggers

PIR sensors are analog thermal devices wrapped in digital logic. When they fail, it is almost always an environmental or power-delivery issue, not a broken microcontroller. If your serial monitor is misbehaving, follow this diagnostic tree.

Symptom 1: Serial monitor spams "[EVENT] Motion Detected" continuously

If the log shows [EVENT] Motion Detected but never prints [EVENT] Motion Ended, the sensor's output pin is stuck HIGH.

  1. Cause 1 (Most Likely): Jumper is in 'H' mode. If the jumper cap is on the 'H' (Repeatable Trigger) pins, the sensor will re-trigger its internal timer continuously as long as it sees heat. Fix: Move jumper to 'L'.
  2. Cause 2: Delay potentiometer is maxed out. If turned fully clockwise, the hardware delay is set to ~300 seconds (5 minutes). The pin will stay HIGH for 5 minutes after you leave the room. Fix: Turn fully counter-clockwise.
  3. Cause 3: Thermal saturation. If the sensor is pointed directly at a heat source (radiator, direct sunlight), the pyroelectric element is saturated. Fix: Reposition sensor.

Symptom 2: Random "[EVENT] Motion Detected" when the room is empty

False triggers are the bane of PIR deployments. The Adafruit PIR Guide notes that environmental thermal shifts are the primary culprit.

  1. Cause 1: HVAC Airflow. An AC vent blowing across the Fresnel lens creates rapid temperature differentials that mimic a human walking. Fix: Relocate sensor or tape off the bottom lens facets.
  2. Cause 2: Power Supply Ripple. Noisy USB power from cheap wall adapters causes the onboard comparator to trip. Fix: Ensure the 100µF decoupling capacitor is installed.
  3. Cause 3: Missing Boot Calibration. If you wave your hand in front of the sensor during the first 30 seconds of power-on, the sensor memorizes your body heat as the "empty room" baseline. Fix: Power cycle and leave the room.
The First 3 Things to Check When It Fails:
  1. Multimeter Check: Measure voltage between the sensor's VCC and GND pins. It must read between 4.8V and 5.2V. If it reads 4.2V, your USB cable is dropping too much voltage.
  2. Jumper Position: Verify the cap is bridging the bottom two pins ('L' mode).
  3. Potentiometer Position: Verify the delay pot is fully counter-clockwise for testing.

Extending and Simplifying the Build

Once you have the baseline working, you will likely want to adapt it for a specific deployment. Here is how to scale the project up or down.

How to Simplify the Build

If you are building a battery-powered wearable or a hidden node where physical adjustment pots are inaccessible, ditch the HC-SR501 and use the AM312. The AM312 has no potentiometers and operates natively at 3.3V. To simplify the code, remove the warm-up timer (the AM312 calibrates in ~2 seconds) and rely entirely on software-based millis() timers to enforce your own "delay" and "lockout" periods, ignoring any hardware triggers that occur within your software-defined cooldown window.

How to Extend the Build

For a smart-home integration, swap the Arduino Uno R3 for an ESP32-DevKitC V4. The ESP32 operates at 3.3V logic natively, which perfectly matches the HC-SR501's 3.3V output without needing level shifters. You can extend the C++ code to include the WiFi.h and PubSubClient libraries, publishing the [EVENT] Motion Detected string as an MQTT payload to a topic like home/livingroom/motion.

Another high-value extension is adding an LDR (Light Dependent Resistor) in a voltage divider circuit to an analog pin (e.g., A0). By reading the ambient light level, you can programmatically disable the motion interrupt during daylight hours, saving battery life and preventing unnecessary smart-light activations. For detailed pinout and wiring diagrams for ESP32 integrations, refer to the official Arduino Hardware Documentation to ensure you are not routing PIR signals through pins that lack interrupt capabilities.