
The circuit above implements a light switch which is activated by darkness and movement, using an LDR and a PIR sensor respectively. It draws its power from the voltage it's switching, using a 7805 voltage regulator (on the left). A manual override switch is also provided.
The sketch below implements the brains of this circuit. Highlights are:
- It smooths the light reading using an array of samples to prevent accidental triggering.
- Hysteresis also helps with this, a light reading below the light threshold is definitely daylight, while one above the dark threshold is definitely night-time.
- The light level is tunable using a potentiometer, when it's between the light and dark thresholds, an LED is lit indicating dusk.
- Another LED is lit, using the signal from the PIR, when motion is detected. This is useful when positioning the box in daylight!
- A manual override is provided to turn on the light for half an hour when it's daytime, and turn it off for 15s at night-time. (The latter allows the light to be turned off and the operator time to get out of the way.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define LIGHT 5 | |
#define MOTION 0 | |
#define SWITCH 1 | |
#define LEDS 2 | |
#define DUSK 4 | |
#define HZ 4 | |
#define SAMPLES (15*HZ) | |
#define ON_DELAY (30*60*HZ) | |
#define OFF_DELAY (15*HZ) | |
#define DARK_THRESHOLD 720 | |
#define LIGHT_THRESHOLD 680 | |
boolean off = true; | |
int onCount = 0, offCount = 0; | |
int samples[SAMPLES], pos, smoothed; | |
long total; | |
int readLight() { | |
int reading = analogRead(LIGHT); | |
return reading; | |
} | |
int sampleLight() { | |
int curr = readLight(); | |
total += curr - samples[pos]; | |
samples[pos++] = curr; | |
if (pos == SAMPLES) pos = 0; | |
smoothed = (int)(total / SAMPLES); | |
return curr; | |
} | |
void setup() { | |
pinMode(MOTION, INPUT); | |
pinMode(SWITCH, INPUT); | |
digitalWrite(SWITCH, HIGH); // activate pull-up | |
pinMode(LEDS, OUTPUT); | |
pinMode(DUSK, OUTPUT); | |
smoothed = readLight(); | |
for (int i = 0; i < SAMPLES; i++) { | |
samples[i] = smoothed; | |
total += smoothed; | |
} | |
} | |
inline boolean manual() { | |
return digitalRead(SWITCH) == LOW; | |
} | |
inline boolean isDark(int val) { | |
return val > DARK_THRESHOLD; | |
} | |
inline boolean isDark() { | |
return isDark(smoothed); | |
} | |
inline boolean isLight(int val) { | |
return val < LIGHT_THRESHOLD; | |
} | |
inline boolean isLight() { | |
return isLight(smoothed); | |
} | |
void turnOn() { | |
if (off) { | |
digitalWrite(LEDS, HIGH); | |
off = false; | |
} | |
} | |
void turnOff() { | |
if (!off) { | |
digitalWrite(LEDS, LOW); | |
off = true; | |
} | |
} | |
inline boolean movement() { | |
return digitalRead(MOTION) == HIGH; | |
} | |
inline void updateIndication(int val) { | |
digitalWrite(DUSK, isDark(val) || isLight(val)? HIGH: LOW); | |
} | |
void loop() { | |
updateIndication(sampleLight()); | |
if (manual()) { | |
if (off) { | |
turnOn(); | |
onCount = ON_DELAY; | |
} else { | |
turnOff(); | |
offCount = OFF_DELAY; | |
} | |
} else if (movement()) { | |
onCount = ON_DELAY; | |
if (offCount == 0 && isDark()) | |
turnOn(); | |
} else if (onCount == 0) | |
turnOff(); | |
delay(1000 / HZ); | |
if (offCount > 0) offCount--; | |
if (onCount > 0) onCount--; | |
} |