The HC-SR501 is the white-domed PIR used in stair lights, intruder alarms and automatic washroom fans. It does the detection itself; the Nano only watches one output pin.
This sketch uses D2 with an interrupt, so the Nano notices motion even while it’s busy, and it times each motion event. That’s the starting point for a light that stays on only while someone is in the room.
Wiring
| Arduino Nano | HC-SR501 PIR motion sensor | Wire | Notes |
|---|---|---|---|
| 5V | VCC | red | |
| D2 | OUT | yellow | D2 is INT0 — one of only two external-interrupt pins on the Nano. |
| GND | GND | black |
4.5–20 V (5 V typical) part, 5 V board. Power it from 5 V (VIN/5V pin). Its output is a 3.3 V logic signal, so it connects directly to ESP32, Pico and Uno GPIOs.
Board quirks
- Only D2 (INT0) and D3 (INT1) support
attachInterrupt()on the Nano. A PIR on any other pin has to be polled. - The HC-SR501 output is 3.3 V HIGH. The ATmega328P reads anything above 3 V as HIGH at 5 V supply, so it works without a resistor.
- After power-up the PIR fires randomly for up to a minute — the sketch waits 60 s before arming.
- Two orange trimmers set sensitivity (Sx) and hold time (Tx, 3 s–5 min). Turn Tx fully anticlockwise while testing. The jumper selects single (L) or repeat (H) trigger.
Code
// HC-SR501 PIR motion sensor on an Arduino Nano, on external interrupt INT0 (D2).
// VCC → 5V, OUT → D2, GND → GND. Lights the on-board LED and logs how long motion lasted.
const uint8_t PIR_PIN = 2; // D2 = INT0, D3 = INT1: the Nano's only external-interrupt pins
volatile bool changed = false;
void onPir() { changed = true; }
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
Serial.println(F("PIR warming up for 60 s - stay out of view"));
delay(60000UL);
attachInterrupt(digitalPinToInterrupt(PIR_PIN), onPir, CHANGE);
Serial.println(F("Ready"));
}
void loop() {
static unsigned long startedAt = 0;
if (changed) {
changed = false;
bool motion = digitalRead(PIR_PIN) == HIGH;
digitalWrite(LED_BUILTIN, motion ? HIGH : LOW);
if (motion) {
startedAt = millis();
Serial.println(F("Motion detected"));
} else {
Serial.print(F("Motion ended after "));
Serial.print((millis() - startedAt) / 1000.0, 1);
Serial.println(F(" s"));
}
}
}// HC-SR501 PIR motion sensor on an Arduino Nano, on external interrupt INT0 (D2).
// VCC → 5V, OUT → D2, GND → GND. Lights the on-board LED and logs how long motion lasted.
const uint8_t PIR_PIN = 2; // D2 = INT0, D3 = INT1: the Nano's only external-interrupt pins
volatile bool changed = false;
void onPir() { changed = true; }
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(LED_BUILTIN, OUTPUT);
Serial.println(F("PIR warming up for 60 s - stay out of view"));
delay(60000UL);
attachInterrupt(digitalPinToInterrupt(PIR_PIN), onPir, CHANGE);
Serial.println(F("Ready"));
}
void loop() {
static unsigned long startedAt = 0;
if (changed) {
changed = false;
bool motion = digitalRead(PIR_PIN) == HIGH;
digitalWrite(LED_BUILTIN, motion ? HIGH : LOW);
if (motion) {
startedAt = millis();
Serial.println(F("Motion detected"));
} else {
Serial.print(F("Motion ended after "));
Serial.print((millis() - startedAt) / 1000.0, 1);
Serial.println(F(" s"));
}
}
}Expected serial output
PIR warming up for 60 s - stay out of view
Ready
Motion detected
Motion ended after 4.2 sPIR warming up for 60 s - stay out of view Ready Motion detected Motion ended after 4.2 s
Common errors
Triggers by itself
Warm-up not finished, a noisy supply, or sunlight/heaters in view. A 100 µF capacitor across VCC/GND and pointing it away from windows help.
Output stays HIGH for minutes
The Tx trimmer is turned up. Turn it anticlockwise for the 3-second minimum.