What you will learn
- Distinguish the HC-SR501's 5 V supply from its output compatible with 3.3 V logic.
- Connect the PIR output to GPIO27 and an LED to GPIO25 through a 220 Ω resistor.
- Explain the 60-second startup wait and the additional five-second light hold.
- Track the latest active output with millis() and refresh the interval without a long blocking delay.
- Distinguish module timing, software holding time and the limitations of motion detection.
Before you start
Complete courses 04 and 07: timing with millis() and reading a sensor module's digital output. You should also be able to wire an LED with a series resistor.
Reference setup
- Board in Arduino IDE
- ESP32 Dev Module
- Arduino-ESP32
- 3.3.12
- Serial Monitor
- 115200 baud
This is a reference profile, not an identification of the pictured board. Adapt the GPIO mapping and verify the circuit before using ESP32-C3/S2/S3 or a differently labelled board.
What we will build
We will build a model of a motion-operated light. An HC-SR501 PIR module sends a digital signal to the ESP32, and our program turns on an LED. When the module stops reporting an active signal, the LED stays on for another five seconds. A new active signal extends that interval.
The program keeps the LED off for the first 60 seconds while the sensor settles. The exercise shows how the sensor, its timing circuit and our software combine to determine the result.
What you will learn
- Distinguish a sensor's supply voltage from its output voltage.
- Connect an HC-SR501 to an ESP32 digital input.
- Implement a startup waiting period without a long
delay()call. - Extend the light's on-time while an active signal is present.
- Distinguish motion detection from reliable presence detection.
Prerequisites
Complete courses 04 and 07: timing with millis() and reading a digital sensor output. You should know LED wiring and identify GPIO, GND, 3V3 and USB-derived 5 V.
Required equipment
| Component | Quantity | Specification and notes |
|---|---|---|
| Development board with a classic ESP32-WROOM-32 module | 1 | Match the reference GPIO labels; check physical header positions on the actual board. |
| USB data and power cable | 1 | Use the connector fitted to your board; the pictured kit lists Micro-USB. |
| Solderless breadboard | 1 | The kit lists 830 tie points. Check whether the power rails are split. |
| HC-SR501 PIR motion sensor | 1 | Module supplied from the board USB 5 V rail; OUT is 3.3 V. Verify pin order, H/L jumper and output pulse duration. |
| Red LED | 1 | Anode A and cathode K; identify polarity on the actual part. |
| 220 Ω resistor | 1 | One current-limiting resistor per LED branch; use the kit’s 220 Ω resistors. |
| Jumper wires | 6 | Use male-to-male or female-to-male leads to suit the board headers. Approximate quantity, depending on layout and connector types. |
Use a classic ESP32-WROOM-32. The reference HC-SR501 uses a 5 V supply and outputs approximately 3.3 V when active; see the SunFounder specification. Check your module and board documentation. A photograph's product name does not establish every clone's pin order or specifications.
What a PIR sensor detects
A PIR is a passive infrared sensor. It responds to changes in infrared radiation within its field of view. A person walking past can trigger it, but a person remaining still may not maintain detection. One signal cannot identify people, count them or measure their distance. The SunFounder documentation explains the module's sensing principle.
The HC-SR501 already has its own output holding time and sensitivity adjustment. For this demonstration, select retriggering mode H and the minimum delay according to your module's markings. In mode H, new motion can extend HIGH; mode L handles retriggering differently. Our five-second interval therefore does not mean five seconds from the last physical movement.
Wiring and assembly
Wiring diagram
Power the HC-SR501 from the board’s verified USB 5 V rail; its OUT uses 3.3 V logic. USB5V is a functional label: the actual pin may be marked 5V or VIN and must be checked against the board schematic. Never connect 5 V to a GPIO.
| From | To | Connection |
|---|---|---|
ESP32.USB5V | U1.VCC | Module supply; identify the VCC terminal. |
ESP32.GND | U1.GND | Common ground for the board and sensor. |
U1.OUT | ESP32.GPIO27 | 3.3 V logic signal to GPIO27. |
ESP32.GPIO25 | R1.1 | Digital output for the LED. |
R1.2 | D1.A | R1, 220 Ω, in series with the LED anode. |
D1.K | ESP32.GND | Cathode to common ground. |
- Disconnect USB and identify VCC, OUT and GND on the module. Do not copy another version's physical pin order.
- Connect module GND to ESP32 GND.
- Connect module VCC to the board terminal documented to provide USB-derived 5 V. A
VINlabel on an unfamiliar board is not sufficient confirmation. - Connect OUT to GPIO27. The actual output must be compatible with 3.3 V logic; never apply 5 V to a GPIO.
- Connect GPIO25 through R1, a 220 Ω resistor, to the anode of D1. Connect the LED cathode to GND.
- Inspect the wiring, then power the board through USB only. Do not add a separate external 5 V supply alongside USB in this exercise.
We configure GPIO27 as INPUT: the sensor drives its level. The 5 V terminal powers the module; it does not define a voltage the ESP32 input can accept.
Arduino setup
In Arduino IDE 2.x, use esp32 by Espressif Systems 3.3.12, select ESP32 Dev Module and choose the correct port. The Espressif installation guide explains adding the board package. No additional libraries are needed.
Extract the Arduino example and open pir_light/pir_light.ino. Keep the folder name matched to the sketch name. Confirm GPIO27 for the PIR and GPIO25 for the LED, then set Serial Monitor to 115200 baud.
Complete Arduino sketch
#include <Arduino.h>
// HC-SR501: USB-derived 5 V to VCC, common GND, verified 3.3 V OUT to GPIO27.
constexpr uint8_t PIR_PIN = 27;
constexpr uint8_t LED_PIN = 25;
constexpr uint32_t WARMUP_MS = 60000;
constexpr uint32_t HOLD_MS = 5000;
constexpr uint32_t REPORT_MS = 500;
uint32_t startedAt = 0;
uint32_t lastHighAt = 0;
uint32_t lastReportAt = 0;
bool warmupComplete = false;
bool lampOn = false;
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
startedAt = millis();
lastReportAt = startedAt;
Serial.println("PIR warm-up: LED remains OFF for 60 seconds.");
}
void loop() {
const uint32_t now = millis();
const int raw = digitalRead(PIR_PIN);
if (!warmupComplete && static_cast<uint32_t>(now - startedAt) >= WARMUP_MS) {
warmupComplete = true;
Serial.println("PIR monitoring started.");
}
if (!warmupComplete) {
lampOn = false;
} else if (raw == HIGH) {
lastHighAt = now;
lampOn = true;
} else if (lampOn && static_cast<uint32_t>(now - lastHighAt) >= HOLD_MS) {
lampOn = false;
}
digitalWrite(LED_PIN, lampOn ? HIGH : LOW);
if (static_cast<uint32_t>(now - lastReportAt) >= REPORT_MS) {
lastReportAt = now;
Serial.print("state="); Serial.print(warmupComplete ? "MONITORING" : "WARMUP");
Serial.print(" raw="); Serial.print(raw);
Serial.print(" LED="); Serial.println(lampOn ? "ON" : "OFF");
}
}
Start with the original program. The constants WARMUP_MS = 60000 and HOLD_MS = 5000 establish a baseline that you can check.
How the timing works
During startup waiting, the LED stays off regardless of OUT. Afterwards, every observed HIGH turns on the LED and refreshes lastHighAt. While the input is LOW, the program compares the current time with the last recorded HIGH. It switches the LED off once at least HOLD_MS has elapsed.
Timing uses subtraction of uint32_t timestamps rather than comparison with a future absolute timestamp. For these short intervals, this handles the millis() counter wrapping around. Once the initial waiting period has completed, it stays completed; a later counter wrap does not restart it. The Arduino millis() reference describes its return value and rollover.
Running the experiment
Click Verify, then Upload. Open Serial Monitor and reset the ESP32. Wait 60 seconds with the sensor held still. This is the waiting interval chosen for our exercise, not a promise that every module will be fully settled at that exact moment.
After waiting, walk in front of the sensor. Observe state, raw and LED in reports arriving approximately every 500 ms. MONITORING indicates normal operation. Move away and wait for OUT to become LOW, then observe the additional software hold. Reports are less frequent than input checks, so their timing does not locate every transition precisely.
Observation table
| Step | Expected behaviour | Record |
|---|---|---|
| Reset and first 60 s | LED stays off even if OUT becomes active. | End of the waiting period. |
| Movement after waiting | HIGH turns the LED on. | First observed active output. |
OUT remains HIGH | The LED does not begin counting down to switch-off. | Module output duration. |
OUT returns to LOW | LED turns off about five seconds after the last observed HIGH. | Transition and switch-off times. |
Another HIGH before switch-off | The five-second interval starts again. | Difference from the previous attempt. |
These are expected outcomes, not measurements already taken. Enter your own observations while working with the board.
Troubleshooting
| Symptom | Check and action |
|---|---|
| LED does not react immediately after reset | Wait until the 60-second startup interval ends. |
| LED stays on for a long time | Check OUT first; the module may still hold it HIGH. Reduce its delay according to its documentation. |
| No detection | Check common GND, GPIO27, the correct USB-derived 5 V supply and sensor placement. |
| Triggers without deliberate movement | Secure the module, let it settle and move it away from changing heat sources. |
| OUT changes but the LED does not work | Check GPIO25, polarity and resistor; repeat the LED example in course 02. |
Independent challenge
Change HOLD_MS to 10000 and repeat the same experiment. Keep the startup wait. Then calculate this scenario: after settling, the final observed HIGH occurs 78 seconds after startup, and no further HIGH follows. When should the light turn off with the original value and with the modified value?
Check your understanding
- Why does a 5 V supply not mean that OUT must also be 5 V?
- What happens if the module continuously outputs
HIGH? - Can an unlit LED prove that nobody is in the room?
Worked guidance and answers
For a final HIGH at 78 seconds, the original program turns the LED off at approximately 83 seconds; the modified program does so at 88 seconds. Switch-off occurs on the first loop iteration after the interval expires. Any later HIGH moves that moment forward.
Supply and output voltage are separate module specifications. Continuous HIGH repeatedly refreshes the timestamp and keeps the light on. An unlit LED means our timer has expired without another active output; it does not establish that the room is empty. Restore HOLD_MS to 5000 before continuing.
