Integrating an Arduino PIR (Passive Infrared) sensor into a project seems trivial until you encounter phantom triggers, stuck outputs, or timing loops that refuse to reset. PIR sensors detect changes in infrared radiation emitted by warm bodies, but their analog front-ends are notoriously susceptible to power supply ripple, RF interference, and thermal drafts.

This guide targets the Arduino Uno R3 (ATmega328P) and covers the two most common modules on the bench: the full-sized HC-SR501 and the miniature AM312. We will cover the exact wiring, provide robust C++ code with hardware fault detection, and break down the specific debugging steps required when your sensor misbehaves.

The Direct Answer: Wiring and Code for Arduino PIR Sensors

To get a reliable motion detection loop running, you need clean power, a defined logic path, and code that accounts for the sensor's mandatory hardware initialization time.

Parts List & Exact Variants

  • Microcontroller: Arduino Uno R3 (ATmega328P) or Arduino Nano v3 (5V logic variants).
  • Standard PIR Module: HC-SR501 (BISS0001 chip, adjustable delay/sensitivity, 5V-20V input).
  • Mini PIR Module: AM312 (Fixed 2-second delay, 2.7V-12V input, 3.3V logic compatible).
  • Passive Components: 10kΩ pull-down resistor, 100µF electrolytic capacitor (for power decoupling).
  • Wiring: 22 AWG solid core jumper wires.

Pin Mapping Table

HC-SR501 / AM312 Pin Arduino Uno R3 Pin Notes & Hardware Requirements
VCC 5V HC-SR501 requires 5V. Do not use 3.3V.
OUT D2 (Interrupt 0) Add a 10kΩ pull-down resistor to GND on this line.
GND GND Ensure a common ground with the Arduino and load.
Bench Tip: The HC-SR501 requires a 30-to-60 second warm-up period upon first receiving power to calibrate its internal analog baseline. If you poll the sensor during this window, you will get continuous false triggers. The code below handles this automatically.

Complete Compilable Code with Fault Detection

This sketch targets the Arduino Uno R3. It includes a warm-up blocking routine, state-change debouncing, and a specific hardware fault check for a stuck HIGH output.

/*
 * Arduino PIR Motion Detector with Hardware Fault Detection
 * Target Board: Arduino Uno R3 (ATmega328P)
 * Sensor: HC-SR501 or AM312
 */

#define PIR_PIN 2
#define LED_PIN 13
#define WARMUP_TIME_MS 45000 // 45 seconds for HC-SR501 baseline calibration
#define STUCK_HIGH_THRESHOLD_MS 15000 // Max physical delay is ~5s; >15s means hardware fault

unsigned long lastMotionTime = 0;
unsigned long highStartTime = 0;
bool motionState = false;
bool faultDetected = false;

void setup() {
  Serial.begin(115200);
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  
  Serial.println("[INIT] Calibrating PIR sensor... Do not move.");
  digitalWrite(LED_PIN, HIGH); // Visual indicator of warm-up
  delay(WARMUP_TIME_MS);
  digitalWrite(LED_PIN, LOW);
  Serial.println("[INIT] Calibration complete. Monitoring motion.");
}

void loop() {
  int currentReading = digitalRead(PIR_PIN);
  unsigned long currentMillis = millis();

  if (currentReading == HIGH) {
    if (highStartTime == 0) {
      highStartTime = currentMillis; // Mark when the pin first went HIGH
    }
    
    // Check for Stuck HIGH Fault (e.g., BISS0001 chip locked up or severe EMI)
    if (currentMillis - highStartTime > STUCK_HIGH_THRESHOLD_MS) {
      if (!faultDetected) {
        Serial.println("[FAULT] PIR_OUT_STUCK_HIGH for >15000ms. Check power ripple and pull-down resistor.");
        faultDetected = true;
      }
    }

    if (!motionState) {
      motionState = true;
      lastMotionTime = currentMillis;
      Serial.print("[EVENT] Motion Detected at: ");
      Serial.println(currentMillis);
      digitalWrite(LED_PIN, HIGH);
    }
  } else {
    highStartTime = 0; // Reset timer when pin goes LOW
    faultDetected = false;
    
    if (motionState) {
      motionState = false;
      Serial.print("[EVENT] Motion Ended. Duration: ");
      Serial.print(currentMillis - lastMotionTime);
      Serial.println("ms");
      digitalWrite(LED_PIN, LOW);
    }
  }
}

Debugging: Why Your PIR Sensor is Stuck HIGH or False Triggering

When your serial monitor spams [EVENT] Motion Detected every few seconds while the room is empty, or throws the [FAULT] PIR_OUT_STUCK_HIGH error, the issue is almost always analog noise or a missing passive component, not a software bug.

The First Three Things to Check When It Fails

  1. Power Supply Ripple (The #1 Culprit): The HC-SR501 uses an internal LM324 op-amp to amplify microvolt-level signals from the pyroelectric element. If you power the Arduino via a cheap, unregulated USB hub, high-frequency switching noise rides the 5V rail. The op-amp interprets this ripple as thermal motion. Fix: Solder a 100µF electrolytic capacitor directly across the VCC and GND pins on the PIR module, or power the Arduino via the barrel jack with a regulated 9V wall adapter.
  2. Missing Pull-Down Resistor: The output pin of the HC-SR501 is driven by a BJT transistor. If the cable run between the sensor and the Arduino exceeds 12 inches, the wire acts as an antenna, picking up ambient RF (especially from nearby ESP32 WiFi modules). The Arduino's internal pull-up/pull-down resistors (approx. 20kΩ-50kΩ) are too weak to shunt this noise. Fix: Install an external 10kΩ resistor between the OUT pin and GND.
  3. Thermal and RF Interference: PIR sensors detect changes in infrared heat. Placing the sensor facing an HVAC vent, a sunlit window, or directly above a hot voltage regulator will cause continuous triggering. Furthermore, placing a 2.4GHz WiFi antenna within 2 inches of the Fresnel lens will induce false triggers via RF rectification in the sensor's analog front-end.

Hardware Deep Dive: HC-SR501 vs. AM312 Mini PIR

Choosing the right module prevents logic-level mismatches and timing headaches. Here is how the two standard modules compare on the bench.

Specification HC-SR501 (Standard) AM312 (Mini)
Logic Output Voltage ~5V (Requires level shifting for 3.3V MCUs) 3.3V (Directly compatible with ESP32/RP2040)
Delay Time Adjust 0.3s to 5s (via onboard potentiometer) Fixed ~2.0 seconds
Detection Range Up to 7 meters (120° cone) Up to 3-5 meters (100° cone)
Warm-up Time 30 to 60 seconds ~2 to 3 seconds
Typical Price (2026) $1.50 - $2.50 $1.80 - $3.00

When to choose which: Use the HC-SR501 when you need adjustable timing, maximum range, and are running a 5V Arduino Uno or Mega. Use the AM312 when building compact, battery-powered IoT nodes with 3.3V microcontrollers (like the ESP32 or Raspberry Pi Pico), where a fixed 2-second delay is acceptable and fast wake-up is required.

Extending and Simplifying Your Motion Build

How to Simplify the Build

If you are building a simple closet light or a basic alarm and don't want to deal with the HC-SR501's 45-second warm-up time or potentiometer tuning, switch to the AM312. Because its delay is fixed and its internal ASIC handles baseline calibration almost instantly, you can delete the WARMUP_TIME_MS blocking delay from your code entirely. Just wire VCC to 3.3V, OUT to a GPIO, and GND to GND.

How to Extend for Low-Power Battery Operation

Polling digitalRead() in the loop() keeps the microcontroller awake, draining a 18650 lithium cell in days. To extend battery life to months, use hardware interrupts combined with the MCU's sleep modes. According to the official Arduino attachInterrupt() documentation, pin D2 on the Uno maps to INT0.

Replace the polling loop with an interrupt service routine (ISR):

volatile bool motionFlag = false;

void wakeUp() {
  motionFlag = true;
}

void setup() {
  // ... initialization ...
  attachInterrupt(digitalPinToInterrupt(PIR_PIN), wakeUp, RISING);
  // Enter sleep mode here using the avr/sleep.h library
}

When the PIR OUT pin goes HIGH, it triggers the ISR, wakes the Arduino, processes the event, and puts it back to sleep. For a complete guide on integrating sleep modes with PIR sensors, refer to the Adafruit PIR Sensor Guide.

Arduino PIR FAQ: Long-Tail Troubleshooting

Why does my Arduino PIR sensor trigger when no one is in the room?

Phantom triggers are almost always caused by environmental noise rather than actual motion. The most common culprits are: (1) High-frequency ripple on the 5V power rail from a cheap USB power supply, (2) An HVAC vent blowing warm air across the sensor's field of view, causing rapid thermal shifts, or (3) RF interference from a nearby WiFi router or ESP32 module rectifying inside the PIR's analog circuitry. Adding a 100µF decoupling capacitor across the sensor's VCC and GND pins resolves power-related false triggers in 90% of cases.

How do I change the HC-SR501 from repeat trigger to single trigger mode?

On the back of the HC-SR501 PCB, opposite the potentiometers, there is a 3-pin header with a jumper block.

  • H (High / Repeat Trigger): The OUT pin goes HIGH when motion is detected and stays HIGH as long as motion continues. The timer resets with every new movement. This is the default and best for lighting control.
  • L (Low / Single Trigger): The OUT pin goes HIGH on motion, stays HIGH for the exact duration set by the delay potentiometer, and then goes LOW, ignoring any motion during the lockout period. Use this for strict timed alarms.
Move the plastic jumper cap to the desired position to change the mode.

Can I power an HC-SR501 directly from the Arduino 3.3V pin?

No. The HC-SR501 has an onboard voltage regulator (typically an LM78L05 or similar LDO) designed to step down higher voltages to 5V for the internal BISS0001 chip. If you feed it 3.3V, the internal logic will not have enough headroom to operate, resulting in a dead OUT pin or erratic brownouts. If you must use a 3.3V system (like an ESP32), either power the HC-SR501 from a separate 5V rail and use a logic level shifter on the OUT pin, or switch to the AM312 module which natively supports 2.7V to 12V input and outputs 3.3V logic.

What is the warm-up time for a PIR sensor and how do I code for it?

When a PIR sensor first receives power, the pyroelectric material and the internal op-amp circuitry need time to stabilize their thermal and electrical baselines. For the HC-SR501, this takes 30 to 60 seconds. If you read the OUT pin during this window, it will output erratic HIGH/LOW signals. In your Arduino code, implement a blocking delay(45000); in the setup() function, or use a non-blocking millis() timer to ignore all sensor inputs for the first 45 seconds after boot. The AM312 requires only 2 to 3 seconds of warm-up time.