Luci scale in sequenza con Arduino: accensione automatica bidirezionale
Realizziamo un sistema completo che accende progressivamente le luci dei gradini dal basso verso l’alto oppure dall’alto verso il basso, usando due pulsanti o due sensori di presenza.
Configurazione scelta
La versione base controlla 10 gradini indipendenti. Per ogni gradino Arduino fornisce soltanto il segnale di comando: LED ad alta potenza, strisce LED o vere lampade devono essere pilotati attraverso MOSFET, driver o relè appropriati.
Gli ingressi possono essere due semplici pulsanti oppure due sensori PIR. Il software permette di scegliere il tipo di segnale modificando una sola costante.
Materiale necessario per il prototipo
🧰 Kit Arduino consigliato
Un kit completo è utile per avere breadboard, cavetti, resistenze, LED e molti componenti di base per realizzare prima il prototipo a bassa tensione.
👉 Guarda il kit Arduino consigliatoLink di affiliazione Amazon: in qualità di Affiliato Amazon ricevo un guadagno dagli acquisti idonei, senza alcun costo aggiuntivo per te.
📘 Vuoi imparare Arduino passo dopo passo?
Se vuoi approfondire e realizzare progetti più completi, puoi dare un’occhiata anche ai miei libri dedicati ad Arduino. All’interno troverai spiegazioni semplici, schemi chiari e molti progetti pratici pensati per chi parte da zero.
👉 Scopri i miei libri su ArduinoLink di affiliazione Amazon: in qualità di Affiliato Amazon ricevo un guadagno dagli acquisti idonei, senza alcun costo aggiuntivo per te.
Collegamenti del prototipo
| Funzione | Pin Arduino |
|---|---|
| Gradino 1 | D2 |
| Gradino 2 | D3 |
| Gradino 3 | D4 |
| Gradino 4 | D5 |
| Gradino 5 | D6 |
| Gradino 6 | D7 |
| Gradino 7 | D8 |
| Gradino 8 | D9 |
| Gradino 9 | D10 |
| Gradino 10 | D11 |
| Sensore/pulsante lato basso | A0 |
| Sensore/pulsante lato alto | A1 |
Uso con due pulsanti
Collega ogni pulsante tra il relativo ingresso e GND. Il programma usa le resistenze di pull-up interne di Arduino, quindi non sono necessarie resistenze esterne sugli ingressi.
const bool INPUT_ACTIVE_LOW = true;Uso con due sensori PIR
Se usi sensori di presenza con uscita digitale HIGH quando rilevano movimento, porta l’uscita del sensore su A0 o A1 e modifica:
const bool INPUT_ACTIVE_LOW = false;Codice Arduino completo
/*
LUCI SCALE IN SEQUENZA CON ARDUINO UNO
Versione completa bidirezionale e non bloccante
FUNZIONAMENTO
- Sensore/pulsante BASSO: accensione dal gradino 1 al gradino 10.
- Sensore/pulsante ALTO: accensione dal gradino 10 al gradino 1.
- Dopo un tempo di permanenza, le luci si spengono nella stessa direzione.
- La sequenza usa millis(): Arduino continua a leggere gli ingressi.
USCITE GRADINI:
D2 -> gradino 1
D3 -> gradino 2
D4 -> gradino 3
D5 -> gradino 4
D6 -> gradino 5
D7 -> gradino 6
D8 -> gradino 7
D9 -> gradino 8
D10 -> gradino 9
D11 -> gradino 10
INGRESSI:
A0 -> comando/sensore lato BASSO
A1 -> comando/sensore lato ALTO
Per pulsanti:
- collega il pulsante tra A0/A1 e GND
- lascia INPUT_ACTIVE_LOW = true
Per sensori PIR con uscita HIGH:
- collega OUT a A0/A1
- imposta INPUT_ACTIVE_LOW = false
IMPORTANTE:
Le uscite Arduino NON devono alimentare direttamente lampade,
strisce LED potenti o carichi a 230 V.
Usa MOSFET, driver o relè adeguati.
*/
const byte NUM_GRADINI = 10;
// Pin delle 10 uscite
const byte luci[NUM_GRADINI] = {
2, 3, 4, 5, 6,
7, 8, 9, 10, 11
};
// Ingressi di comando
const byte INGRESSO_BASSO = A0;
const byte INGRESSO_ALTO = A1;
// true = pulsanti verso GND con INPUT_PULLUP
// false = sensori che portano HIGH quando rilevano presenza
const bool INPUT_ACTIVE_LOW = true;
// Se i tuoi moduli di uscita sono active LOW,
// inverti questi due valori.
const byte LUCE_ON = HIGH;
const byte LUCE_OFF = LOW;
// Tempo tra un gradino e il successivo
const unsigned long TEMPO_PASSO = 180;
// Quanto restano accese tutte le luci
const unsigned long TEMPO_ACCESE = 10000;
// Antirimbalzo / filtro ingresso
const unsigned long DEBOUNCE_MS = 60;
enum Stato {
ATTESA,
ACCENSIONE,
PERMANENZA,
SPEGNIMENTO
};
Stato stato = ATTESA;
// +1 = dal basso verso l'alto
// -1 = dall'alto verso il basso
int direzione = +1;
// Indice usato durante le sequenze
int indice = 0;
unsigned long ultimoPasso = 0;
unsigned long inizioPermanenza = 0;
// Variabili per evitare retrigger continui
bool ultimoBasso = false;
bool ultimoAlto = false;
unsigned long ultimoCambioBasso = 0;
unsigned long ultimoCambioAlto = 0;
void setup() {
// Configura tutte le uscite
for (byte i = 0; i < NUM_GRADINI; i++) {
pinMode(luci[i], OUTPUT);
digitalWrite(luci[i], LUCE_OFF);
}
// Configurazione ingressi
if (INPUT_ACTIVE_LOW) {
pinMode(INGRESSO_BASSO, INPUT_PULLUP);
pinMode(INGRESSO_ALTO, INPUT_PULLUP);
} else {
pinMode(INGRESSO_BASSO, INPUT);
pinMode(INGRESSO_ALTO, INPUT);
}
}
void loop() {
// Legge continuamente i due ingressi
bool triggerBasso = ingressoAttivo(
INGRESSO_BASSO,
ultimoBasso,
ultimoCambioBasso
);
bool triggerAlto = ingressoAttivo(
INGRESSO_ALTO,
ultimoAlto,
ultimoCambioAlto
);
switch (stato) {
// ------------------------------------------------
// ATTESA
// ------------------------------------------------
case ATTESA:
if (triggerBasso) {
avviaSequenza(+1);
}
else if (triggerAlto) {
avviaSequenza(-1);
}
break;
// ------------------------------------------------
// ACCENSIONE PROGRESSIVA
// ------------------------------------------------
case ACCENSIONE:
if (millis() - ultimoPasso >= TEMPO_PASSO) {
ultimoPasso = millis();
digitalWrite(luci[indice], LUCE_ON);
indice += direzione;
// Fine della sequenza di accensione
if (
indice >= NUM_GRADINI ||
indice < 0
) {
stato = PERMANENZA;
inizioPermanenza = millis();
}
}
break;
// ------------------------------------------------
// TUTTE LE LUCI RESTANO ACCESE
// ------------------------------------------------
case PERMANENZA:
// Se passa qualcuno dall'altro lato mentre
// le luci sono accese, prolunga il tempo.
if (triggerBasso || triggerAlto) {
inizioPermanenza = millis();
}
if (
millis() - inizioPermanenza >= TEMPO_ACCESE
) {
// Per uno spegnimento visivamente naturale
// partiamo dallo stesso lato da cui
// era iniziata l'accensione.
indice = (direzione == +1)
? 0
: NUM_GRADINI - 1;
ultimoPasso = millis();
stato = SPEGNIMENTO;
}
break;
// ------------------------------------------------
// SPEGNIMENTO PROGRESSIVO
// ------------------------------------------------
case SPEGNIMENTO:
// Se qualcuno arriva mentre stiamo spegnendo,
// riaccendiamo la scala nella sua direzione.
if (triggerBasso) {
avviaSequenza(+1);
break;
}
if (triggerAlto) {
avviaSequenza(-1);
break;
}
if (millis() - ultimoPasso >= TEMPO_PASSO) {
ultimoPasso = millis();
digitalWrite(luci[indice], LUCE_OFF);
indice += direzione;
if (
indice >= NUM_GRADINI ||
indice < 0
) {
stato = ATTESA;
}
}
break;
}
}
// ----------------------------------------------------
// Avvia la sequenza nella direzione richiesta
// ----------------------------------------------------
void avviaSequenza(int nuovaDirezione) {
direzione = nuovaDirezione;
indice = (direzione == +1)
? 0
: NUM_GRADINI - 1;
ultimoPasso = millis();
stato = ACCENSIONE;
}
// ----------------------------------------------------
// Restituisce true SOLO sul fronte di attivazione
// dell'ingresso, con un piccolo filtro antirimbalzo.
// ----------------------------------------------------
bool ingressoAttivo(
byte pin,
bool &ultimoStato,
unsigned long &ultimoCambio
) {
bool lettura;
if (INPUT_ACTIVE_LOW) {
lettura = (digitalRead(pin) == LOW);
} else {
lettura = (digitalRead(pin) == HIGH);
}
// Se lo stato cambia, memorizziamo il momento
if (lettura != ultimoStato) {
if (millis() - ultimoCambio >= DEBOUNCE_MS) {
ultimoCambio = millis();
ultimoStato = lettura;
// Genera trigger solo quando passa a "attivo"
if (lettura) {
return true;
}
}
}
return false;
}
Perché non usiamo una lunga serie di delay()
Con molti delay() Arduino resterebbe bloccato durante tutta la sequenza. In quel periodo non potrebbe reagire bene a una persona che entra dalla parte opposta della scala.
Il programma usa invece millis() e una piccola macchina a stati con quattro fasi:
- ATTESA: nessuna sequenza in corso;
- ACCENSIONE: le luci vengono attivate una alla volta;
- PERMANENZA: tutte le luci restano accese;
- SPEGNIMENTO: le luci vengono spente progressivamente.
Accensione dal basso
Quando viene attivato l’ingresso A0, Arduino parte dal gradino 1 e procede fino al gradino 10. Ogni uscita viene attivata dopo circa 180 ms dalla precedente.
Accensione dall’alto
Quando viene attivato A1, l’indice parte dall’ultimo gradino e procede a ritroso. La stessa scala può quindi reagire correttamente in entrambe le direzioni.
Tempo di permanenza
Quando tutti i gradini sono accesi, il programma attende 10 secondi prima di iniziare lo spegnimento.
const unsigned long TEMPO_ACCESE = 10000;Puoi aumentarlo, per esempio a 20000 per 20 secondi.
Velocità della sequenza
const unsigned long TEMPO_PASSO = 180;Con valori inferiori la scala si illumina più velocemente. Con valori maggiori l’effetto diventa più lento e scenografico.
Cosa succede se arriva un’altra persona?
Durante la fase in cui tutte le luci sono accese, un nuovo rilevamento prolunga il tempo prima dello spegnimento. Se invece Arduino sta già spegnendo le luci e rileva un nuovo passaggio, interrompe lo spegnimento e avvia nuovamente la sequenza nella direzione richiesta.
Passare dai LED alle vere luci dei gradini
Per un impianto reale consiglierei strisce LED a bassa tensione, ad esempio una sezione indipendente per ogni gradino. Ogni uscita di Arduino dovrebbe comandare un MOSFET logic-level dimensionato per la corrente del relativo tratto LED.
E se la scala ha più di 10 gradini?
La soluzione più ordinata è aggiungere espansori di uscita, per esempio registri a scorrimento o moduli I/O, invece di occupare un pin Arduino per ogni gradino. Il software può mantenere la stessa logica bidirezionale.
Versione con dissolvenza
Per ottenere un effetto ancora più elegante si può sostituire la semplice accensione ON/OFF con una dissolvenza progressiva. Per molti gradini, però, conviene usare un driver PWM multicanale perché Arduino Uno dispone di un numero limitato di uscite PWM hardware.
Debug rapido
- prova inizialmente il circuito con dieci LED e resistenze da 220 Ω;
- verifica separatamente il comando dal basso e quello dall’alto;
- se usi pulsanti, lascia
INPUT_ACTIVE_LOW = true; - se usi PIR con uscita HIGH, imposta
INPUT_ACTIVE_LOW = false; - se un modulo relè lavora al contrario, inverti
LUCE_ONeLUCE_OFF; - modifica
TEMPO_PASSOper regolare la velocità ; - modifica
TEMPO_ACCESEper regolare il tempo di permanenza.
Varianti del progetto
- aggiungere un sensore crepuscolare per funzionare solo al buio;
- usare due sensori ToF invece dei PIR;
- aggiungere dissolvenza graduale;
- controllare 15 o 20 gradini con un’espansione di uscite;
- aggiungere una modalità luce notturna permanente;
- impostare velocità diversa tra accensione e spegnimento;
- aggiungere ESP32 per controllo e configurazione via Wi-Fi.
Cosa devi ricordare
- La versione base controlla 10 gradini.
- A0 rileva il passaggio dal basso, A1 quello dall’alto.
- La direzione di accensione dipende dal lato da cui arriva la persona.
- La sequenza è gestita con millis() e non blocca Arduino.
- I nuovi rilevamenti possono prolungare o riavviare la sequenza.
- Per strisce LED reali servono driver o MOSFET adeguati.
Domande frequenti
Posso usare due pulsanti invece dei sensori?
Sì. Il codice è già predisposto. Collega ciascun pulsante tra A0/A1 e GND e lascia INPUT_ACTIVE_LOW = true.
Posso usare due sensori PIR?
Sì. Collega le uscite digitali dei PIR agli ingressi A0 e A1 e, se forniscono HIGH quando rilevano movimento, imposta INPUT_ACTIVE_LOW = false.
Posso controllare più di 10 gradini?
Sì. Per numeri superiori conviene usare un’espansione di uscite, così non si esauriscono rapidamente i pin di Arduino Uno.
Posso collegare direttamente una striscia LED ai pin Arduino?
No. Usa un MOSFET o un driver adeguato e un alimentatore separato dimensionato per la corrente richiesta.

