Walking up or down the stairs at night can be unsafe without proper lighting. Instead of turning on a bright floodlight, why not make your staircase intelligent and stylish? In this project, we’ll build an automatic staircase lighting system that turns on LED strips step by step as you climb, and then turns them off step by step as you exit. The system uses two ultrasonic sensors (one at the bottom, one at the top) to detect motion.
/*
Two-sensor staircase lights (sequential ON/OFF)
– Sensor 1 (bottom): trig=t_s1, echo=e_s1
– Sensor 2 (top): trig=t_s2, echo=e_s2
– LEDs in OutPins[] map from bottom (index 0) to top (last index)
Behavior:
Start at Sensor 1 -> cascade ON bottom->top; on reaching Sensor 2 -> cascade OFF top->bottom
Start at Sensor 2 -> cascade ON top->bottom; on reaching Sensor 1 -> cascade OFF bottom->top
Notes:
– Uses millis() (non-blocking)
– Debounces sensor triggers
– Auto-timeout turns lights off if other sensor is never reached
*/
//////////////////// USER SETTINGS ////////////////////
int set_dis = 8; // Detection distance in cm (typ. 8–20 depending on placement)
int set_shift = 500; // Step interval (ms) between LEDs turning on/off
int set_timer = 20; // Max seconds to wait for the opposite sensor before auto OFF
// Map your LED pins from BOTTOM (index 0) to TOP (last index)
int OutPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};==
#define e_s1 A0 // Echo pin of Sensor 1 (bottom)
#define t_s1 A1 // Trig pin of Sensor 1 (bottom)
#define e_s2 A2 // Echo pin of Sensor 2 (top)
#define t_s2 A3 // Trig pin of Sensor 2 (top)
///////////////////////////////////////////////////////
const uint8_t N_STEPS = sizeof(OutPins) / sizeof(OutPins[0]);
// ———- Internal timing & state ———-
enum Direction { NONE, UP, DOWN }; // UP = bottom->top movement, DOWN = top->bottom
enum Phase { IDLE, ON_CASCADE, WAITING_OPPOSITE, OFF_CASCADE };
Direction dir = NONE;
Phase phase = IDLE;
int stepIndex = -1; // progress pointer for cascades
unsigned long lastStepMs = 0; // for pacing cascade steps
unsigned long waitDeadline = 0; // for WAITING_OPPOSITE timeout
// Sensor debounce / edge detect
bool s1PrevActive = false, s2PrevActive = false;
unsigned long s1LastEdgeMs = 0, s2LastEdgeMs = 0;
const unsigned long rearmMs = 800; // ignore repeated triggers for this long
// —————— Helpers ——————
long readDistanceCM(uint8_t trig, uint8_t echo) {
// Trigger the ultrasonic (HC-SR04 style)
digitalWrite(trig, LOW); delayMicroseconds(2);
digitalWrite(trig, HIGH); delayMicroseconds(10);
digitalWrite(trig, LOW);
// 30,000 us timeout ≈ 5m; avoids long blocking if nothing is seen
unsigned long dur = pulseIn(echo, HIGH, 30000UL);
if (dur == 0) return 0; // no reading
long cm = dur / 58; // µs to cm
return cm;
}
void setAll(bool on) {
for (uint8_t i = 0; i < N_STEPS; i++) digitalWrite(OutPins[i], on ? HIGH : LOW);
}
void allOff() { setAll(false); }
// Edge detection with basic debounce/rearm
bool sensorEdge(bool activeNow, bool &prevActive, unsigned long &lastEdgeMs) {
unsigned long now = millis();
bool rising = activeNow && !prevActive && (now – lastEdgeMs > rearmMs);
prevActive = activeNow;
if (rising) lastEdgeMs = now;
return rising;
}
void startSequence(Direction startDir) {
dir = startDir;
phase = ON_CASCADE;
lastStepMs = 0; // force immediate first step
if (dir == UP) {
// Turning ON from bottom to top
stepIndex = 0;
} else { // DOWN
// Turning ON from top to bottom
stepIndex = N_STEPS – 1;
}
}
void startWaiting() {
phase = WAITING_OPPOSITE;
waitDeadline = millis() + (unsigned long)set_timer * 1000UL;
}
void startOffCascade() {
phase = OFF_CASCADE;
if (dir == UP) {
// Turn OFF from top to bottom
stepIndex = N_STEPS – 1;
} else { // DOWN
// Turn OFF from bottom to top
stepIndex = 0;
}
}
void finishSequence() {
dir = NONE;
phase = IDLE;
stepIndex = -1;
}
// —————— Arduino Core ——————
void setup() {
Serial.begin(9600);
// LED pins
for (uint8_t i = 0; i < N_STEPS; i++) {
pinMode(OutPins[i], OUTPUT);
digitalWrite(OutPins[i], LOW);
}
// Ultrasonic pins
pinMode(t_s1, OUTPUT); pinMode(e_s1, INPUT);
pinMode(t_s2, OUTPUT); pinMode(e_s2, INPUT);
digitalWrite(t_s1, LOW);
digitalWrite(t_s2, LOW);
// Ready
Serial.println(F(“Stair Lights Controller: READY”));
}
void loop() {
// —- Read sensors once per loop —-
long d1 = readDistanceCM(t_s1, e_s1); // bottom
long d2 = readDistanceCM(t_s2, e_s2); // top
bool s1Active = (d1 > 0 && d1 <= set_dis);
bool s2Active = (d2 > 0 && d2 <= set_dis);
bool s1EdgeRise = sensorEdge(s1Active, s1PrevActive, s1LastEdgeMs);
bool s2EdgeRise = sensorEdge(s2Active, s2PrevActive, s2LastEdgeMs);
// Uncomment for debugging:
// Serial.print(“d1=”); Serial.print(d1); Serial.print(” d2=”); Serial.println(d2);
unsigned long now = millis();
switch (phase) {
case IDLE:
// Start from whichever sensor sees a person first
if (s1EdgeRise) {
// starting bottom -> going UP
startSequence(UP);
} else if (s2EdgeRise) {
// starting top -> going DOWN
startSequence(DOWN);
}
break;
case ON_CASCADE:
if (now – lastStepMs >= (unsigned long)set_shift) {
lastStepMs = now;
if (dir == UP) {
// Turn ON next LED from bottom to top
digitalWrite(OutPins[stepIndex], HIGH);
stepIndex++;
if (stepIndex >= (int)N_STEPS) {
startWaiting(); // wait to see the opposite sensor
}
} else { // DOWN
// Turn ON next LED from top to bottom
digitalWrite(OutPins[stepIndex], HIGH);
stepIndex–;
if (stepIndex < 0) {
startWaiting();
}
}
}
break;
case WAITING_OPPOSITE:
// Wait for the opposite end sensor or timeout, then cascade OFF
if (dir == UP) {
if (s2EdgeRise) {
startOffCascade();
} else if ((long)(now – waitDeadline) >= 0) {
// Timeout fallback
startOffCascade();
}
} else { // DOWN
if (s1EdgeRise) {
startOffCascade();
} else if ((long)(now – waitDeadline) >= 0) {
startOffCascade();
}
}
break;
case OFF_CASCADE:
if (now – lastStepMs >= (unsigned long)set_shift) {
lastStepMs = now;
if (dir == UP) {
// Turn OFF from top to bottom
digitalWrite(OutPins[stepIndex], LOW);
stepIndex–;
if (stepIndex < 0) {
finishSequence();
}
} else { // DOWN
// Turn OFF from bottom to top
digitalWrite(OutPins[stepIndex], LOW);
stepIndex++;
if (stepIndex >= (int)N_STEPS) {
finishSequence();
}
}
}
break;
}
}