Motion Activated LED Strip using LDR and PIR
A motion-activated LED strip system that turns on lights in the dark using input from both a PIR motion sensor and an LDR..
Components and supplies
1
10k ohm resistors
1
HC-SR501
1
12V LED Strip
1
LDR Resistor
1
Arduino Nano
1
330 ohm resistors
1
Mosfet IRFZ44N
1
12v adapter
Tools and machines
1
Soldering kit
1
Double sided tape
Apps and platforms
1
Arduino IDE
Project description
Code
Code
Upload and enjoy!!!
1const int PIR_PIN = 12; // PIR sensor pin 2const int LED_PIN = 8; // LED pin 3const int LDR_PIN = A0; // LDR sensor pin 4 5// Timing constants 6const int TIMEOUT = 5000; // 5 seconds timeout for walking to bathroom 7const int CHECK_TIME = 4000; // Check at 4 seconds 8const int DEBOUNCE_TIME = 200; // Time between debounce readings 9const int DEBOUNCE_READS = 3; // Number of readings for debounce 10const int LDR_CHECK_INTERVAL = 1000; // Check light level every 1 second 11 12// LDR constants 13const int numReadings = 3; // Moving average window size for LDR (reduced from 5) 14const int hysteresis = 20; // Hysteresis to prevent flickering 15 16// Debugging 17const bool DEBUG = true; // Set to true for serial debugging, false for normal operation 18 19const int LDR_FAST_INTERVAL = 1000; // Check light every 1 second when dark or changing 20const int LDR_SLOW_INTERVAL = 10000; // Check light every 10 seconds when consistently bright 21const int LIGHT_HISTORY_THRESHOLD = 5; // Number of consecutive light readings to trigger slow mode 22 23// Add these new state variables with other state variables 24int consecutiveLightReadings = 0; 25int currentLdrInterval = LDR_FAST_INTERVAL; 26 27// State variables for PIR 28bool motionDetected = false; 29unsigned long motionStartTime = 0; 30unsigned long lastSerialTime = 0; 31unsigned long lastWatchdogCheck = 0; 32 33// State variables for LDR 34int ldrReadings[numReadings]; // Array to store LDR readings 35int readIndex = 0; 36int ldrTotal = 0; 37int ldrAverage = 0; 38int ldrThreshold = 0; // Will be set during calibration 39bool isDark = false; 40unsigned long lastLdrCheck = 0; 41 42// System state 43bool ledsEnabled = false; // Combined state from both sensors 44 45unsigned long ledOnTime = 0; 46const int LDR_LOCKOUT_PERIOD = 10000; // 10 seconds lockout after LEDs turn on 47 48void setup() { 49 pinMode(PIR_PIN, INPUT); 50 pinMode(LED_PIN, OUTPUT); 51 pinMode(LDR_PIN, INPUT); 52 digitalWrite(LED_PIN, LOW); // Ensure OFF state on startup 53 54 if (DEBUG) { 55 Serial.begin(9600); 56 Serial.println("⚡ Combined PIR Motion & LDR Light Sensor System Ready..."); 57 } 58 59 // Initialize LDR readings array 60 for (int i = 0; i < numReadings; i++) { 61 ldrReadings[i] = analogRead(LDR_PIN); 62 ldrTotal += ldrReadings[i]; 63 } 64 65 // Calibrate LDR threshold 66 calibrateLDR(); 67 68 // Allow PIR sensor to stabilize on startup 69 if (DEBUG) Serial.println("PIR sensor stabilizing..."); 70 delay(3000); 71 if (DEBUG) Serial.println("System ready!"); 72} 73 74// LDR calibration function 75void calibrateLDR() { 76 // if (DEBUG) Serial.println("Calibrating LDR sensor..."); 77 78 // int sum = 0; 79 // for (int i = 0; i < 10; i++) { // Take 10 readings at startup 80 // sum += analogRead(LDR_PIN); 81 // delay(50); 82 // } 83 84 // ldrThreshold = sum / 10; // Set the threshold dynamically 85 86 // if (DEBUG) { 87 // Serial.print("Calibrated LDR Threshold: "); 88 // Serial.println(ldrThreshold); 89 // Serial.println("IMPORTANT: After testing, replace the calibrateLDR() function with:"); 90 // Serial.println("void calibrateLDR() {"); 91 // Serial.print(" ldrThreshold = "); 92 // Serial.print(ldrThreshold); 93 // Serial.println("; // Hardcoded value based on testing"); 94 // Serial.println("}"); 95 // } 96 97 ldrThreshold = 30; 98 99 // Initial darkness check 100 checkLight(); 101} 102 103// Helper function for debounced PIR reading 104bool debounceMotion() { 105 int motionCount = 0; 106 107 // Take multiple readings with short pauses 108 for (int i = 0; i < DEBOUNCE_READS; i++) { 109 if (digitalRead(PIR_PIN) == HIGH) { 110 motionCount++; 111 } 112 delay(DEBOUNCE_TIME / DEBOUNCE_READS); 113 } 114 115 // Return true if majority of readings show motion 116 return (motionCount > DEBOUNCE_READS / 2); 117} 118 119// Check motion sensor and update motion state 120void checkMotion() { 121 unsigned long currentMillis = millis(); 122 123 // Handle millis() overflow 124 // millis() will eventually overflow (~50 days), rolling back to 0. If that happens, currentMillis will suddenly be less than your stored motionStartTime. This resets your timer to avoid weird behavior. 125 if (currentMillis < motionStartTime) { 126 motionStartTime = currentMillis; 127 } 128 129 // Only check motion when needed to reduce processor load 130 bool motionNow = false; 131 132 if (!motionDetected || 133 (motionDetected && (currentMillis - motionStartTime >= CHECK_TIME) && 134 (currentMillis - motionStartTime < TIMEOUT))) { 135 // Only debounce when we need to check for motion 136 motionNow = debounceMotion(); 137 } 138 139 // State machine 140 if (!motionDetected && motionNow) { 141 // New motion detected 142 motionDetected = true; 143 motionStartTime = currentMillis; 144 145 if (DEBUG && (currentMillis - lastSerialTime > 1000)) { 146 Serial.println("✅ Motion detected!"); 147 lastSerialTime = currentMillis; 148 } 149 } 150 else if (motionDetected) { 151 // Already in a motion cycle 152 if (currentMillis - motionStartTime >= CHECK_TIME && 153 currentMillis - motionStartTime < TIMEOUT && motionNow) { 154 // Motion still present at check time - extend timer 155 motionStartTime = currentMillis; 156 157 if (DEBUG && (currentMillis - lastSerialTime > 1000)) { 158 Serial.println("🔁 Motion still detected, extending time..."); 159 lastSerialTime = currentMillis; 160 } 161 } 162 else if (currentMillis - motionStartTime >= TIMEOUT) { 163 // Timeout reached 164 motionDetected = false; 165 166 if (DEBUG && (currentMillis - lastSerialTime > 1000)) { 167 Serial.println("🚨 No motion detected for full cycle."); 168 lastSerialTime = currentMillis; 169 } 170 } 171 } 172} 173 174// Check light sensor and update darkness state 175void checkLight() { 176 // Remove oldest reading 177 ldrTotal -= ldrReadings[readIndex]; 178 179 // Read new value and update moving average 180 ldrReadings[readIndex] = analogRead(LDR_PIN); 181 ldrTotal += ldrReadings[readIndex]; 182 readIndex = (readIndex + 1) % numReadings; 183 ldrAverage = ldrTotal / numReadings; // Compute moving average 184 185 if (DEBUG) { 186 Serial.print("LDR Average: "); 187 Serial.print(ldrAverage); 188 Serial.print(" (Threshold: "); 189 Serial.print(ldrThreshold); 190 Serial.println(")"); 191 } 192 193 // Update darkness state with hysteresis 194 if (!isDark && ldrAverage <= ldrThreshold - hysteresis) { 195 isDark = true; 196 if (DEBUG) Serial.println("🌙 Dark environment detected"); 197 } 198 else if (isDark && ldrAverage >= ldrThreshold + hysteresis) { 199 isDark = false; 200 if (DEBUG) Serial.println("☀️ Light environment detected"); 201 } 202} 203 204// // Update LED state based on sensor inputs 205// void updateLEDs() { 206// // Since we're only calling this when it's dark, just check motion 207// bool newLedState = motionDetected; 208 209// // Only update physical LED if state has changed 210// if (ledsEnabled != newLedState) { 211// ledsEnabled = newLedState; 212// digitalWrite(LED_PIN, ledsEnabled ? HIGH : LOW); 213 214// if (DEBUG) { 215// if (ledsEnabled) { 216// Serial.println("💡 LEDs ON - Dark + Motion"); 217// } else { 218// Serial.println("💡 LEDs OFF - No motion"); 219// } 220// } 221// } 222// } 223 224// Watchdog function to detect inconsistent states 225void runWatchdog() { 226 unsigned long currentMillis = millis(); 227 228 // Watchdog - check for inconsistent states every 30 seconds 229 if (currentMillis - lastWatchdogCheck > 30000) { 230 lastWatchdogCheck = currentMillis; 231 232 // Fix inconsistent LED states 233 if (ledsEnabled != digitalRead(LED_PIN)) { 234 digitalWrite(LED_PIN, ledsEnabled ? HIGH : LOW); 235 236 if (DEBUG) { 237 Serial.println("⚠️ Watchdog reset - inconsistent LED state detected"); 238 } 239 } 240 } 241} 242 243// The issue is in the loop function and how we're managing the state variables. 244// Here's the fixed loop() function: 245 246void loop() { 247 unsigned long currentMillis = millis(); 248 249 // Check if we're in the lockout period after turning on LEDs 250 bool inLockoutPeriod = ledsEnabled && (currentMillis - ledOnTime < LDR_LOCKOUT_PERIOD); 251 252 // Only check light level when not in lockout period and enough time has passed since the last check. 253 if (!inLockoutPeriod && currentMillis - lastLdrCheck >= currentLdrInterval) { 254 lastLdrCheck = currentMillis; 255 256 // Check light before doing anything else 257 bool previousDarkState = isDark; 258 checkLight(); 259 260 // Adaptive light checking interval logic 261 if (!isDark) { 262 // Environment is light, increment counter 263 consecutiveLightReadings++; 264 if (consecutiveLightReadings >= LIGHT_HISTORY_THRESHOLD && currentLdrInterval == LDR_FAST_INTERVAL) { 265 // Switch to slow checking after consistent light readings 266 currentLdrInterval = LDR_SLOW_INTERVAL; 267 if (DEBUG) Serial.println("☀️ Consistently bright environment detected, reducing LDR check frequency"); 268 } 269 270 // Ensure LEDs are off when it's light (do this immediately after light check) 271 if (ledsEnabled) { 272 ledsEnabled = false; 273 digitalWrite(LED_PIN, LOW); 274 if (DEBUG) Serial.println("💡 LEDs OFF - Room is bright"); 275 } 276 } else { 277 // Environment is dark, reset counter and ensure fast checking 278 consecutiveLightReadings = 0; 279 if (currentLdrInterval == LDR_SLOW_INTERVAL) { 280 currentLdrInterval = LDR_FAST_INTERVAL; 281 if (DEBUG) Serial.println("🌙 Dark environment detected, increasing LDR check frequency"); 282 } 283 } 284 } 285 286 // Only proceed with motion detection and LED control if it's dark 287 if (isDark) { 288 checkMotion(); 289 290 // Update LED state based on motion (only if dark) 291 bool newLedState = motionDetected; 292 if (ledsEnabled != newLedState) { 293 ledsEnabled = newLedState; 294 digitalWrite(LED_PIN, ledsEnabled ? HIGH : LOW); 295 296 // Record time when LEDs turn on 297 if (ledsEnabled) { 298 ledOnTime = currentMillis; 299 } 300 301 if (DEBUG) { 302 if (ledsEnabled) { 303 Serial.println("💡 LEDs ON - Dark + Motion"); 304 } else { 305 Serial.println("💡 LEDs OFF - No motion"); 306 } 307 } 308 } 309 } 310 311 // Run watchdog regardless of light conditions 312 runWatchdog(); 313 314 // Short delay to reduce CPU usage 315 delay(300); 316} 317// The updateLEDs() function is now integrated directly into the loop 318// to ensure proper state management 319/* 320 * CALIBRATION INSTRUCTIONS: 321 * 322 * 1. Run this code with DEBUG set to true 323 * 2. Note the LDR Threshold value after calibration 324 * 3. After testing with your actual setup in its final position, 325 * replace the calibrateLDR() function with the hardcoded version: 326 * 327 * void calibrateLDR() { 328 * ldrThreshold = 450; // Replace 450 with your calibrated value 329 * } 330 * 331 * 4. Comment out the dynamic calibration code and uncomment the 332 * hardcoded version below: 333 */ 334 335/* UNCOMMENT THIS SECTION AFTER CALIBRATION 336void calibrateLDR() { 337 // Hardcoded threshold based on testing in the final installation location 338 ldrThreshold = 450; // Replace with your calibrated value 339 340 if (DEBUG) { 341 Serial.print("Using hardcoded LDR Threshold: "); 342 Serial.println(ldrThreshold); 343 } 344 345 // Initial darkness check 346 checkLight(); 347} 348*/
Documentation
Project Schematic
LED is used instead of LED Strip for simplicity.
Schematic_LED-Strip_2025-04-23.png

Comments
Only logged in users can leave comments