PIR motion light with a relay on an Arduino Uno
After dark, an HC-SR501 PIR switches a 12 V lamp on through a relay, and it goes off 20 s after the last movement; a light sensor keeps it off by day. The whole build, an Arduino Uno and 5 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Beginner About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $39. On Mokxi they're free.
What you'll learn
- Switching a higher voltage load with a relay
- PIR motion sensing
- Using a digital light module output
- Restarting a timer on every event
- Difficulty: Beginner
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 7
How it works
An HC-SR501 PIR on pin 2 senses movement. A photoresistor module on pin 3 reads HIGH when it is dark. The sketch only reacts to motion if it is dark.
In the dark, any motion switches pin 7 HIGH, which closes the relay and powers a 12 V lamp. Each new motion restarts a timer.
The lamp goes off ON_MS (20 seconds) after the last motion. The sketch only checks darkness while the lamp is off, so the lamp's own light cannot fool it.
Parts list
7 parts, plus the jumper wires. They retail for about $39; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Half breadboard
- 1 × Motion sensorIn Halloween Prop Kit.
- 1 × Photoresistor sensor moduleIn Desk Buddy Kit, Escape Box Kit.
- 1 × Relay moduleIn Halloween Prop Kit.
- 1 × DC jack and adapter
- 1 × Filament lamp
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Relay module pin IN | Arduino Uno R3 pin 7 |
| Photoresistor sensor module pin DO | Arduino Uno R3 pin 3 |
| Motion sensor pin OUT | Arduino Uno R3 pin 2 |
| Motion sensor pin VCC | Arduino Uno R3 pin 5V |
| Photoresistor sensor module pin VCC | Arduino Uno R3 pin 5V |
| Relay module pin VCC | Arduino Uno R3 pin 5V |
| Motion sensor pin GND | Arduino Uno R3 pin GND |
| Photoresistor sensor module pin GND | Arduino Uno R3 pin GND |
| Relay module pin GND | Arduino Uno R3 pin GND |
| Filament lamp pin 1 | Relay module pin NO |
| DC jack and adapter pin V+ | Relay module pin COM |
| DC jack and adapter pin V- | Ground |
| Filament lamp pin 2 | Ground |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// PIR motion light with a relay: after dark, someone walking past switches a
// 12 V lamp on through a relay, and it stays on until nobody has moved for
// ON_MS. In daylight the PIR is ignored.
//
// pin 2 the HC-SR501's OUT (VCC 5V, GND)
// pin 3 the photoresistor module's DO: HIGH when darker than its trimmer
// pin 7 the relay module's IN (high trigger, VCC 5V, GND)
// relay COM to the 12 V adapter's +, NO to the lamp, the lamp's other
// lead to the adapter's -
// (the adapter's - also to GND, sharing the Uno's ground)
//
// The HC-SR501 holds OUT high for a couple of seconds after the last movement
// (its Tx trimmer) and then needs a moment before it can trigger again. The
// sketch keeps its own, longer timer: every time OUT is high the timer starts
// over, so the light stays on while anyone is moving and goes off ON_MS after
// the last of them.
//
// Darkness is only read while the lamp is off. On a real porch the lamp
// shines on its own sensor, so a sketch that keeps checking would see
// "daylight", switch the lamp off, see "dark" and switch it on again, all
// night. Here the lamp's light does not reach the module, but the sketch is
// written for the real porch.
//
// The PIR's output is 3.3 V, which an Uno pin reads as HIGH (it wants 3.0 V).
// The lamp is 12 V; switching mains with a relay belongs in a closed box.
//
// Press Run, drag the light slider down to night, then click the PIR.
const int PIR_PIN = 2;
const int DARK_PIN = 3;
const int RELAY_PIN = 7;
const unsigned long ON_MS = 20000; // 20 s after the last movement
bool lampOn = false;
bool dark = false;
bool known = false; // whether dark has been read yet
unsigned long lastMotion = 0;
void lamp(bool state) {
lampOn = state;
digitalWrite(RELAY_PIN, state ? HIGH : LOW);
Serial.println(state ? "lamp ON" : "lamp OFF");
}
void setup() {
pinMode(PIR_PIN, INPUT);
pinMode(DARK_PIN, INPUT);
digitalWrite(RELAY_PIN, LOW);
pinMode(RELAY_PIN, OUTPUT);
Serial.begin(9600);
Serial.println("PIR motion light: waiting for dark");
}
void loop() {
unsigned long now = millis();
if (!lampOn) {
bool nowDark = digitalRead(DARK_PIN) == HIGH;
if (nowDark != dark || !known) {
dark = nowDark;
known = true;
Serial.println(dark ? "dark: watching for motion" : "daylight: PIR ignored");
}
}
if (digitalRead(PIR_PIN) == HIGH) {
if (dark) {
lastMotion = now;
if (!lampOn) lamp(true);
}
}
if (lampOn && now - lastMotion >= ON_MS) {
lamp(false);
}
delay(50);
}
Code walkthrough
The key functions of the sketch, and what each one does.
lamp sketch.ino
Sets lampOn, writes the relay pin HIGH or LOW to match, and prints "lamp ON" or "lamp OFF". Every change of the lamp goes through here.
void lamp(bool state) {
lampOn = state;
digitalWrite(RELAY_PIN, state ? HIGH : LOW);
Serial.println(state ? "lamp ON" : "lamp OFF");
}setup sketch.ino
Sets the PIR and dark pins as inputs. It writes the relay pin LOW before making it an output, so the lamp does not flash on at start-up.
void setup() {
pinMode(PIR_PIN, INPUT);
pinMode(DARK_PIN, INPUT);
digitalWrite(RELAY_PIN, LOW);
pinMode(RELAY_PIN, OUTPUT);
Serial.begin(9600);
Serial.println("PIR motion light: waiting for dark");
}loop sketch.ino
While the lamp is off, it reads DARK_PIN and reports changes. If the PIR is HIGH and it is dark, it records lastMotion and turns the lamp on. When ON_MS has passed since the last motion, it turns the lamp off.
void loop() {
unsigned long now = millis();
if (!lampOn) {
bool nowDark = digitalRead(DARK_PIN) == HIGH;
if (nowDark != dark || !known) {
dark = nowDark;
known = true;
Serial.println(dark ? "dark: watching for motion" : "daylight: PIR ignored");
}
}
if (digitalRead(PIR_PIN) == HIGH) {
if (dark) {
lastMotion = now;
if (!lampOn) lamp(true);
}
}
if (lampOn && now - lastMotion >= ON_MS) {
lamp(false);
}
delay(50);
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The lamp never turns on This project
It has to be dark. Drag the light slider down to night, then click the PIR. In daylight the sketch ignores motion. - The relay clicks but the lamp stays dark This project
Wire the adapter's + to COM and the lamp to NO. Join the adapter's - to ground and to the lamp's other lead. - The lamp stays on too long or not long enough This project
ChangeON_MS. It is in milliseconds. - The sensor reads high for ages after power up. Motion sensor
A real PIR needs 30 to 60 seconds to settle. Ignore its output during that time, and keep still in front of it until the delay passes. - Wrong output or it never triggers. Motion sensor
Power it from 5 V, join GND, and read OUT as a digital input (3.3 V logic high). Check the trimmers for delay and sensitivity. - The digital output never changes, or is always on, no matter the light. Photoresistor sensor module
Turn the trimmer on the module until DO flips at the light level you want. The digital output is set by that threshold, and which direction means bright depends on the board. - Analog readings go down when it gets brighter, and the code expects the opposite. Photoresistor sensor module
The LDR sits in a divider, so the direction depends on how the module is built. Print the raw value in the light and dark, then flip it in code with 1023 minus the reading if needed. - The relay does not click, or the board resets when it switches. Relay module
A relay coil needs more current than a pin can give. Use a module with a transistor driver, and power VCC from 5 V. Share ground between the module and the Arduino.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.