If you are building an intruder alarm, an automated lighting rig, or a wildlife camera trigger, pairing an ESP32 motion sensor setup with a Passive Infrared (PIR) module is the standard approach. But not all PIR modules play nicely with the ESP32's 3.3V logic and strict power budgets. Feeding 5V into an ESP32 GPIO will permanently brick the silicon.
The direct answer: For 90% of battery-powered, deep-sleep IoT projects, buy the AM312 Mini PIR. It operates natively at 3.3V, draws microamps, and requires no external timing components. If your project is mains-powered and you need adjustable time-delay and sensitivity potentiometers on the fly, buy the HC-SR501 (but you must step down its output voltage to protect the ESP32).
Which ESP32 Motion Sensor Should You Pick?
Do not guess which sensor to buy based on a generic tutorial. Use this decision path to lock in the right hardware for your specific power and logic constraints.
| Project Constraint | If Yes... | If No... |
|---|---|---|
| Is the ESP32 running on battery/solar with Deep Sleep? | Go to AM312 | Go to next row |
| Do you need to manually tune the delay time with a screwdriver? | Go to HC-SR501 | Go to AM312 |
| Is your enclosure smaller than 30mm x 30mm? | Go to AM312 | Go to HC-SR501 |
| Final Verdict | Default Pick: AM312 Mini PIR. It is logic-level safe, tiny, and code-friendly. | |
Hardware Spec Sheet: AM312 vs HC-SR501
Understanding the silicon inside these modules prevents wiring disasters. The HC-SR501 uses a BISS0001 processing chip, while the AM312 uses a highly integrated ASIC that handles the Fresnel lens signal filtering internally.
| Specification | AM312 Mini PIR | HC-SR501 (Standard Clone) |
|---|---|---|
| Operating Voltage | 2.7V to 12V DC | 4.5V to 20V DC (Requires 5V for reliable LDO) |
| Output Logic Level | Equals VCC (3.3V safe if powered at 3.3V) | Often 5V (Requires voltage divider for ESP32) |
| Quiescent Current | ~10 µA | ~50 µA to 1 mA (varies wildly by clone) |
| Delay Time | Fixed ~2.5 seconds | Adjustable 0.3s to 200s via potentiometer |
| Trigger Mode | Non-retriggerable | Selectable (H/L jumper) retriggerable |
| Approx. Price (2026) | $1.20 - $1.80 | $2.00 - $3.50 |
Pin Mapping and Wiring Steps
This guide targets the ESP32 DevKit V1 (featuring the ESP32-WROOM-32 module). We are using GPIO 13 for the PIR input. We avoid GPIOs 0, 2, 12, and 15 because they are strapping pins; pulling them to the wrong state during boot will cause the ESP32 to enter flash mode or fail to start.
| PIR Module Pin | ESP32 DevKit V1 Pin | Notes |
|---|---|---|
| VCC | 3V3 (AM312) / VIN (HC-SR501) | AM312 runs on 3.3V. HC-SR501 needs 5V from VIN/USB. |
| GND | GND | Common ground is mandatory. |
| OUT | GPIO 13 | HC-SR501 requires a voltage divider here (see below). |
VIN pin (5V). However, its OUT pin will output 5V when triggered. You MUST build a voltage divider using a 1kΩ and 2.2kΩ resistor between the HC-SR501 OUT pin and ESP32 GPIO 13 to drop the voltage to a safe ~3.3V. The AM312 does not require this.
- De-energize the board: Unplug the USB-C cable from your ESP32 DevKit V1 before inserting jumper wires to prevent accidental short circuits on the breadboard.
- Connect Power: Run a jumper from the ESP32
3V3pin to the AM312VCCpin. (UseVINif using HC-SR501). - Connect Ground: Run a jumper from any ESP32
GNDpin to the PIRGNDpin. - Connect Signal: Run a jumper from the PIR
OUTpin to ESP32GPIO 13. - Add Decoupling Capacitor: Insert a 100µF electrolytic capacitor across the VCC and GND rails on the breadboard, as close to the PIR sensor as possible. This prevents inrush current spikes from resetting the ESP32.
- Verify Connections: Use a multimeter in continuity mode to verify there is no short between VCC and GND before plugging in USB power.
Complete ESP32 PIR Code with Debounce and Error Handling
PIR sensors are notoriously noisy. A single insect flying near the Fresnel lens or a sudden change in ambient room temperature can cause the OUT pin to flutter between HIGH and LOW in milliseconds. This code implements a software debounce and state-change tracker to prevent your MQTT broker or serial monitor from being flooded with false events.
Target Board: ESP32 Dev Module (Arduino IDE). Ensure you have the official ESP32 Arduino Core installed.
// ESP32 Motion Sensor Debounce & State Tracker
// Target: ESP32 DevKit V1 (ESP32-WROOM-32)
#define PIN_PIR 13 // PIR OUT pin connected here
#define PIN_LED 2 // Onboard LED for visual feedback
#define DEBOUNCE_MS 250 // Ignore state changes faster than this
unsigned long lastDebounceTime = 0;
int lastMotionState = LOW;
int currentMotionState = LOW;
void setup() {
// Initialize Serial with timeout to prevent hanging if USB disconnects
Serial.begin(115200);
unsigned long serialTimeout = millis() + 2000;
while (!Serial && millis() < serialTimeout) {
delay(10);
}
if (!Serial) {
// Fallback: blink LED rapidly to indicate serial failure in headless mode
pinMode(PIN_LED, OUTPUT);
for(int i=0; i<10; i++) { digitalWrite(PIN_LED, !digitalRead(PIN_LED)); delay(50); }
} else {
Serial.println("[BOOT] ESP32 Motion Sensor initialized.");
}
pinMode(PIN_PIR, INPUT);
pinMode(PIN_LED, OUTPUT);
// Allow PIR sensor to calibrate its ambient IR baseline
Serial.println("[CAL] Calibrating sensor for 10 seconds... stand clear.");
delay(10000);
Serial.println("[READY] Monitoring for motion.");
}
void loop() {
int reading = digitalRead(PIN_PIR);
// Debounce logic: only accept state change if it holds steady
if (reading != lastMotionState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > DEBOUNCE_MS) {
// If the state actually changed after the debounce window
if (reading != currentMotionState) {
currentMotionState = reading;
if (currentMotionState == HIGH) {
digitalWrite(PIN_LED, HIGH);
Serial.println("[EVENT] Motion DETECTED");
// TODO: Trigger MQTT publish or wake camera here
} else {
digitalWrite(PIN_LED, LOW);
Serial.println("[EVENT] Motion CLEARED");
}
}
}
lastMotionState = reading;
// Feed the watchdog timer implicitly via small delay
delay(10);
}
Troubleshooting: Brownout Errors and False Triggers
When an ESP32 motion sensor project fails, it rarely fails silently. It usually panics and dumps a stack trace to the serial monitor. Here are the exact error strings you will see and how to fix them.
1. Exact Error: Brownout detector was triggered
Ranked Causes:
- USB Cable Voltage Drop (80% probability): Cheap USB cables have high AWG wire (thin), causing a voltage drop when the ESP32 WiFi radio and the PIR sensor draw current simultaneously. Fix: Use a high-quality, short USB-C data cable rated for 3A.
- PIR Inrush Current (15% probability): The PIR sensor draws a spike of current when transitioning from LOW to HIGH. Fix: Ensure the 100µF decoupling capacitor is installed across the breadboard power rails.
- Backpowering GPIO (5% probability): You wired a 5V HC-SR501 OUT pin directly to GPIO 13 without a voltage divider, backfeeding 5V into the ESP32's 3.3V rail. Fix: Add the 1k/2.2k voltage divider immediately.
2. Exact Error: Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout)
Ranked Causes:
- Blocking Code in Loop: You added a
delay(5000)or a blocking HTTP request inside theif (currentMotionState == HIGH)block, starving the FreeRTOS idle task. Fix: Use non-blockingmillis()timers or move network calls to Core 0. - I2C/SPI Bus Lockup: If you added an OLED display to the same I2C bus and a wire vibrated loose, the ESP32 will hang waiting for an ACK. Fix: Add pull-up resistors (4.7kΩ) to SDA/SCL lines.
1. Multimeter Check: Measure the voltage at the PIR VCC pin while the sensor is triggering. If it drops below 2.9V, you have a power delivery issue.
2. Oscilloscope/Logic Analyzer: Probe the OUT pin. If you see high-frequency ringing (nanosecond spikes) when motion occurs, your software debounce time (
DEBOUNCE_MS) is too low. Increase it to 500ms.3. Thermal Check: Point the sensor away from your PC monitor, router, or HVAC vents. PIR sensors detect changes in infrared heat; a router's exhaust fan will cause endless false triggers.
Extending the Build: Deep Sleep and MQTT
Once your basic circuit is stable, you will likely want to deploy this in the field. Here is how to extend or simplify the build based on your end goal.
How to Simplify (Interrupt-Only Mode)
If you only need to wake the ESP32 from deep sleep and do not care about tracking when motion stops, strip out the debounce logic and the loop() entirely. Use the ESP32's RTC GPIO to wake the chip. According to the Espressif Deep Sleep Documentation, you can map the PIR OUT pin to an RTC-capable GPIO (like GPIO 33) and use esp_sleep_enable_ext0_wakeup(). This drops your power consumption from ~80mA to roughly 15µA.
How to Extend (MQTT and Home Assistant)
To integrate this into a smart home, add the PubSubClient library. Inside the if (currentMotionState == HIGH) block, publish a retained MQTT message to homeassistant/binary_sensor/motion/state with the payload ON. When the motion clears, publish OFF. Ensure you implement a WiFi reconnection routine using WiFi.setAutoReconnect(true) so the sensor recovers automatically after a router reboot.
Final Recommendation: Stop overthinking the sensor choice. Order a 5-pack of AM312 Mini PIR modules, wire them to GPIO 13 with a 100µF capacitor, upload the debounced code above, and you will have a rock-solid motion detection node ready for Home Assistant in under 20 minutes.






