Difficulty: 2/5 | Estimated Time: 20 minutes | Board Target: Arduino Uno R3

A passive infrared (PIR) sensor detects changes in infrared radiation emitted by warm bodies, making it the standard choice for occupancy detection and security triggers. The HC-SR501 is the most common PIR module for embedded projects due to its onboard signal conditioning and adjustable timing. To interface a passive ir sensor arduino setup, you connect the module's VCC to 5V, GND to ground, and the OUT pin to a digital input (e.g., D2). The sensor outputs a HIGH signal when motion is detected, but requires a 30-to-60-second initialization period on boot to establish a thermal baseline.

HC-SR501 Module Specifications & Operating Modes

Before wiring the sensor, it is critical to understand the hardware limitations of the HC-SR501. The module is built around the BISS0001 pyroelectric infrared signal processor IC. It does not output raw analog data; instead, it uses internal op-amps and comparators to output a clean digital HIGH/LOW signal based on the onboard potentiometer settings.

ParameterSpecification / ValueNotes & Bench Observations
Operating Voltage4.5V to 20V DCUse 5V from Arduino. Ripple >50mV causes false triggers.
Quiescent Current< 50 μAExcellent for battery-powered nodes, but LDOs must support low load.
Detection Angle< 120° coneDetermined by the Fresnel lens. Can be masked with tape.
Detection Distance3 to 7 metersAdjustable via the 'Sensitivity' potentiometer (orange trimmer).
Delay Time0.3s to 200sAdjustable via the 'Time Delay' potentiometer. Sets how long OUT stays HIGH.
Trigger ModeL (Non-Repeat) / H (Repeat)Set via jumper block. H-mode is standard for Arduino interrupts.
Initialization Time30 to 60 secondsRequired on power-up for the pyroelectric crystal to stabilize.

Trigger Mode Comparison: H vs L

The jumper block on the HC-SR501 dictates how the sensor handles continuous motion. Choosing the wrong mode is a primary cause of 'stuck' logic in Arduino sketches.

FeatureH-Mode (Repeat Trigger)L-Mode (Non-Repeat Trigger)
Behavior during motionTimer resets as long as motion is detected.Timer runs out regardless of continued motion.
Output StateStays HIGH continuously while occupied.Goes LOW briefly at the end of the timer, then HIGH again if motion persists.
Best Use CaseLighting control, security alarms, room occupancy.Counting distinct entry events, automated dispensers.

Required Hardware & Pin Mapping

This build targets the Arduino Uno R3 (ATmega328P). While the code is compatible with the Uno R4 Minima or Nano, the Uno R3's robust 5V linear regulator handles the PIR module's transient current spikes better than the switching regulators on newer boards.

Parts List

  • Microcontroller: Arduino Uno R3 (or genuine clone with ATmega16U2 USB-to-Serial)
  • Sensor: HC-SR501 PIR Motion Sensor Module (ensure it has the BISS0001 IC and white Fresnel dome)
  • Decoupling Capacitor: 100μF electrolytic capacitor (16V or higher) — critical for noise rejection
  • Wiring: 4x male-to-female jumper wires (22 AWG silicone preferred)

Pin Mapping Table

HC-SR501 PinArduino Uno R3 PinFunction
VCC5VPower supply (do not use 3.3V, module will brownout)
OUTD2Digital signal output (HIGH on motion)
GNDGNDCommon ground reference

Step-by-Step Wiring Procedure

  1. Set the Jumper: Move the jumper cap on the HC-SR501 to the H-position (the side closest to the diode). This enables Repeat Trigger mode, which keeps the output HIGH as long as a person is in the room.
  2. Adjust Potentiometers: Using a small Phillips screwdriver, turn the 'Time Delay' pot fully counter-clockwise (minimum ~0.3s delay) for fast testing. Turn the 'Sensitivity' pot to the 12 o'clock position (medium range).
  3. Connect Power and Ground: Connect the module's VCC to the Arduino's 5V pin, and GND to GND. Do not power the sensor from a breadboard rail shared with high-current devices like motors or relays.
  4. Install the Decoupling Capacitor: Insert the 100μF capacitor directly across the VCC and GND pins on the HC-SR501 header. Observe polarity (stripe to GND). This is the most common hardware fix for false triggers caused by USB power ripple.
  5. Connect the Signal Pin: Wire the OUT pin to Arduino Digital Pin 2. Pin 2 is preferred because it supports hardware interrupts (INT0) if you decide to upgrade from polling to interrupt-driven code later.
Bench Tip: If your HC-SR501 is triggering randomly when connected to a laptop USB port, the 5V rail likely has high-frequency switching noise. The 100μF capacitor acts as a local energy reservoir and low-pass filter. For severe cases, add a 0.1μF ceramic capacitor in parallel with the electrolytic.

Complete Arduino Code with Debounce & Error Handling

The following C++ code is written for the Arduino IDE (2.x) and targets the Uno R3. It includes a mandatory 60-second warm-up blocking sequence, state-change polling, and serial debug outputs. PIR sensors often exhibit a 'bounce' on the falling edge (when the timer expires), so the code includes a software debounce to prevent rapid ON/OFF flickering in your logic.

#include <Arduino.h>

// --- Pin Definitions ---
#define PIR_PIN 2
#define LED_PIN 13 // Built-in Uno LED for visual feedback

// --- Timing Constants ---
const unsigned long WARMUP_TIME_MS = 60000; // 60 seconds for BISS0001 baseline
const unsigned long DEBOUNCE_MS = 250;      // Falling edge debounce

// --- State Variables ---
int currentPirState = LOW;
int previousPirState = LOW;
unsigned long lastStateChangeTime = 0;
bool sensorInitialized = false;

void setup() {
  Serial.begin(115200);
  while (!Serial) { ; } // Wait for serial port (native USB boards)
  
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  
  Serial.println(F("[SYS] HC-SR501 PIR Sensor Initializing..."));
  Serial.println(F("[SYS] Calibrating thermal baseline. Wait 60 seconds."));
  
  // Hardware initialization delay
  unsigned long startTime = millis();
  while (millis() - startTime < WARMUP_TIME_MS) {
    // Blink LED to indicate warm-up phase
    digitalWrite(LED_PIN, HIGH);
    delay(250);
    digitalWrite(LED_PIN, LOW);
    delay(250);
  }
  
  // Verify sensor is not stuck HIGH after warmup
  if (digitalRead(PIR_PIN) == HIGH) {
    Serial.println(F("[ERR] PIR Sensor Stuck HIGH. Check L/H jumper and power rail."));
    // Halt execution to prevent false automation triggers
    while(true) {
      digitalWrite(LED_PIN, HIGH);
      delay(1000);
      digitalWrite(LED_PIN, LOW);
      delay(1000);
    }
  }
  
  sensorInitialized = true;
  Serial.println(F("[SYS] Calibration complete. Monitoring for motion."));
}

void loop() {
  if (!sensorInitialized) return;

  int rawPirState = digitalRead(PIR_PIN);
  unsigned long currentTime = millis();

  // Debounce logic to filter falling-edge noise
  if (rawPirState != previousPirState) {
    lastStateChangeTime = currentTime;
  }

  if ((currentTime - lastStateChangeTime) > DEBOUNCE_MS) {
    // State is stable, update current state
    if (rawPirState != currentPirState) {
      currentPirState = rawPirState;
      
      if (currentPirState == HIGH) {
        Serial.println(F("[EVT] Motion Detected: Room Occupied"));
        digitalWrite(LED_PIN, HIGH);
      } else {
        Serial.println(F("[EVT] Motion Ended: Room Vacant"));
        digitalWrite(LED_PIN, LOW);
      }
    }
  }
  
  previousPirState = rawPirState;
}

Debugging: False Triggers and Initialization Errors

PIR sensors are notoriously sensitive to environmental factors. If your serial monitor outputs unexpected data, follow this diagnostic framework. According to Adafruit's PIR Sensor Guide, environmental heat and power quality account for 90% of field failures.

The First Three Things to Check When It Fails

  1. Power Supply Ripple: Measure the AC voltage across the VCC and GND pins on the sensor using a multimeter set to AC mV. If you read more than 30mV AC, your power supply is too noisy. Add the 100μF capacitor or switch to a regulated linear bench supply.
  2. Jumper Position: If the sensor triggers once and ignores subsequent motion, the jumper is likely in the 'L' (Non-Repeat) position. Move it to 'H'.
  3. Warm-Up Violation: If the sensor triggers immediately upon boot and stays HIGH, your code did not enforce the 60-second delay. The BISS0001 chip outputs garbage data until the internal reference voltage stabilizes.

Ranked Causes for Specific Error Symptoms

Symptom 1: Serial prints [EVT] Motion Detected continuously in an empty room.

  • Cause A (Most Likely): RF Interference. WiFi routers or 2.4GHz transmitters placed within 12 inches of the PIR module will induce current in the high-impedance pyroelectric element. Move the router away or shield the sensor.
  • Cause B: Thermal Drafts. An HVAC vent, space heater, or direct sunlight hitting the Fresnel lens will cause rapid ambient temperature shifts. Mask the lens with electrical tape to narrow the field of view.
  • Cause C: Sensitivity Pot too high. Turn the orange trimmer counter-clockwise by two full turns.

Symptom 2: Serial prints [ERR] PIR Sensor Stuck HIGH after the 60-second warmup.

  • Cause A: Bad ground reference. Ensure the Arduino GND and Sensor GND share the exact same bus. A floating ground will cause the comparator to latch HIGH.
  • Cause B: Damaged BISS0001 IC. If the module was accidentally wired to 12V or a reversed polarity, the internal voltage regulator is fried. Replace the module (they cost <$2).

Symptom 3: Sensor never triggers, Serial remains silent.

  • Cause A: Broken internal trace. The jumper pins on cheap clones often crack the PCB trace connecting the OUT pin to the IC. Inspect under magnification and reflow the header pins with a soldering iron.
  • Cause B: Code polling too fast without debounce, masking the state change. Ensure your loop delay or debounce logic isn't swallowing the 0.3s pulse.

Extending and Simplifying the Build

Once the baseline passive ir sensor arduino circuit is stable, you can adapt the architecture to fit specific project constraints.

How to Extend the Build (Advanced)

To build a smart-home occupancy node, migrate the code to an ESP32-WROOM-32. Because the ESP32 operates at 3.3V logic, you must use a logic level shifter or a voltage divider (e.g., 2kΩ and 3.3kΩ resistors) on the OUT pin to prevent damaging the ESP32's GPIO. Add an LDR (Light Dependent Resistor) to an ADC pin to gate the motion events—ignoring motion when the room is already flooded with daylight. Finally, integrate the Arduino MQTT library to publish occupancy states to a Home Assistant broker over WiFi.

How to Simplify the Build (No Code)

If you only need to switch a 12V LED strip or a 120V AC lamp, you can eliminate the Arduino entirely. The HC-SR501's OUT pin can source roughly 200μA, which is enough to trigger the optocoupler inside a standard 5V relay module. Connect the PIR OUT directly to the relay module's IN pin, power both from a 5V 1A wall adapter, and use the relay's NO (Normally Open) contacts to switch your load. This reduces component count, cost, and power consumption to a bare minimum.

Safety Warning: If extending this project to switch mains AC loads (120V/240V), always use a properly rated mechanical relay or solid-state relay with zero-cross detection. Never wire PIR logic directly to high-voltage lines, and ensure all mains connections are enclosed in a grounded, fire-retardant junction box.