In short: A PIR sensor outputs a clean digital HIGH when it detects moving infrared heat, so you wire its OUT pin to an Arduino interrupt pin and let a hardware ISR latch the event the instant it fires — no polling, no missed triggers, and almost zero CPU cost.
Passive infrared (PIR) motion sensors are the workhorse behind automatic lights, intruder alarms and occupancy-based power saving. They are "passive" because, unlike an ultrasonic or radar module, they emit nothing — they only listen for changes in the 8–14 µm long-wave infrared that warm bodies radiate. Pairing one with an Arduino is one of the cleanest embedded exercises you can do, and it teaches a concept that matters far beyond hobby projects: event-driven sensing with hardware interrupts.
How a PIR sensor actually works
Inside the familiar white dome is a pyroelectric sensor — usually a dual-element device such as the RE200B — paired with a signal-conditioning IC like the BISS0001. The two pyroelectric elements are wired in a differential (opposing) configuration. When a uniform heat source fills the field of view, both elements see the same change and cancel out. When a person moves across the field, the warm patch sweeps from one element to the other, producing a positive-then-negative differential pulse. That differential trick is why PIR responds to motion, not to a static warm object.
The white dome is a moulded Fresnel lens. It carves the hemisphere into dozens of discrete "beams," so a moving target repeatedly crosses the boundary between illuminated and dark zones, amplifying the differential signal. A typical HC-SR501 module covers roughly a 110° cone out to about 7 metres.
The BISS0001 does all the analog heavy lifting. By the time the signal reaches the Arduino it is already a clean 3.3 V logic level — no ADC, no op-amp, no threshold tuning in code. The MCU only has to answer one question: did the edge go HIGH?
Wiring the HC-SR501 to an Arduino
The HC-SR501 has three pins: VCC, OUT and GND. It is forgiving — it accepts 4.5–20 V on VCC thanks to an onboard regulator, so the Arduino's 5 V rail is fine. Critically, the OUT pin idles LOW and pulses to about 3.3 V on detection, which an Arduino reads reliably as HIGH.
| HC-SR501 pin | Arduino Uno pin | Note |
|---|---|---|
| VCC | 5V | Onboard LDO tolerates 4.5–20V |
| OUT | D2 | D2/D3 are the Uno's interrupt pins |
| GND | GND | Common ground is mandatory |
Pick D2 or D3 on an Uno — those are the only pins mapped to external interrupts INT0 and INT1. On a Mega or a Leonardo/Micro the interrupt-capable pins differ, which is exactly why Arduino gives you digitalPinToInterrupt() rather than making you hardcode a number.
Why interrupts beat polling
You could read the pin in a loop with digitalRead(). But a loop that is busy writing to an LCD, driving a buzzer or talking over serial might not sample the pin until milliseconds later, and a very short trigger could slip past. An interrupt flips that: the hardware watches the pin in silicon and vectors the CPU into your handler the microsecond the edge arrives, regardless of what loop() is doing. On the ATmega328P the interrupt latency is only a handful of clock cycles — well under a microsecond at 16 MHz.
Interrupt-based detection code
The golden rule of an ISR: keep it short and touch only volatile variables. Do not call delay(), Serial.print() or anything blocking inside it — those rely on interrupts that are masked while your handler runs. Instead, set a flag and act on it back in loop().
const byte PIR_PIN = 2;
const byte LED_PIN = 13;
volatile bool motionFlag = false;
void motionISR() { // runs on the rising edge
motionFlag = true; // keep it to a single volatile write
}
void setup() {
pinMode(PIR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(PIR_PIN), motionISR, RISING);
}
void loop() {
if (motionFlag) {
noInterrupts();
motionFlag = false; // clear atomically
interrupts();
Serial.println("Motion detected!");
digitalWrite(LED_PIN, HIGH);
delay(5000); // hold the light/alarm 5 s
digitalWrite(LED_PIN, LOW);
}
}
Declaring the flag volatile is not optional. It tells the compiler the variable can change outside normal program flow, so it must re-read it from RAM every time instead of caching it in a register — a cache that the ISR would never update.
Swap the LED for a relay or a MOSFET and the same sketch drives a 230 V lamp or a 12 V siren. If you need to distinguish approach from retreat, or measure exact distance, a PIR can't do it — reach for a ranging sensor instead, as covered in our ultrasonic sensor with Arduino guide. For battery projects, see our notes on putting the Arduino to sleep and waking it directly from the PIR interrupt.
Tuning the two onboard potentiometers
The HC-SR501 exposes two trimmers. Sensitivity sets detection range (roughly 3–7 m). Time delay (Tx) sets how long OUT stays HIGH after a trigger, from about 3 s to 300 s. There is also a jumper for trigger mode: non-repeatable (L) drops OUT after the timeout even if motion continues, while repeatable (H) keeps extending the HIGH as long as motion persists — almost always the mode you want for lighting.
One field gotcha: give the module 30–60 seconds to stabilise after power-up. The pyroelectric front end needs to settle to ambient, and reading it immediately yields false triggers. Also keep it away from HVAC vents and direct sunlight, both of which create the fast thermal gradients PIR is designed to catch.
From hobby sensor to real silicon
The BISS0001 is a fixed-function ASIC, but the exact pyroelectric-to-decision pipeline — differential front end, bandpass filter, window comparator, debounce timer — is what analog and mixed-signal engineers design from scratch in professional flows. Teams model that chain in Cadence Virtuoso and Synopsys Custom Compiler, verify it with Spectre, and sign off physical rules in Siemens Calibre. The humble PIR is a surprisingly good gateway into understanding why a sensor front end needs careful analog conditioning before any digital logic sees it. If you want that depth, VLSI design-service teams such as Avecas build exactly these mixed-signal blocks for production ASICs.
Ready to go from blinking an LED on motion to designing the sensor interfaces inside real products? Explore the CourseTron Embedded Systems & VLSI courses and build hardware you can put on a résumé.
Frequently asked questions
Why does my PIR trigger randomly with nothing moving?
The three usual causes are: you read it before the 60-second warm-up, it faces a window or heater creating thermal drafts, or VCC is noisy. Add a short delay after boot, re-aim the dome, and decouple VCC with a 10 µF capacitor to ground.
Can I connect the OUT pin directly to the Arduino?
Yes. The HC-SR501 drives a clean 3.3 V logic HIGH that the 5 V Arduino reads as HIGH, and no current-limiting is needed because it is a logic output, not a raw sensor line. A common ground is the only hard requirement.
Should I use RISING, FALLING or CHANGE for the interrupt?
Use RISING to catch the moment motion starts, which is what alarms and lighting want. Use CHANGE only if you also need to know the instant the trigger clears, e.g. to measure how long someone lingered.
How is a PIR different from a microwave or ultrasonic sensor?
PIR is passive and detects heat movement; it cannot see through glass or measure distance. Ultrasonic and microwave sensors emit energy and measure the echo, so they give range and can detect very slow or stationary objects a PIR would miss.