The HC-SR501 PIR motion sensor module outputs a 3.3V digital HIGH signal when it detects moving infrared heat signatures within its 120-degree cone. Despite being commonly powered by 5V, the onboard voltage regulator drops the logic output to 3.3V, making it natively compatible with ESP32 and Raspberry Pi GPIO pins without level shifters. To use it effectively, you must account for its 30-second boot calibration window, tune its dual potentiometers for your specific room geometry, and use non-blocking polling to track physical occupancy duration.
How the HC-SR501 PIR Motion Sensor Module Detects Heat
The core of the module is a pyroelectric sensor (typically an RE200B or clone) housed under a multifaceted plastic Fresnel lens. Pyroelectric materials generate a temporary surface voltage when their temperature changes. The sensor contains two dual-element slots wired in opposition; this cancels out ambient room temperature shifts but creates a differential AC voltage spike when a warm body moves across the lens facets, alternately exposing the two elements to infrared radiation.
This tiny analog signal (in the microvolt range) is fed into the module's onboard BISS0001 signal conditioning chip. The BISS0001 applies a two-stage operational amplifier to boost the signal, passes it through a bandpass filter tuned to human walking frequencies (roughly 0.3Hz to 10Hz), and feeds it into a voltage comparator. When the amplified signal crosses the internal reference threshold, the chip drives the OUT pin HIGH, translating the physical heat differential into a clean digital logic state.
Wiring Guide and the 3.3V Output Gotcha
The most common bench mistake with the HC-SR501 is assuming a 5V power supply yields a 5V logic output. The module includes an onboard LDO (Low Dropout) regulator—usually an HT7133 or equivalent—that steps the input voltage down to 3.3V specifically to power the BISS0001 chip. Consequently, the OUT pin swings to approximately 3.3V when HIGH, regardless of whether you supply 5V, 9V, or 12V to the VCC pin.
| Pin | Function | Electrical Characteristics | ESP32 / 3.3V MCU Connection |
|---|---|---|---|
| VCC | Power Supply | 4.5V to 20V DC (5V recommended) | Connect to ESP32 VIN (5V) or external 5V rail |
| OUT | Digital Output | HIGH: ~3.3V / LOW: 0V | Connect to any standard GPIO (e.g., GPIO 14) |
| GND | Ground | System Common | Connect to ESP32 GND |
Signal Math: Translating Digital Pulses to Occupancy Time
Because the BISS0001 handles the analog-to-digital conversion internally, your microcontroller only receives a binary raw reading. To extract a meaningful physical unit—Occupancy Duration (Seconds)—you must map the digital pulse width to time using timestamp math rather than the blocking pulseIn() function.
Internal Analog-to-Digital Threshold Math:
The BISS0001 triggers a digital HIGH when the amplified pyroelectric voltage ($V_{amp}$) exceeds the internal reference voltage ($V_{ref}$).
V_amp = V_pyro × Gain (approx. 70dB or 3162x)
If V_amp > V_ref, then OUT = 3.3V (HIGH).
The sensitivity potentiometer adjusts $V_{ref}$, effectively changing the microvolt threshold required to trip the comparator.
External Raw-to-Unit Math (Microcontroller side):
To calculate the physical duration of motion in seconds ($T_{occ}$), track the state changes using millis():
T_occ = (Timestamp_LOW - Timestamp_HIGH) / 1000.0
If the sensor is set to 'Repeatable Trigger' (H mode), the OUT pin will stay HIGH as long as motion continues, resetting the internal timer with every new thermal detection. This allows you to accurately measure continuous room occupancy without complex state machines.
Calibration, Interference, and the BISS0001 Chip
Out of the box, the HC-SR501 is rarely tuned for your specific environment. You must adjust three physical interfaces on the board:
- Time Delay Potentiometer: Adjusts how long the OUT pin stays HIGH after motion ceases. Range: ~0.3 seconds (fully counter-clockwise) to ~15 seconds (fully clockwise). For room lighting, set this to roughly 5 seconds.
- Sensitivity Potentiometer: Adjusts the internal $V_{ref}$ threshold and detection range. Range: ~3 meters (counter-clockwise) to ~7 meters (clockwise). Turn this down if your sensor triggers from pets or distant hallway traffic.
- Trigger Mode Jumper:
- H (Repeatable): The output stays HIGH as long as motion is continuously detected. The timer resets with every new movement. (Use this for 95% of applications).
- L (Non-Repeatable): The output goes HIGH for the set delay time, then goes LOW and ignores all motion for a 'blocking time' (approx. 2.5 seconds) before it can trigger again.
Common Interference Sources
The HC-SR501 is notoriously susceptible to two types of environmental noise:
- RF Interference: The BISS0001's high-impedance amplifier acts as an antenna. Placing an ESP32 or WiFi router within 10cm of the sensor will cause the RF emissions to induce microvolt spikes in the traces, triggering false positives. Fix: Keep WiFi antennas at least 15cm away from the PIR dome.
- Thermal Drafts: HVAC vents, space heaters, or direct sunlight moving across the Fresnel lens will trigger the pyroelectric element. Fix: Aim the sensor away from windows and AC registers, and use the sensitivity pot to reduce the detection cone.
Decision Tree: Which Motion Sensor Should You Actually Buy?
Do not default to the HC-SR501 for every project. Use this decision matrix to select the correct module for your physical constraints.
| Application Constraint | HC-SR501 (PIR) | RCWL-0516 (Microwave Radar) | AM312 (Mini PIR) |
|---|---|---|---|
| Needs to see through drywall/plastic? | No (Blocked by walls) | Yes (Penetrates non-metals) | No |
| Strict power budget (< 1mA)? | No (~50mA quiescent) | No (~3mA quiescent) | Yes (~10µA quiescent) |
| Must ignore pets/small animals? | Yes (Tunable lens geometry) | No (Detects all movement) | No |
| Operating in extreme heat (>35°C)? | Poor (Body temp blends with ambient) | Good (Unaffected by ambient temp) | Poor |
Non-Blocking ESP32 Implementation Steps
Never use delay() or the blocking pulseIn() function when reading the HC-SR501 on an ESP32, as the sensor's HIGH pulse can last for 15+ seconds, halting your WiFi stack and causing MQTT disconnects. Follow these steps to implement a non-blocking state tracker.
- Wire the Sensor: Connect VCC to 5V, GND to GND, and OUT to GPIO 14.
- Set the Jumper: Place the jumper cap on the 'H' (Repeatable) pins.
- Account for Boot Calibration: The BISS0001 requires 30 to 60 seconds on startup to sample the ambient IR baseline. During this time, it will output erratic HIGH signals. Your code must ignore triggers during this window.
- Upload the Non-Blocking Code: Use the
millis()based logic below to track state changes and calculate physical occupancy time.
const int PIR_PIN = 14;
const unsigned long CALIBRATION_TIME = 30000; // 30s BISS0001 warmup
unsigned long bootTime;
unsigned long motionStartTime = 0;
bool isCalibrated = false;
bool lastPirState = LOW;
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
bootTime = millis();
Serial.println("HC-SR501 Calibrating... Wait 30s.");
}
void loop() {
// Handle calibration window
if (!isCalibrated) {
if (millis() - bootTime > CALIBRATION_TIME) {
isCalibrated = true;
Serial.println("Calibration complete. Tracking motion.");
}
return; // Skip reading during warmup
}
bool currentPirState = digitalRead(PIR_PIN);
// Detect Rising Edge (Motion Started)
if (currentPirState == HIGH && lastPirState == LOW) {
motionStartTime = millis();
Serial.println("[EVENT] Motion Detected");
}
// Detect Falling Edge (Motion Ended)
if (currentPirState == LOW && lastPirState == HIGH) {
unsigned long durationMs = millis() - motionStartTime;
float physicalDurationSec = durationMs / 1000.0;
Serial.printf("[DATA] Occupancy Duration: %.2f seconds\n", physicalDurationSec);
}
lastPirState = currentPirState;
// Run other non-blocking tasks (WiFi, MQTT) here
}
By mapping the digital pulse edges to millis() timestamps, you extract precise physical occupancy data while keeping the ESP32's RTOS tasks and wireless radios fully operational. For deeper integration, pair this non-blocking tracker with an MQTT publisher to feed physical room usage statistics directly into Home Assistant.






