The HC-SR501 is a pyroelectric infrared (PIR) motion sensor that outputs a 3.3V to 5V HIGH signal when it detects a change in thermal radiation within its field of view. When pairing the sensor HC SR501 Arduino setups, the microcontroller simply needs to monitor a single digital input pin for state changes. However, the physical realities of the BISS0001 signal conditioning chip on the module—specifically its calibration delay, hardware lockout time, and susceptibility to power rail noise—cause most hobbyist builds to fail in practice.
This guide targets the Arduino Uno R3 (ATmega328P) and Arduino Nano v3 board variants. The provided C++ code uses non-blocking millis() logic to handle the sensor's hardware quirks without freezing your main loop. Below, you will find the exact hardware specifications, a bulletproof wiring sequence, and a diagnostic framework for the most common runtime errors.
Estimated Build Time: 15 minutes (plus 30 seconds sensor calibration)
Estimated Cost: $14.00 ($12 for Uno clone, $2 for HC-SR501)
HC-SR501 Spec Sheet & Operating Modes
Before wiring the module, you must understand the physical limits of the sensor. The HC-SR501 relies on a Fresnel lens to focus infrared light onto a dual-slot pyroelectric sensor. The chip evaluates the differential signal between the two slots; if a heat source moves across both slots sequentially, it registers as motion. Static heat sources (like a person sitting perfectly still) will not trigger the device.
| Parameter | Value / Range | Practical Notes |
|---|---|---|
| Operating Voltage | 5V - 20V DC | Logic HIGH output is VCC - 1.5V (approx 3.3V at 5V input). |
| Quiescent Current | < 50 µA | Ideal for battery-powered nodes; negligible draw on Arduino 5V rail. |
| Detection Range | 3m - 7m (Adjustable) | Max range requires the sensitivity potentiometer turned fully clockwise. |
| Detection Angle | < 120° Cone | Fresnel lens dictates the shape; outer edges are less sensitive. |
| Delay Time (Tx) | 0.3s - 250s | Adjusted via potentiometer. Time the OUT pin stays HIGH after motion stops. |
| Block Time (Ti) | ~2.5s (Fixed) | Hardware lockout. Sensor ignores ALL motion for 2.5s after OUT goes LOW. |
| Trigger Mode | H (Repeat) / L (Single) | Set via jumper cap. H resets the delay timer on continuous motion. |
According to the Adafruit PIR Sensor Guide, the Repeat Trigger (H) mode is vastly superior for lighting and security applications. In H mode, if a person walks into the room and stays there, the sensor continuously resets its internal timer, keeping the Arduino pin HIGH. In Single Trigger (L) mode, the pin will drop LOW after the delay time expires, even if the person is still dancing in front of the lens, followed by the 2.5-second blind block time.
Parts List & Pin Mapping
Keep your wire runs short. The HC-SR501 operates on high-impedance analog signals internally, and long jumper wires act as antennas for 50/60Hz mains hum and RF interference from nearby Wi-Fi routers.
Required Components
- Microcontroller: Arduino Uno R3 (or compatible ATmega328P clone)
- Sensor: HC-SR501 PIR Module (v1.2 or generic BISS0001 variant)
- Wiring: 3x 22 AWG solid-core jumper wires (Male-to-Male for breadboard, or Male-to-Female for direct connection)
- Power: High-quality 5V USB power supply (Avoid unbranded switching adapters with high output ripple)
Pin Mapping Table
| HC-SR501 Pin | Arduino Uno R3 Pin | Wire Color (Standard) | Function |
|---|---|---|---|
| VCC | 5V | Red | Power input (Do not use 3.3V pin) |
| OUT | D2 | Yellow/Orange | Digital Signal Output |
| GND | GND | Black | Common Ground |
Wiring & Assembly Steps
Follow this exact sequence to prevent accidental short circuits and ensure the BISS0001 chip initializes correctly.
- De-energize the board: Unplug the Arduino Uno from the USB cable before making connections.
- Set the Jumper Cap: Locate the 3-pin header in the bottom corner of the HC-SR501. Move the plastic jumper cap to the H position (Repeat Trigger mode). This bridges the center pin to the pin closest to the diode.
- Connect Ground: Plug the black wire from the HC-SR501 GND pin to any GND pin on the Arduino.
- Connect Power: Plug the red wire from the HC-SR501 VCC pin to the Arduino 5V pin. Never connect VCC to the 3.3V pin; the BISS0001 requires a minimum of 4.5V to operate its internal voltage regulator.
- Connect Signal: Plug the yellow wire from the HC-SR501 OUT pin to Arduino Digital Pin 2.
- Power Up and Calibrate: Plug the Arduino into USB. Wait 30 seconds. The sensor requires this time to sample the ambient thermal baseline. During this window, the OUT pin may randomly toggle HIGH/LOW. Do not read the pin in your code during this initialization phase.
Complete Arduino Code (Non-Blocking)
The following code targets the Arduino Uno R3 and Nano v3. It avoids the common beginner mistake of using delay() to handle the sensor's lockout time. Instead, it uses millis() to track state changes and enforce a software cooldown that respects the hardware's 2.5-second block time. This ensures your main loop remains free to handle other tasks, like reading buttons or updating displays.
/*
* HC-SR501 PIR Sensor Non-Blocking Implementation
* Target: Arduino Uno R3 / Nano v3 (ATmega328P)
* Author: ElectricalFlux
*/
// --- Pin Definitions ---
#define PIR_SENSOR_PIN 2
#define STATUS_LED_PIN 13 // Built-in LED on Uno/Nano
// --- Timing Constants ---
const unsigned long CALIBRATION_TIME = 30000; // 30 seconds for BISS0001 baseline
const unsigned long HARDWARE_LOCKOUT = 2500; // 2.5s sensor block time after LOW
// --- State Variables ---
int pirState = LOW; // Current state of the PIR OUT pin
unsigned long bootTime; // Timestamp of Arduino boot
unsigned long lastTriggerTime = 0; // Timestamp of last valid motion event
void setup() {
pinMode(PIR_SENSOR_PIN, INPUT);
pinMode(STATUS_LED_PIN, OUTPUT);
Serial.begin(9600);
bootTime = millis();
Serial.println("[SYS] Booting... Waiting for HC-SR501 thermal calibration.");
Serial.println("[SYS] Do not move in front of the sensor for 30 seconds.");
digitalWrite(STATUS_LED_PIN, LOW);
}
void loop() {
unsigned long currentMillis = millis();
// 1. Handle Calibration Phase
if (currentMillis - bootTime < CALIBRATION_TIME) {
// Blink LED rapidly to indicate calibration mode
if ((currentMillis / 250) % 2 == 0) {
digitalWrite(STATUS_LED_PIN, HIGH);
} else {
digitalWrite(STATUS_LED_PIN, LOW);
}
return; // Skip sensor reading until calibrated
}
// 2. Read Sensor State
int currentPirValue = digitalRead(PIR_SENSOR_PIN);
// 3. Detect State Change (LOW to HIGH)
if (currentPirValue == HIGH && pirState == LOW) {
// Check if we are still in the hardware lockout window to prevent bounce
if (currentMillis - lastTriggerTime > HARDWARE_LOCKOUT) {
pirState = HIGH;
lastTriggerTime = currentMillis;
digitalWrite(STATUS_LED_PIN, HIGH);
Serial.println("[MOTION] Intrusion detected. Timer reset.");
}
}
// 4. Detect State Change (HIGH to LOW)
else if (currentPirValue == LOW && pirState == HIGH) {
pirState = LOW;
digitalWrite(STATUS_LED_PIN, LOW);
Serial.println("[IDLE] Motion ceased. Entering 2.5s hardware lockout.");
}
// Add other non-blocking loop tasks here (e.g., MQTT polling, button debouncing)
}
This implementation relies on the digitalRead() function. Because the HC-SR501 outputs roughly 3.3V when powered at 5V, it easily crosses the ATmega328P's 3V logic HIGH threshold, making level shifters unnecessary for 5V Arduino boards. If you port this code to a 3.3V ESP32, the sensor's 3.3V output is still sufficient, but you should power the HC-SR501 VCC pin from the ESP32's VIN or 5V pin, not the 3V3 pin.
Debugging: False Triggers and "Constant HIGH" Errors
PIR sensors are notorious for failing in unpredictable ways. Because the BISS0001 amplifies micro-volt changes from the pyroelectric element, environmental noise easily masquerades as motion. Below are the exact runtime error strings you might log, along with their ranked causes.
The First 3 Things to Check When It Fails
- Calibration Timeout: Did your code attempt to read the pin before the 30-second boot delay elapsed? The sensor will output erratic signals during this window.
- Jumper Cap Position: Verify the jumper is in the H (Repeat) position. If it is in L (Single), the sensor will drop LOW while a person is still standing in front of it, confusing your logic.
- Power Supply Ripple: Measure the 5V rail with a multimeter set to AC Volts. If you read more than 30mV of AC ripple, your power supply is injecting noise directly into the sensor's high-gain op-amps.
Runtime Error: [ERR] SENSOR STUCK HIGH
If your Serial Monitor logs a continuous HIGH state and the LED never turns off, the sensor is failing to reset its internal timer.
- Cause 1 (Most Likely): Thermal Saturation. The sensor is pointed directly at a heat source (HVAC vent, incandescent bulb, or direct sunlight). The differential slots are uniformly flooded with IR, preventing the chip from detecting a "return to baseline" state.
- Cause 2: Potentiometer Over-tuning. The Time Delay potentiometer is turned fully clockwise, setting the delay to ~250 seconds. Turn it counter-clockwise to reduce the delay to a manageable 3-5 seconds.
- Cause 3: Damaged BISS0001 IC. If the module was accidentally wired to a 12V source backwards, the internal voltage regulator is fried, locking the output transistor in a conductive state.
Runtime Error: [WARN] RAPID FALSE TRIGGERS
If the sensor triggers every 5-10 seconds with no one in the room, you are experiencing environmental interference.
- Cause 1 (Most Likely): RF Interference. The HC-SR501's unshielded traces act as an antenna. If a Wi-Fi router, two-way radio, or cell phone is within 2 feet of the module, the RF envelope will induce a voltage spike in the pyroelectric element. Fix: Move the router away, or solder a 0.1µF ceramic capacitor directly across the VCC and GND pins on the sensor PCB.
- Cause 2: Air Drafts. A ceiling fan or AC vent blowing air across the Fresnel lens causes rapid localized temperature drops, which the sensor interprets as a moving cold body. Fix: Shield the sensor from direct airflow.
- Cause 3: Long Jumper Wires. Using 10cm+ dupont wires creates an inductive loop that picks up 50/60Hz mains hum from nearby wall wiring. Fix: Solder the module directly to a shielded cable or keep jumper wires under 5cm.
Extending and Simplifying the Build
Once the baseline circuit is stable, you can adapt the hardware to fit specific project constraints.
How to Extend the Build
For advanced home automation, you rarely want lights turning on during the day. You can extend this circuit by adding an LM393 Light Dependent Resistor (LDR) module. Wire the LDR's digital output to Arduino Pin 3. In your code, simply wrap the motion detection logic in an if (digitalRead(LDR_PIN) == DARK) condition. This creates a daylight-gated security light without requiring complex software scheduling.
If you are building an IoT node, swap the Arduino Uno for an ESP32 DevKit v1. The code provided above is 100% compatible with the ESP32 Arduino core. You can then use the PubSubClient library to publish the [MOTION] state changes to an MQTT broker like Mosquitto, integrating the sensor into Home Assistant or Node-RED.
How to Simplify the Build
If you are building a simple standalone alarm that doesn't need to interface with a complex software loop, you can eliminate the microcontroller entirely. The HC-SR501's OUT pin can source up to 200mA (though 10mA is safer for continuous use). You can wire the OUT pin directly to the base of a 2N2222 NPN transistor (via a 1kΩ current-limiting resistor) to drive a 5V relay or a high-power buzzer. In this simplified hardware-only approach, set the Time Delay potentiometer to your desired alarm duration, and rely entirely on the sensor's internal BISS0001 timing circuitry.






