HC-SR501 PIR Motion Sensor Tutorial: Reliable Motion Detection, Sensitivity & Delay Tuning, Repeat/Non-Repeat Modes, and Arduino Alarm Automation
This tutorial is a detailed, practical guide to using the HC-SR501 Adjustable Pyroelectric Infrared (PIR) Motion Sensor Module (Leobot Product #1616) for motion-triggered projects. You will learn what PIR sensors actually detect (changes in IR heat patterns), how to wire it correctly, how to tune the sensitivity (range) and delay time, how to use the jumper for repeatable (retrigger) vs non-repeatable modes, and how to write robust Arduino code that avoids false triggers and noisy outputs.
1) What PIR sensors detect (and why they false-trigger)
A PIR sensor detects motion by measuring changes in infrared (IR) energy across two sensing elements. When a warm object (human/animal) moves across the Fresnel lens zones, the IR pattern changes and the module outputs a trigger.
Why false triggers happen
- Rapid temperature changes: heaters turning on, hot air blasts, sunlight shifting.
- Moving warm air: aircon vents, fans, open windows.
- Electrical noise: long wires, weak power, relay/motor switching nearby.
- Bad placement: lens facing direct sun or reflecting surfaces.
2) Key specs you should actually care about
Specs vary slightly by vendor/clone, but typical HC-SR501 characteristics are:
- Supply voltage: ~4.5–20VDC
- Output level: typically ~3.3V HIGH (logic signal)
- Detection distance: adjustable, typically ~3–7 meters
- Delay time: adjustable, typically ~0.5–200 seconds
- Modes: repeatable (retrigger) / non-repeatable
- Detection angle: typically around a ~100° cone
3) Pinout and wiring (Arduino / ESP32)
HC-SR501 modules commonly expose 3 pins:
- VCC: power input (often labeled VCC, +, or “+5V” on some PCBs)
- OUT: digital output (HIGH when motion is detected)
- GND: ground
3.1 Wiring to Arduino UNO/Nano
- VCC ? 5V (or VIN if your board expects higher input and you know what you’re doing)
- GND ? GND
- OUT ? D2 (or any digital input)
3.2 Wiring to ESP32 / ESP8266
- VCC ? 5V (many dev boards provide 5V from USB) or a suitable supply within module range
- GND ? GND
- OUT ? a GPIO input (reads HIGH at ~3.3V typically)
4) Adjustments: sensitivity + delay + mode jumper
4.1 Sensitivity / range potentiometer
- Controls how easily motion triggers the output (effectively range / threshold).
- Start mid-range, then adjust in small steps.
- If you get false triggers, reduce sensitivity before doing anything else.
4.2 Delay time potentiometer
- Controls how long OUT stays HIGH after motion is detected.
- Typical adjustable range is very wide (~0.5–200 seconds).
- Short delays are better for “event” systems; longer delays are better for “keep light on while moving”.
4.3 Mode jumper: repeatable vs non-repeatable
- Repeatable (retrigger): if motion continues, the timer extends (OUT stays HIGH longer).
- Non-repeatable: once triggered, it holds HIGH for the delay, then must go LOW before triggering again.
5) Warm-up time and stabilization (non-negotiable)
PIR modules typically need a short stabilization period after power-up. During this time, they may output random triggers while their internal reference settles.
- Plan for a warm-up delay in firmware (often 10–60 seconds in real projects).
- Do not treat “first seconds after boot” as valid security data.
6) Placement & lens physics: coverage, blind spots, and mounting height
- Mount height: higher generally increases coverage, but too high can reduce sensitivity to small motion.
- Angle: aim across likely motion paths, not directly at a doorway where motion is “toward” the sensor.
- Across vs toward: PIR tends to trigger better when a person moves across lens zones rather than straight toward it.
- Avoid heat sources: don’t point at heaters, sunny windows, or active vents.
7) Arduino Example 1: basic motion detect + LED
This is the simplest “does it work?” example. OUT goes HIGH when motion is detected.
/*
HC-SR501 PIR Motion Sensor - Basic Test
Wiring:
PIR VCC -> 5V
PIR GND -> GND
PIR OUT -> D2
Behavior:
- Turns LED on while motion output is HIGH.
*/
const int PIN_PIR = 2;
const int PIN_LED = 13;
void setup() {
pinMode(PIN_PIR, INPUT);
pinMode(PIN_LED, OUTPUT);
}
void loop() {
int motion = digitalRead(PIN_PIR);
digitalWrite(PIN_LED, motion ? HIGH : LOW);
}
8) Arduino Example 2: robust motion events (debounce + hold + cooldown)
Real builds benefit from “event shaping”:
- Warm-up ignore window
- Minimum HIGH duration before accepting a trigger (reduces noise spikes)
- Cooldown so you don’t spam events
/*
HC-SR501 PIR Motion Sensor - Robust Event Handling
- Warm-up ignore: ignore triggers for WARMUP_MS after boot
- Debounce: require PIR to be HIGH continuously for CONFIRM_MS
- Hold: treat motion as "active" for HOLD_MS after a confirmed trigger
- Cooldown: minimum gap between confirmed events
Wiring:
PIR OUT -> D2
*/
const int PIN_PIR = 2;
const int PIN_LED = 13;
const unsigned long WARMUP_MS = 30UL * 1000UL; // 30s ignore after boot
const unsigned long CONFIRM_MS = 150UL; // require 150ms stable HIGH
const unsigned long HOLD_MS = 10UL * 1000UL; // keep "motion active" for 10s
const unsigned long COOLDOWN_MS= 2UL * 1000UL; // 2s between confirmed events
unsigned long bootMs;
unsigned long highStartMs = 0;
unsigned long motionUntilMs = 0;
unsigned long lastEventMs = 0;
void setup() {
pinMode(PIN_PIR, INPUT);
pinMode(PIN_LED, OUTPUT);
bootMs = millis();
}
void loop() {
unsigned long now = millis();
bool inWarmup = (now - bootMs) < WARMUP_MS;
bool pirHigh = (digitalRead(PIN_PIR) == HIGH);
if (!inWarmup) {
if (pirHigh) {
if (highStartMs == 0) highStartMs = now;
bool confirmed = (now - highStartMs) >= CONFIRM_MS;
bool cooldownDone = (now - lastEventMs) >= COOLDOWN_MS;
if (confirmed && cooldownDone) {
lastEventMs = now;
motionUntilMs = now + HOLD_MS;
// You can log / trigger actions here
}
} else {
highStartMs = 0;
}
}
bool motionActive = (!inWarmup) && (now < motionUntilMs);
digitalWrite(PIN_LED, motionActive ? HIGH : LOW);
}
9) Arduino Example 3: alarm/relay trigger with arming + siren timer
This pattern is extremely common: arm the system, then if motion occurs, trigger an alarm for a fixed time. (For a real siren/relay, use a proper relay or MOSFET stage with flyback protection.)
/*
HC-SR501 PIR Motion Sensor - Simple Alarm Logic
States:
- Arming delay (exit time)
- Armed
- Alarm active for ALARM_MS, then re-arm after RESET_MS
Wiring:
PIR OUT -> D2
ALARM OUT (buzzer/relay control) -> D8 (through proper driver if needed)
*/
const int PIN_PIR = 2;
const int PIN_ALARM = 8;
const unsigned long ARM_DELAY_MS = 30UL * 1000UL;
const unsigned long ALARM_MS = 20UL * 1000UL;
const unsigned long RESET_MS = 10UL * 1000UL;
enum State { ARMING, ARMED, ALARMING, RESETTING };
State st = ARMING;
unsigned long stateMs = 0;
void setState(State s) {
st = s;
stateMs = millis();
}
void setup() {
pinMode(PIN_PIR, INPUT);
pinMode(PIN_ALARM, OUTPUT);
digitalWrite(PIN_ALARM, LOW);
setState(ARMING);
}
void loop() {
unsigned long now = millis();
bool motion = (digitalRead(PIN_PIR) == HIGH);
switch (st) {
case ARMING:
digitalWrite(PIN_ALARM, LOW);
if (now - stateMs >= ARM_DELAY_MS) setState(ARMED);
break;
case ARMED:
digitalWrite(PIN_ALARM, LOW);
if (motion) setState(ALARMING);
break;
case ALARMING:
digitalWrite(PIN_ALARM, HIGH);
if (now - stateMs >= ALARM_MS) setState(RESETTING);
break;
case RESETTING:
digitalWrite(PIN_ALARM, LOW);
if (now - stateMs >= RESET_MS) setState(ARMED);
break;
}
}
10) Common mistakes
- Mistake: Treating PIR as “presence” instead of “motion”.
Fix: Use PIR as a trigger; add other sensors if you need continuous occupancy. - Mistake: Testing immediately on power-up and concluding it’s “random”.
Fix: Add a warm-up ignore period and/or arming delay. - Mistake: Pointing it at a sunny window or heater/vent.
Fix: Relocate/angle sensor away from thermal noise sources. - Mistake: Long wiring next to relay/motor mains lines.
Fix: Separate routing, add decoupling, and improve wiring discipline. - Mistake: Expecting perfect, zero-false-trigger behavior.
Fix: Add filtering logic (confirm time, cooldown, and event shaping).
11) Troubleshooting (random triggers, no triggers, short range)
Random triggers
- Cause: warm-up/stabilization. Fix: ignore first 30–60 seconds; use arming delay.
- Cause: thermal noise (sun/heater/vent). Fix: reposition; reduce sensitivity.
- Cause: electrical noise / long wires. Fix: add decoupling cap; shorten OUT wire; keep away from switching loads.
No triggers / too insensitive
- Cause: sensitivity pot too low. Fix: increase sensitivity gradually.
- Cause: placement wrong (motion toward sensor). Fix: orient so motion crosses lens zones.
- Cause: power issue. Fix: verify stable VCC/GND connections.
Output stays HIGH “too long”
- Cause: delay pot set high (can be up to ~200s). Fix: reduce delay pot.
- Cause: repeatable mode retriggering. Fix: switch to non-repeatable mode or handle it in firmware.
12) Quick checklist
HC-SR501 PIR Motion Sensor Checklist
-----------------------------------
Wired correctly: VCC, GND, OUT (common ground to MCU)
Warm-up / arming delay implemented (ignore early random triggers)
Sensitivity tuned to environment (reduce first if false triggers)
Delay tuned to use-case (short for events, longer for lights)
Mode jumper set intentionally: repeatable vs non-repeatable
Sensor placed away from sunlight/heaters/vents; aimed across motion paths
If needed: software filtering (confirm time + cooldown + hold)