DEV Community

張旭豐
張旭豐

Posted on

Why Your PIR Motion Sensor Keeps Triggering at Nothing — And How to Make It Actually Reliable

Why Your PIR Motion Sensor Keeps Triggering at Nothing — And How to Make It Actually Reliable

The Moment Your Installation Lies to You

You walk into the room. The LED strip lights up. You smile.

Then you step back. The LED strip lights up again — but nobody is there.

Then it turns off. Turns on. Turns off. You're standing perfectly still and the sensor keeps firing. Your visitor watches your troubleshooting dance with growing confusion.

That's when you realize: the PIR sensor isn't detecting presence. It's detecting noise.

PIR HC-SR501 + WS2812B presence detection tutorial poster

Affiliate disclosure: As an Amazon Associate, I earn from qualifying purchases.

The components I use in this build are the HC-SR501 PIR module and WS2812B addressable LED strip — search HC-SR501 on Amazon and search WS2812B LED strips on Amazon.

The Problem Nobody Explains Clearly

Most HC-SR501 tutorials show you this:

int pirValue = digitalRead(PIR_PIN);
if (pirValue == HIGH) {
    digitalWrite(LED_PIN, HIGH);
} else {
    digitalWrite(LED_PIN, LOW);
}
Enter fullscreen mode Exit fullscreen mode

This works on your desk. It fails in the real world — not because of bad hardware, but because of three things nobody tells you about:

1. Trigger Mode vs. Repeatable Detection

Most PIR sensors have a jumper or solder pad for "single trigger" vs. "repeatable trigger." In single trigger mode, the output goes HIGH once when motion starts, then stays HIGH for a fixed duration before going LOW — regardless of whether you're still in front of it. In repeatable mode, it keeps resetting its timer as long as you stay in range.

The difference changes your entire interaction design.

2. The Blind Time (Lockout Period)

After the PIR triggers, most modules have a 2.5-3 second "blind time" where they ignore all input. If you wave again during this window, nothing happens. Designers think the sensor is broken. It's not — it's just locked out.

3. Thermal Noise and Ambient Temperature

PIR sensors detect changes in infrared — specifically, the difference between your body heat and ambient temperature. On a hot day when the room is nearly body temperature, the sensor barely registers. In an air-conditioned room with a 10°C differential, it's hypersensitive. The same person triggers it differently at 28°C versus 18°C.

What Actually Makes It Reliable

The key is designing for the sensor's actual behavior — not the idealized tutorial behavior.

The Core Principle

Motion detected → Behavior (not just ON/OFF) → Gradual fade based on dwell time
Enter fullscreen mode Exit fullscreen mode

Instead of flipping the LED on/off, design a system that:

  • Activates when motion is first detected
  • Stays active as long as motion keeps happening (repeatable trigger)
  • Fades out gradually when motion stops — not instantly

The Wiring

HC-SR501 VCC → 5V (use stable 5V, not Arduino's 3.3V rail)
HC-SR501 GND → GND
HC-SR501 OUT → Arduino Pin 2
WS2812B Data → Arduino Pin 6
WS2812B VCC → separate 5V supply if > 8 LEDs
WS2812B GND → shared GND with Arduino
Enter fullscreen mode Exit fullscreen mode

The Code That Actually Behaves Like Presence

#include <Adafruit_NeoPixel.h>
#define PIR_PIN 2
#define LED_PIN 6
#define LED_COUNT 24

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB);
#define MOTION_TIMEOUT 3000  // 3 seconds after last motion → fade out
#define FADE_DURATION 1500   // 1.5 second fade out

enum State { IDLE, ACTIVE, FADING };
State currentState = IDLE;
unsigned long lastMotionTime = 0;
unsigned long fadeStartTime = 0;
uint8_t currentBrightness = 0;

void setup() {
    strip.begin();
    strip.setBrightness(0);
    strip.show();
    pinMode(PIR_PIN, INPUT);
}

void loop() {
    int pirValue = digitalRead(PIR_PIN);
    unsigned long now = millis();

    switch (currentState) {
        case IDLE:
            if (pirValue == HIGH) {
                // Motion detected → activate
                currentState = ACTIVE;
                currentBrightness = 255;
                strip.setBrightness(255);
                colorAll(strip.Color(200, 150, 255));  // Soft purple wake-up
                strip.show();
            }
            break;

        case ACTIVE:
            if (pirValue == HIGH) {
                lastMotionTime = now;  // Reset timeout while motion continues
                // Soft breathing while occupied (sine-wave, not step)
                float breathe = sin(now / 1000.0) * 40.0 + 215.0;  // ~175–255 range
                strip.setBrightness((uint8_t)breathe);
                strip.show();
            }
            // Check if timeout expired (no motion for MOTION_TIMEOUT)
            if (now - lastMotionTime > MOTION_TIMEOUT) {
                currentState = FADING;
                fadeStartTime = now;
            }
            break;

        case FADING:
            {
                unsigned long elapsed = now - fadeStartTime;
                if (elapsed >= FADE_DURATION) {
                    // Fade complete → back to idle
                    strip.setBrightness(0);
                    colorAll(strip.Color(0, 0, 0));
                    strip.show();
                    currentState = IDLE;
                } else {
                    // Non-blocking fade with color shift
                    currentBrightness = map(elapsed, 0, FADE_DURATION, 0, 255);
                    currentBrightness = 255 - currentBrightness;
                    strip.setBrightness(currentBrightness);
                    // Color shifts from purple to warm amber
                    uint8_t r = map(elapsed, 0, FADE_DURATION, 200, 255);
                    uint8_t g = map(elapsed, 0, FADE_DURATION, 150, 80);
                    uint8_t b = map(elapsed, 0, FADE_DURATION, 255, 20);
                    colorAll(strip.Color(r, g, b));
                    strip.show();
                    // Still check for new motion during fade
                    if (pirValue == HIGH) {
                        currentState = ACTIVE;
                        lastMotionTime = now;
                    }
                }
            }
            break;
    }
}

void colorAll(uint32_t color) {
    for (int i = 0; i < LED_COUNT; i++) {
        strip.setPixelColor(i, color);
    }
}
Enter fullscreen mode Exit fullscreen mode

Why this feels like "someone is home" instead of "a sensor is broken":

Note: HC-SR501 detects motion — changes in infrared. It cannot see a static person. The "presence experience" is created entirely through software logic: repeatable trigger mode keeps the light on as long as motion keeps resetting the timeout, and a slow fade-out means the room doesn't go dark the instant you stop moving.

  1. Instant-on on first detection — immediate response, feels alive
  2. Gradual fade-out — the room "exhales" when you leave, not a hard cut
  3. Warm color transition — fading from purple to amber feels like cooling down
  4. Breathing pulse while occupied — subtle variation so the room doesn't look frozen

The Adjustment Points

What you adjust Effect
MOTION_TIMEOUT How long the light stays on after last detected motion
FADE_DURATION How long the fade-out takes — longer = more dramatic
currentBrightness max Overall brightness when occupied
PIR sensitivity (trimpot) How far away motion triggers — turn too high = triggers on temperature changes

When It Still Fires Randomly

The lockout period is your enemy in fast-moving environments. If you need faster re-trigger, use a PIR module that allows shorter blind time (some modules have a trimpot for this), or add a simple motion filter: require three consecutive HIGH readings before activating.

Thermal drift is worst in poorly insulated walls. If your installation is near an exterior wall that heats up in the afternoon sun, the sensor will drift in sensitivity throughout the day. Use a thermal barrier between the wall and the sensor, or calibrate your threshold after the space has stabilized.

Pet immunity requires lowering the detection zone. Standard PIR sensors look for human-sized heat signatures by default. Lowering the lens mask or angling the sensor downward excludes pets without removing the wall. Some modules have dual-element sensors designed for pet immunity.

The Real Principle

A motion sensor that just turns LEDs on/off is a motion sensor. An installation that activates when you arrive, breathes gently while you're present, and exhales slowly when you leave — that's a presence experience.

When you design for dwell time and fade-out behavior, the installation stops feeling like a security system and starts feeling like something that knows you're there.


If you're figuring out which modules to use for an interactive installation — and you want a blueprint that maps your specific sensors to the interaction behaviors that will actually feel intentional — I offer a personalized one-page PDF that turns your module selection into a complete interaction design.

Get a custom interactive prototype blueprint on Fiverr

Top comments (0)