Skip to content

Motion Sensors

A PIR (passive infrared) sensor notices when something warm, like a person or a pet, moves in front of it. It's what switches on automatic lights, and it's a digital input just like a button: 1 when it sees motion, 0 when it doesn't.

You'll need:

  • 1 × HC-SR501 PIR motion sensor
  • 3 × jumper wires (female-to-male are handiest)
  • The LED and resistor from the LEDs lesson
  • The buzzer from the Piezo Buzzers lesson (for the challenge)

Hardware

Everything warm gives off invisible infrared light. The white dome is a lens that focuses infrared from the room onto a sensor behind it. When the pattern of heat changes, because something warm moved, the sensor sets its output pin HIGH.

Pins

The PIR sensor's three pins, labelled A, B and C

Pin Name Connect to
A GND ESP32 GND
B OUT (output) ESP32 GPIO 33
C VCC (power) ESP32 VIN (5 V)

The sensor needs about 5 V to run, so it's powered from VIN. Its output signal is only 3.3 V though, which is safe for the ESP32's pins.

Check the labels

The pin names are printed on the circuit board, hidden under the white dome. The dome pulls straight off, so if your sensor looks different from the diagram, take a peek and put it back on afterwards.

If your sensor came with a loose 3-pin header, fit it into the three holes (some sensors need it soldered in) so you can plug in jumper wires:

The two dials

The PIR sensor's two adjustment dials

On the side are two small orange dials you can turn with a screwdriver:

  • Time delay (often labelled Tx): how long the output stays at 1 after motion is detected, from about 3 seconds up to a few minutes. Turn it all the way down (anticlockwise) for this lesson.
  • Sensitivity (often labelled Sx): how far away it can see, from roughly 3 m to 7 m.

Many sensors also have a small jumper with two positions. In H (repeat trigger), the output stays on for as long as there's movement. In L (single trigger), it turns off after the delay even if you're still moving. H is the one you'll usually want.

Software

pir_basic.py
# Light the LED whenever the motion sensor sees movement
import time
from machine import Pin

PIR_PIN = 33
LED_PIN = 27

pir = Pin(PIR_PIN, Pin.IN)   # the sensor drives the pin itself: no pull-up needed
led = Pin(LED_PIN, Pin.OUT)

print("Warming up... give the sensor about a minute to settle")

while True:
    led.value(pir.value())   # 1 = motion, 0 = no motion
    time.sleep_ms(50)
  • Pin(PIR_PIN, Pin.IN) sets up an input without a pull-up resistor. Unlike a button, the sensor actively drives its output to 0 or 1 itself.
  • led.value(pir.value()) copies the sensor's reading straight onto the LED.

Give it a minute

After power-up, a PIR sensor needs 30–60 seconds to learn what the room looks like. It may trigger randomly during that time. It also ignores new motion for a couple of seconds after its output switches off.

Reacting when motion starts

Usually you want to do something once when motion begins, such as sending an alert, rather than the whole time it's happening. It's the same trick as the button lesson: remember the last reading and look for a change:

pir_events.py
# Print a message when motion starts and when it stops
import time
from machine import Pin

pir = Pin(33, Pin.IN)

last_value = pir.value()
count = 0

while True:
    value = pir.value()
    if value != last_value:
        if value == 1:
            count += 1
            print("Motion started! (#" + str(count) + ")")
        else:
            print("Motion stopped")
        last_value = value
    time.sleep_ms(50)

No debouncing is needed here: the sensor's output is already clean.

Challenge

Build a mini intruder alarm: each time motion starts, flash the LED and chirp the buzzer (GPIO 26) three times.

Show a solution
pir_alarm.py
# Chirp the buzzer and flash the LED each time motion starts
import time
from machine import PWM, Pin

pir = Pin(33, Pin.IN)
led = Pin(27, Pin.OUT)
buzzer = PWM(Pin(26), freq=1500, duty_u16=0)


def chirp():
    for i in range(3):
        led.on()
        buzzer.duty_u16(32768)
        time.sleep_ms(100)
        led.off()
        buzzer.duty_u16(0)
        time.sleep_ms(100)


last_value = pir.value()
while True:
    value = pir.value()
    if value == 1 and last_value == 0:   # motion just started
        chirp()
    last_value = value
    time.sleep_ms(50)

Want the alarm to message your phone instead? That's the Motion Alert project.

Next up: Wi-Fi & APIs.