The HC-SR501 is the default arduino motion sensor for hobbyists and prototypers because it operates natively at 5V, requires no external pull-up resistors, and outputs a clean digital HIGH (approx 3.3V to 5V depending on supply) when it detects infrared thermal changes. Unlike microwave sensors that penetrate walls, the HC-SR501 uses a pyroelectric sensor paired with a BISS0001 analog signal processing IC to detect moving heat signatures within a 120-degree cone.

However, out-of-the-box modules are notorious for false triggers and initialization quirks. This guide provides the exact wiring, a robust non-blocking C++ state machine for the Arduino Uno R3, and hardware-level debugging steps to eliminate phantom motion events.

Sensor Comparison: PIR vs. Microwave vs. Industrial

Before wiring the HC-SR501, verify it is actually the right tool for your environment. Microwave sensors will trigger through drywall, while mini PIRs sacrifice range for footprint. Here is how the standard modules compare on the bench in 2026.

Module Variant Detection Tech Max Range Quiescent Current Output Logic Typical Price (2026)
HC-SR501 v1.2 Pyroelectric (PIR) 7 Meters ~50 µA 5V HIGH / 0V LOW $1.20 - $1.80
RCWL-0516 Microwave Doppler 9 Meters (through walls) ~2.8 mA 3.3V HIGH / 0V LOW $1.50 - $2.20
AM312 Mini Pyroelectric 3 Meters ~15 µA 3.3V HIGH / 0V LOW $0.80 - $1.10
Panasonic EKMC Industrial PIR (Digital) 12 Meters ~150 µA Open-Drain / Push-Pull $10.00 - $14.00

Expert Note: If your project requires detecting motion through a wooden door or drywall, the HC-SR501 will fail. You must use the RCWL-0516 microwave sensor. Conversely, if you are building a battery-powered node, the AM312 or Panasonic EKMC draw significantly less quiescent current than the HC-SR501's onboard voltage regulator.

Parts List and Pin Mapping

This build targets the Arduino Uno R3 (ATmega328P). The code and pin mappings rely on the Uno's 5V logic and hardware interrupt capabilities on Digital Pin 2.

Required Materials:
  • Arduino Uno R3 (or Nano v3 with ATmega328P)
  • HC-SR501 PIR Motion Sensor Module (v1.2 with BISS0001 IC)
  • 3x Female-to-Male 22 AWG Jumper Wires
  • 100µF Electrolytic Capacitor (for power rail decoupling)
  • USB-C to USB-B cable (for Uno R3 programming and serial monitoring)
HC-SR501 Pin Arduino Uno R3 Pin Notes
VCC 5V Do NOT use 3.3V. The onboard LDO requires 4.5V to 20V input.
OUT Digital Pin 2 Pin 2 supports hardware interrupts (INT0) on the ATmega328P.
GND GND Must share a common ground with the Arduino.

Step-by-Step Wiring Procedure

  1. De-energize the board: Unplug the Arduino Uno R3 from your PC or wall adapter before making connections.
  2. Connect Ground: Route a black jumper from the HC-SR501 GND pin to any Arduino GND pin.
  3. Connect Power: Route a red jumper from the HC-SR501 VCC pin to the Arduino 5V pin. Do not use the 3.3V pin; the HC-SR501's internal linear regulator will brownout and cause erratic output.
  4. Connect Signal: Route a yellow/green jumper from the HC-SR501 OUT pin to Arduino Digital Pin 2.
  5. Add Decoupling: Solder or plug a 100µF electrolytic capacitor across the 5V and GND rails on your breadboard. The HC-SR501 draws current spikes when the BISS0001 chip triggers the output relay/timer, which can reset the Arduino if the USB power supply is weak.
  6. Verify Jumper Cap: Look at the bottom-left corner of the HC-SR501 PCB. Ensure the small plastic jumper cap is bridging the H pads (High-Trigger Mode / Repeatable Trigger). If it is on the L pads, the sensor will lock out during the delay window, causing missed detections.
  7. Power Up and Wait: Plug in the Arduino. Wait 30 to 60 seconds. The HC-SR501 requires an initialization period to calibrate its internal thermal baseline. If you read the OUT pin during this window, it will output erratic HIGH/LOW signals.

Complete Compilable Code (Target: Arduino Uno R3)

Beginner tutorials often use delay() or simple digitalRead() loops. This is a mistake for motion sensors because the HC-SR501's hardware delay potentiometer can hold the pin HIGH for up to 200 seconds, blocking your main loop. The code below uses a non-blocking state machine and includes a watchdog timer to detect if the sensor hardware is stuck in a fault state.

// Target Board: Arduino Uno R3 (ATmega328P)
// Sensor: HC-SR501 PIR Motion Sensor

#define PIR_PIN 2          // Hardware interrupt pin (INT0)
#define LED_PIN 13         // Onboard LED for visual feedback
#define SERIAL_BAUD 115200

// Timing constants (in milliseconds)
const unsigned long DEBOUNCE_TIME = 250;
const unsigned long STUCK_HIGH_THRESHOLD = 250000; // 250 seconds

volatile bool motionDetected = false;
unsigned long lastMotionTime = 0;
unsigned long motionStartTime = 0;
bool isCurrentlyOccupied = false;

void setup() {
  Serial.begin(SERIAL_BAUD);
  
  pinMode(PIR_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);
  
  // Attach interrupt for RISING edge (0V to 5V transition)
  attachInterrupt(digitalPinToInterrupt(PIR_PIN), motionISR, RISING);
  
  Serial.println(F("System Boot: Waiting 30s for HC-SR501 thermal calibration..."));
  
  // Non-blocking wait would be better for complex setups, but blocking is 
  // acceptable here strictly for the one-time sensor initialization.
  delay(30000); 
  Serial.println(F("Calibration complete. Monitoring motion."));
}

void loop() {
  unsigned long currentMillis = millis();
  
  // Handle ISR flag with software debouncing
  if (motionDetected && (currentMillis - lastMotionTime > DEBOUNCE_TIME)) {
    motionDetected = false;
    lastMotionTime = currentMillis;
    
    if (!isCurrentlyOccupied) {
      isCurrentlyOccupied = true;
      motionStartTime = currentMillis;
      digitalWrite(LED_PIN, HIGH);
      Serial.println(F("[EVENT] Motion Detected: Zone Occupied"));
    } else {
      // Reset the timer on continuous motion
      motionStartTime = currentMillis;
    }
  }
  
  // Check for hardware fault: Sensor stuck HIGH
  // The max hardware delay on a standard HC-SR501 is ~200s. 
  // If we see HIGH for >250s without a new interrupt, the BISS0001 is likely locked up.
  if (isCurrentlyOccupied) {
    if (digitalRead(PIR_PIN) == HIGH) {
      if (currentMillis - motionStartTime > STUCK_HIGH_THRESHOLD) {
        Serial.println(F("[ERROR] Sensor Output Stuck HIGH. Check power supply ripple or thermal interference."));
        isCurrentlyOccupied = false; // Reset state to prevent log spam
        digitalWrite(LED_PIN, LOW);
      }
    } else {
      // Pin went LOW, motion has ended
      isCurrentlyOccupied = false;
      digitalWrite(LED_PIN, LOW);
      Serial.println(F("[EVENT] Motion Cleared: Zone Vacant"));
    }
  }
  
  // Main loop is free for other tasks (WiFi, MQTT, display updates)
}

// Interrupt Service Routine (ISR) - Keep it minimal
void motionISR() {
  motionDetected = true;
}

For more details on how hardware interrupts map to specific pins on different AVR boards, refer to the official Arduino attachInterrupt() documentation.

Debugging: Compile Errors and Hardware False Triggers

When working with the HC-SR501, failures usually fall into two categories: C++ syntax errors from copying fragmented code, or phantom hardware triggers caused by environmental noise.

Software Compile Error: Function Definition Not Allowed

If you are adapting the code above and encounter this exact error string in the Arduino IDE:

error: a function-definition is not allowed here before '{' token

Ranked Causes and Fixes:

  1. Missing closing brace in setup(): You forgot the } at the end of the setup block, meaning the compiler thinks void loop() is being defined inside setup(). Add the missing brace.
  2. Nested Functions: You accidentally pasted a helper function inside the loop() block. C++ does not support nested functions. Move all custom functions outside of setup() and loop().
  3. Missing Semicolon on a Class/Struct: If you defined a custom struct or class above setup() and forgot the trailing semicolon, the compiler bleeds the definition into the next block, triggering this error.

Hardware Debugging: The First Three Things to Check for False Triggers

If your Serial Monitor shows [EVENT] Motion Detected when the room is completely empty, do not throw the sensor away. The HC-SR501 is highly susceptible to environmental noise. Check these three vectors first:

1. Power Supply Ripple and Brownouts
The BISS0001 IC is extremely sensitive to VCC fluctuations. If you are powering the Arduino and sensor from a cheap, unregulated 5V USB wall wart, voltage ripple will trigger the internal comparators. Fix: Add a 100µF to 470µF electrolytic capacitor directly across the VCC and GND pins of the sensor module, and ensure your power supply can deliver at least 500mA continuous current.

2. RF and Thermal Interference
PIR sensors detect changes in infrared radiation. If the sensor is mounted near an HVAC vent, a space heater, or even a sunlit window where shadows shift, it will trigger. Furthermore, 2.4GHz WiFi routers placed within 12 inches of the HC-SR501 can induce currents in the PCB traces that mimic PIR signals. Fix: Relocate the sensor at least 2 feet away from HVAC airflow and RF transmitters. For deep integration, read Adafruit's comprehensive guide on PIR environmental placement.

3. The "H/L" Trigger Jumper Pad
Flip the module over. There are three solder pads labeled H, (middle), and L. If the jumper is on 'L' (Non-Repeatable Trigger), the sensor outputs HIGH, then forces a LOW lockout period where it ignores all motion. If it's on 'H' (Repeatable), it stays HIGH as long as motion continues. If the jumper cap is loose or missing, the logic state floats. Fix: Solder a blob of solder across the H and middle pads for a permanent, reliable Repeatable Trigger configuration.

Extending or Simplifying the Build

Depending on your end goal, the standard Arduino Uno + HC-SR501 setup is either a stepping stone or overkill. Here is how to pivot the architecture.

Extend: IoT Integration with ESP32 and MQTT

If you need to push motion events to Home Assistant or a cloud dashboard, swap the Uno R3 for an ESP32-WROOM-32 DevKit v1. Wiring Caveat: The HC-SR501 outputs up to 5V when powered by a 5V rail. The ESP32 GPIO pins are strictly 3.3V tolerant. You must use a simple voltage divider (e.g., 2kΩ and 3.3kΩ resistors) on the OUT pin before feeding it into an ESP32 GPIO (like GPIO 4), or power the HC-SR501 directly from the ESP32's VIN pin if your USB supply is exactly 5V, which drops the sensor's OUT HIGH to a safer ~3.3V.

Simplify: Ditch the Microcontroller Entirely

If your only goal is to turn on a 12V LED strip or a closet light when someone walks in, you do not need an Arduino. The Bypass Build: Power the HC-SR501 with a standalone 5V USB power bank. Connect the OUT pin directly to the base of a 2N2222 NPN transistor via a 1kΩ current-limiting resistor. Connect the transistor's emitter to ground, and the collector to the low-side of your 12V relay coil. The HC-SR501's onboard BISS0001 timer will handle the delay, and the transistor will switch the heavy load. Total cost drops under $3.00, and you eliminate all software debugging.