If you are building a motion-activated project, the default choice is the HC-SR501 PIR sensor paired with an Arduino Nano v3. The HC-SR501 operates at 5V, features adjustable delay and sensitivity potentiometers, and outputs a clean 3.3V/5V HIGH signal when it detects infrared heat differentials. However, out of the box, these sensors are notorious for false triggers and stuck-HIGH states due to power ripple and improper jumper configurations. This guide gives you the exact wiring, non-blocking debounce code, and a debugging framework to get your PIR sensor working reliably on the first try.
The Quick Decision: Which Motion Sensor Should You Actually Buy?
Not all motion sensors use Passive Infrared (PIR) technology, and not all PIR sensors are built for the same environment. Use this decision matrix to select the exact module for your build.
| Condition / Requirement | Module Pick | Operating Voltage | Why this pick? |
|---|---|---|---|
| Need adjustable time delay (0.3s to 300s) and sensitivity | HC-SR501 | 5V - 20V | Standard size, dual pots for tuning, built-in BISS0001 signal conditioning. |
| Need ultra-compact size, battery power, or native 3.3V logic | AM312 Mini PIR | 2.7V - 12V | No pots (fixed 3s delay), tiny footprint, ultra-low quiescent current (~13µA). |
| Need to detect motion through plastic/wood enclosures or walls | RCWL-0516 | 4V - 28V | Microwave radar (Doppler), not PIR. Sees through non-metallic materials. |
Hardware Spec Sheet & Pin Mapping (Arduino Nano v3)
This build targets the Arduino Nano v3 (ATmega328P). The Nano is preferred over the Uno R3 for permanent installations due to its smaller footprint, but the code and pin logic map 1:1 to the Uno if that is what you have on your bench.
Parts List
- Microcontroller: Arduino Nano v3 (ATmega328P, 5V/16MHz variant)
- Sensor: HC-SR501 PIR Motion Sensor Module
- Wiring: 4x Male-to-Female or Male-to-Male jumper wires (22 AWG stranded)
- Power: 5V 2A USB power supply (Avoid cheap 500mA phone chargers; see debugging section)
Pin Mapping Table
| HC-SR501 Pin | Arduino Nano Pin | Function / Notes |
|---|---|---|
| VCC (Left) | 5V | Requires stable 5V. Do not use 3.3V out. |
| OUT (Middle) | D2 | Digital HIGH on motion. Supports external interrupts. |
| GND (Right) | GND | Common ground reference. |
Step-by-Step Wiring & Physical Gotchas
Before you write a single line of code, you must configure the physical hardware on the HC-SR501 module. Skipping this is the #1 cause of project failure.
- Set the Trigger Mode Jumper: Look at the bottom edge of the HC-SR501 board (opposite the pins). There is a small orange or black jumper cap connecting two of three pins. Move it to the "H" (High/Repeatable) position. In 'H' mode, the output stays HIGH as long as motion is continuously detected. In 'L' (Low/Non-repeatable) mode, it toggles HIGH then LOW regardless of ongoing motion, which breaks most standard Arduino logic.
- Set the Potentiometers: Using a small Phillips screwdriver, turn the Time Delay pot (usually on the right) fully counter-clockwise. This sets the hardware delay to the minimum (~0.3 seconds). Turn the Sensitivity pot (left) to the 12 o'clock position. We will handle software debouncing in the code; minimizing hardware delay prevents the sensor from locking up the microcontroller.
- Connect Power and Signal: Wire VCC to 5V, GND to GND, and OUT to Digital Pin 2 (D2) on the Nano.
- Allow for Initialization Settling: The BISS0001 chip on the HC-SR501 requires 30 to 60 seconds on initial power-up to sample the ambient infrared background. Do not move in front of the sensor during this boot phase, or it will calibrate to your body heat and fail to trigger later.
Complete Compilable Code with Debounce Logic
This C++ code is written for the Arduino IDE (2.x or 1.8.x). It uses a non-blocking millis() timer to handle the sensor's 30-second boot calibration and implements a software debounce to filter out micro-second electrical noise spikes that the BISS0001 occasionally outputs.
/*
* PIR Sensor Arduino Setup with Calibration & Debounce
* Target Board: Arduino Nano v3 (ATmega328P)
* Sensor: HC-SR501 (Jumper set to 'H' mode)
* Pin: D2
*/
// --- PIN DEFINITIONS ---
#define PIR_PIN 2
#define LED_PIN 13 // Built-in Nano LED for visual feedback
// --- TIMING CONSTANTS ---
const unsigned long CALIBRATION_TIME = 30000; // 30s BISS0001 settling time
const unsigned long DEBOUNCE_DELAY = 250; // 250ms software debounce
// --- STATE VARIABLES ---
bool calibrationComplete = false;
int currentPirState = LOW;
int lastPirState = LOW;
unsigned long lastTriggerTime = 0;
void setup() {
// Error Handling: Initialize Serial and verify
Serial.begin(115200);
unsigned long startMillis = millis();
while (!Serial && (millis() - startMillis) < 2000) {
// Wait up to 2 seconds for Serial port to open (prevents hang on some clones)
}
if (!Serial) {
// Fallback for headless operation if Serial fails to enumerate
// Code continues safely without Serial output
} else {
Serial.println(F("[BOOT] HC-SR501 Initializing..."));
Serial.println(F("[BOOT] DO NOT MOVE in front of sensor for 30 seconds."));
}
pinMode(PIR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
// Ensure LED is off during calibration
digitalWrite(LED_PIN, LOW);
}
void loop() {
unsigned long currentMillis = millis();
// 1. Handle Boot Calibration Phase
if (!calibrationComplete) {
if (currentMillis >= CALIBRATION_TIME) {
calibrationComplete = true;
if (Serial) Serial.println(F("[READY] Calibration complete. Monitoring motion."));
}
return; // Skip motion logic during calibration
}
// 2. Read Sensor State
currentPirState = digitalRead(PIR_PIN);
// 3. Edge Detection & Debounce
if (currentPirState != lastPirState) {
// State changed, apply debounce timer
if ((currentMillis - lastTriggerTime) > DEBOUNCE_DELAY) {
lastTriggerTime = currentMillis;
if (currentPirState == HIGH) {
if (Serial) Serial.println(F("[EVENT] Motion Detected!"));
digitalWrite(LED_PIN, HIGH);
} else {
if (Serial) Serial.println(F("[EVENT] Motion Ended."));
digitalWrite(LED_PIN, LOW);
}
}
}
// Update last state for next loop iteration
lastPirState = currentPirState;
}
Debugging: "Motion Detected" Stuck HIGH
The most common failure mode in PIR projects is the serial monitor spamming [EVENT] Motion Detected! or the output pin staying HIGH continuously, even when the room is empty. If you encounter this exact behavior, run through these three ranked causes.
1. The Jumper is in 'L' Mode or Missing
If the jumper cap on the bottom of the HC-SR501 is set to 'L' (Non-repeatable trigger), the sensor outputs a HIGH pulse, then forces a LOW pulse, then ignores motion for a hardware lockout period. If your code reads the pin during the lockout, it misses the event. Worse, if the jumper cap has vibrated off entirely, the trigger mode pin floats, causing erratic HIGH/LOW oscillation. Fix: Ensure the jumper is firmly seated on the two pins closest to the diode (the 'H' position).
2. Power Supply Ripple and Brownouts
The BISS0001 analog signal conditioning chip is highly sensitive to VCC noise. If you are powering the Arduino Nano and the HC-SR501 from a cheap, unregulated 5V USB wall wart, voltage ripple will couple into the sensor's analog stage, causing the internal comparator to trip falsely. Fix: Power the setup from a high-quality 5V 2A+ supply (like an official Raspberry Pi or Samsung phone charger). If running on a breadboard, add a 100µF electrolytic capacitor across the VCC and GND rails near the sensor.
3. Thermal and RF Interference
PIR sensors detect changes in infrared radiation. A sudden draft from an HVAC vent, a space heater cycling on, or direct sunlight shifting across the Fresnel lens will trigger it. Additionally, placing a WiFi module (like an ESP8266 or ESP32) within 2 inches of the PIR dome can cause RF interference in the BISS0001 op-amps. Fix: Relocate the sensor away from heat sources and keep RF-transmitting antennas at least 5cm away from the sensor dome.
Extending and Simplifying the Build
Once the baseline circuit is stable, you can adapt the hardware to fit specific project constraints.
How to Extend: Daylight Lockout & IoT
- Add an LDR (Light Dependent Resistor): Wire a GL5528 LDR in a voltage divider to Analog Pin A0. Read the analog value in the
loop(). If the room is bright (value > 700), bypass the motion logic to save power or prevent lights from turning on during the day. - Add MQTT for Smart Home: Swap the Arduino Nano for an ESP32 DevKit v1. The HC-SR501 outputs 3.3V when powered at 5V, which is perfectly safe for the ESP32's 3.3V GPIO pins. Use the
PubSubClientlibrary to publish the motion state to a Home Assistant MQTT broker.
How to Simplify: Drop the Pots
If you find the HC-SR501 too bulky and don't care about adjusting the hardware delay, swap it for the AM312. The AM312 has no pots, no jumper, and a fixed 3-second delay. It draws microamps, making it the ultimate choice for coin-cell or LiPo battery projects. Just wire VCC, GND, and OUT, and use the exact same C++ code provided above.
For further reading on the physics of the Fresnel lens arrays used in these modules, refer to the Adafruit PIR Sensor Guide. For a deeper understanding of non-blocking timing patterns used in the debounce code, review the official Arduino BlinkWithoutDelay documentation.






