Preview build — page text is a draft awaiting human review. Code badges come from real compiles. The flasher is real but not yet hardware-tested; the Playground is still a preview.

HC-SR501 PIR sensor with Raspberry Pi Pico W — MicroPython motion alert

Detect motion with an HC-SR501 PIR on a Pico W (5 V from VBUS, OUT to GP16) and send an optional phone alert through ntfy over Wi-Fi in MicroPython.

MicroPythonVerified — micropython (mpy-cross v1.29.0), 3 Nov 2026Compile-only

Community draft — report a mistake

The HC-SR501 is the PIR in every security and corridor-light project. It needs 5 V, but its OUT pin is a clean 3.3 V signal, so the Pico W reads it directly on GP16 — no divider, unlike most 5 V modules.

Because this is a Pico W, the script can also tell your phone: set a Wi-Fi name and a private ntfy topic, install the ntfy app, subscribe to the same topic, and every new motion (at most one a minute) arrives as a notification.

Wiring

Raspberry Pi Pico WOnly the pins used are shown · 3.3 V logicHC-SR501 PIR motion sensorGNDVBUSGP16VCCOUTGND
Every connection
Raspberry Pi Pico WHC-SR501 PIR motion sensorWireNotes
VBUSVCCredVBUS (pin 40) is USB 5 V. The HC-SR501 needs 4.5 V or more.
GP16OUTyellowGP16 is physical pin 21. OUT is 3.3 V from the module’s own regulator — no divider needed.
GNDGNDblack

4.5–20 V (5 V typical) part, 3.3 V board. Power it from 5 V (VIN/5V pin). Its output is a 3.3 V logic signal, so it connects directly to ESP32, Pico and Uno GPIOs.

Board quirks

  • The PIR fires randomly for about a minute after power-up while it settles. The script waits 60 s before it starts reporting.
  • Set the yellow jumper to H (repeat trigger) so OUT stays HIGH while someone keeps moving. The two pots set sensitivity (range) and hold time — turn the time pot fully anticlockwise for the shortest ~3 s pulse.
  • Phone alerts are optional: with no Wi-Fi name or ntfy topic set, it just prints “Motion detected”. A cooldown keeps it to one alert a minute.
  • Wi-Fi settings come from /settings.json (written by the FlashReady flasher) or the constants at the top of main.py. The Pico W is 2.4 GHz only.
  • Mount it away from windows, ceiling fans and AC vents: moving warm air and sunlight changes trigger it as easily as a person.

Code

main.pyMicroPython
# HC-SR501 PIR motion sensor on a Raspberry Pi Pico W (MicroPython) with an optional phone alert
# through ntfy.sh. Wiring: VCC -> VBUS (pin 40, USB 5 V), OUT -> GP16 (pin 21), GND -> GND.
# OUT is already a 3.3 V signal (the module has its own regulator), so no divider is needed.
from machine import Pin
import network
import time
import json

WIFI_SSID = "your-wifi"            # optional: leave as-is to just print motion
WIFI_PASS = "your-password"
NTFY_TOPIC = "your-private-topic"  # https://ntfy.sh/<topic> - pick something unguessable
COOLDOWN_S = 60                    # at most one phone alert a minute

# The FlashReady flasher saves your settings to /settings.json; they win over the lines above.
try:
    with open("/settings.json") as f:
        _s = json.load(f)
except (OSError, ValueError):
    _s = {}
WIFI_SSID = _s.get("wifi_ssid") or WIFI_SSID
WIFI_PASS = _s.get("wifi_pass") or WIFI_PASS
NTFY_TOPIC = _s.get("ntfy_topic") or NTFY_TOPIC
ALERTS = WIFI_SSID != "your-wifi" and NTFY_TOPIC != "your-private-topic"

pir = Pin(16, Pin.IN)
led = Pin("LED", Pin.OUT)
wlan = network.WLAN(network.STA_IF)


def connect():
    if wlan.isconnected():
        return True
    wlan.active(True)
    wlan.connect(WIFI_SSID, WIFI_PASS)
    for _ in range(30):  # up to 15 s
        if wlan.isconnected():
            print("Wi-Fi connected:", wlan.ifconfig()[0])
            return True
        time.sleep(0.5)
    print("Wi-Fi failed, status", wlan.status())
    return False


def alert(msg):
    if not ALERTS or not connect():
        return
    try:
        import requests
    except ImportError:
        import urequests as requests
    try:
        requests.post("https://ntfy.sh/" + NTFY_TOPIC, data=msg).close()
        print("Phone alert sent")
    except OSError as e:
        print("ntfy failed:", e)


if ALERTS:
    try:
        network.country("IN")
    except AttributeError:
        import rp2
        rp2.country("IN")
    connect()
else:
    print("Phone alerts off - no Wi-Fi name or ntfy topic set. Flash from flashready.in or type them into main.py.")

print("PIR on GP16 - warming up for 60 s, keep still")
time.sleep(60)  # the HC-SR501 triggers at random for about a minute after power-up
print("Ready")

last = 0
last_alert = -COOLDOWN_S
while True:
    now = pir.value()
    led.value(now)
    if now and not last:
        t = time.time()
        print("Motion detected")
        if t - last_alert >= COOLDOWN_S:
            last_alert = t
            alert("Motion detected by the Pico W")
    elif last and not now:
        print("Motion ended")
    last = now
    time.sleep_ms(50)

Expected serial output

REPL / Thonny shellOutput
Wi-Fi connected: 192.168.1.42
PIR on GP16 - warming up for 60 s, keep still
Ready
Motion detected
Phone alert sent
Motion ended

Common errors

Motion detected every few seconds with nobody there

Still warming up, a fan or AC is in view, or sensitivity is too high. Turn the sensitivity pot anticlockwise and re-aim the dome.

Never detects anything

VCC is on 3V3 instead of VBUS, or OUT is not on GP16. The module needs at least 4.5 V; on battery power VBUS is dead, so feed it from VSYS with a 5 V supply.

ntfy failed: -2

DNS failed — the Pico W is not really online. Check the Wi-Fi name and password, and that your router or hotspot has 2.4 GHz turned on.

↑↓ to move · Enter to open