Allenamento per l’esame di maturità
Percorso di laboratorio con Arduino per studenti di quinta ITIS

Obiettivo didattico
Realizzare una piccola interfaccia utente con un solo pulsante. Il sistema deve riconoscere una pressione breve, un doppio clic, una pressione lunga e un timeout di inattività. L’attività allena la gestione degli eventi, l’antirimbalzo software e l’uso di millis() senza bloccare il programma.
Nota importante
La comprensione del funzionamento di questo esercizio permetterà lo svolgimento in autonomia dell’esercizio aggiuntivo che trovate al fondo di questa scheda di lavoro.
Materiali suggeriti
- Arduino UNO R3 o UNO R4;
- 1 pulsante;
- 3 LED;
- 3 resitori (per i LED);
- breadboard;
- cavetti jumper.
Schema di collegamento

Richiamo teorico
Per leggere correttamente un pulsante reale bisogna evitare il rimbalzo dei contatti. Si usa quindi una variabile con l’ultima lettura, una temporizzazione di stabilizzazione e una logica a eventi. Con millis() si misura il tempo senza usare delay(), così il programma può continuare a controllare altri compiti.
Schema logico dell’attività
Il programma legge continuamente il pulsante. Se la lettura cambia, attende il tempo di debounce. Quando il livello è stabile, riconosce pressione e rilascio. Dal tempo trascorso ricava l’evento: clic breve, doppio clic oppure pressione lunga. Se non accade nulla per molti secondi, attiva un LED di timeout.
Diagramma di flusso
Diagramma di flusso Mermaid
Ricordo che per realizzare il diagramma di flusso, copiate il codice Mermaid seguendo le indicazioni della lezione: Progettare bene, programmare meglio: diagrammi di flusso – cos’è il formato Mermaid? – Lezione 2/5
flowchart TD
A[Inizio] --> B[Configura pin e variabili]
B --> C[Leggi pulsante]
C --> D{Lettura cambiata?}
D -- Sì --> E[Salva istante variazione]
D -- No --> F{Tempo debounce trascorso?}
E --> F
F -- No --> C
F -- Sì --> G{Stato stabile cambiato?}
G -- No --> H{Timeout inattività?}
G -- Sì --> I{Pulsante premuto?}
I -- Sì --> J[Memorizza istante pressione]
I -- No --> K[Calcola durata pressione]
K --> L{Pressione lunga?}
L -- Sì --> M[Genera evento LONG]
L -- No --> N{Secondo clic entro finestra?}
N -- Sì --> O[Genera evento DOUBLE]
N -- No --> P[Attendi possibile secondo clic]
H -- Sì --> Q[Attiva LED timeout]
H -- No --> C
J --> C
M --> C
O --> C
P --> C
Q --> C
Programma
/*
Prof. Maffucci Michele
Esercizio 1: Pulsante singolo con antirimbalzo, doppio clic, pressione lunga e timeout
*/
// ---------------------------
// Definizione dei pin usati
// ---------------------------
const int PIN_PULSANTE = 2;
const int PIN_LED_BREVE = 8;
const int PIN_LED_DOPPIO = 9;
const int PIN_LED_TIMEOUT = 10;
// ---------------------------
// Costanti temporali
// ---------------------------
const unsigned long TEMPO_DEBOUNCE = 30;
const unsigned long SOGLIA_PRESSIONE_LUNGA = 800;
const unsigned long FINESTRA_DOPPIO_CLICK = 350;
const unsigned long TEMPO_TIMEOUT = 5000;
// ---------------------------
// Variabili per antirimbalzo
// ---------------------------
int ultimaLetturaGrezza = HIGH;
int statoStabile = HIGH;
unsigned long istanteUltimaVariazione = 0;
// ---------------------------
// Variabili per eventi utente
// ---------------------------
bool attesaSecondoClick = false;
unsigned long istantePrimoClick = 0;
unsigned long istantePressione = 0;
unsigned long ultimoEventoUtente = 0;
void setup() {
// Il pulsante usa la resistenza interna di pull-up.
pinMode(PIN_PULSANTE, INPUT_PULLUP);
// I tre LED rappresentano tre eventi diversi.
pinMode(PIN_LED_BREVE, OUTPUT);
pinMode(PIN_LED_DOPPIO, OUTPUT);
pinMode(PIN_LED_TIMEOUT, OUTPUT);
// All'avvio tutti i LED sono spenti.
digitalWrite(PIN_LED_BREVE, LOW);
digitalWrite(PIN_LED_DOPPIO, LOW);
digitalWrite(PIN_LED_TIMEOUT, LOW);
// La seriale aiuta a vedere quale evento viene riconosciuto.
Serial.begin(9600);
// Salvo il tempo iniziale come ultimo evento.
ultimoEventoUtente = millis();
}
void loop() {
// Leggo il pulsante in forma grezza.
int letturaCorrente = digitalRead(PIN_PULSANTE);
// Se la lettura è cambiata rispetto alla precedente,
// aggiorno il tempo della variazione.
if (letturaCorrente != ultimaLetturaGrezza) {
istanteUltimaVariazione = millis();
ultimaLetturaGrezza = letturaCorrente;
}
// Se il segnale è stabile da abbastanza tempo,
// posso considerarlo affidabile.
if ((millis() - istanteUltimaVariazione) >= TEMPO_DEBOUNCE) {
// Se anche lo stato stabile è cambiato, ho un nuovo evento.
if (letturaCorrente != statoStabile) {
statoStabile = letturaCorrente;
ultimoEventoUtente = millis();
digitalWrite(PIN_LED_TIMEOUT, LOW);
// Transizione verso livello basso = pulsante premuto.
if (statoStabile == LOW) {
istantePressione = millis();
}
// Transizione verso livello alto = pulsante rilasciato.
else {
unsigned long durataPressione = millis() - istantePressione;
// Se la durata supera la soglia, classifico come pressione lunga.
if (durataPressione >= SOGLIA_PRESSIONE_LUNGA) {
Serial.println("Evento: PRESSIONE LUNGA");
lampeggiaLed(PIN_LED_DOPPIO, 2, 120);
attesaSecondoClick = false;
}
else {
// Se sto già aspettando un secondo clic,
// verifico la finestra temporale.
if (attesaSecondoClick == true &&
(millis() - istantePrimoClick) <= FINESTRA_DOPPIO_CLICK) {
Serial.println("Evento: DOPPIO CLICK");
lampeggiaLed(PIN_LED_DOPPIO, 1, 250);
attesaSecondoClick = false;
}
else {
// Primo clic breve: non lo confermo subito,
// perché potrei ricevere un secondo clic.
attesaSecondoClick = true;
istantePrimoClick = millis();
}
}
}
}
}
// Se è in attesa un secondo clic e la finestra è scaduta,
// confermo il clic breve.
if (attesaSecondoClick == true && (millis() - istantePrimoClick) > FINESTRA_DOPPIO_CLICK) {
Serial.println("Evento: CLICK BREVE");
lampeggiaLed(PIN_LED_BREVE, 1, 250);
attesaSecondoClick = false;
}
// Se passa troppo tempo senza eventi, attivo il LED di timeout.
if ((millis() - ultimoEventoUtente) >= TEMPO_TIMEOUT) {
digitalWrite(PIN_LED_TIMEOUT, HIGH);
}
}
// ----------------------------------------------------------
// Funzione di servizio: fa lampeggiare un LED alcune volte.
// In questa attività è accettabile usare delay() perché
// la funzione serve solo come feedback visivo di conferma.
// ----------------------------------------------------------
void lampeggiaLed(int pinLed, int numeroLampi, int durata) {
for (int i = 0; i < numeroLampi; i = i + 1) {
digitalWrite(pinLed, HIGH);
delay(durata);
digitalWrite(pinLed, LOW);
delay(durata);
}
}
01. Spiegazione generale
Come indicato nell'obiettivo il programma realizza una piccola interfaccia utente a un solo pulsante.
Con un unico ingresso digitale il programma riesce a distinguere:
- clic breve
- doppio clic
- pressione lunga
- timeout di inattività
L’aspetto più importante è che tutto viene gestito senza bloccare il loop() con delay(), tranne nella funzione finale di lampeggio che serve solo come feedback visivo.
Il programma non si limita a leggere se il pulsante è premuto oppure no.
Fa qualcosa di più:
- legge continuamente il pulsante;
- filtra il rimbalzo meccanico con un antirimbalzo software;
- riconosce il momento in cui il pulsante viene premuto;
- riconosce il momento in cui viene rilasciato;
- misura la durata della pressione con millis();
- decide se si tratta di:
- pressione lunga,
- primo clic breve in attesa di un secondo,
- doppio clic,
- clic breve definitivo;
- se non accade nulla per un certo tempo, accende il LED di timeout.
In pratica, il codice si comporta come una macchina a stati semplificata, anche se non è scritta formalmente con switch e stati nominati.
Entriamo ora nel dettaglio del codice
02. Collegamento del pulsante con INPUT_PULLUP
const int PIN_PULSANTE = 2;
Nel setup() troviamo:
pinMode(PIN_PULSANTE, INPUT_PULLUP);
Questo significa che Arduino usa la resistenza di pull-up interna.
Di conseguenza:
- quando il pulsante non è premuto, il pin legge
HIGH; - quando il pulsante è premuto, il pin va a
LOW.
Questa logica è molto importante, perché nel codice:
- LOW = pulsante premuto
- HIGH = pulsante rilasciato
Quindi la logica è "invertita" rispetto a quella che molto spersso siamo abituati a considerare immaginano all’inizio del nostro percorso di studi.
03. Definizione dei LED
const int PIN_LED_BREVE = 8; const int PIN_LED_DOPPIO = 9; const int PIN_LED_TIMEOUT = 10;
I LED servono come feedback:
- LED su pin 8 > clic breve
- LED su pin 9 > doppio clic e anche pressione lunga
- LED su pin 10 > timeout di inattività
Importante: in questo sketch la pressione lunga non ha un LED dedicato.
Viene segnalata con:
lampeggiaLed(PIN_LED_DOPPIO, 2, 120);
cioè usando lo stesso LED del doppio clic, ma con un pattern diverso.
04. Le costanti temporali
const unsigned long TEMPO_DEBOUNCE = 30; const unsigned long SOGLIA_PRESSIONE_LUNGA = 800; const unsigned long FINESTRA_DOPPIO_CLICK = 350; const unsigned long TEMPO_TIMEOUT = 5000;
Queste costanti definiscono il comportamento del sistema.
TEMPO_DEBOUNCE = 30
È il tempo minimo, in millisecondi, per considerare stabile una variazione del pulsante.
Serve a eliminare i falsi cambiamenti dovuti al rimbalzo dei contatti.
SOGLIA_PRESSIONE_LUNGA = 800
Se il pulsante resta premuto per almeno 800 ms, il gesto viene classificato come pressione lunga.
FINESTRA_DOPPIO_CLICK = 350
Dopo un primo clic breve, il programma aspetta fino a 350 ms per vedere se arriva un secondo clic. Se arriva entro questa finestra, allora riconosce un doppio clic.
TEMPO_TIMEOUT = 5000
Se per 5 secondi non accade nessun evento legato al pulsante, si accende il LED di timeout.
05. Variabili per l’antirimbalzo
int ultimaLetturaGrezza = HIGH; int statoStabile = HIGH; unsigned long istanteUltimaVariazione = 0;
Queste tre variabili sono il cuore del filtro software.
ultimaLetturaGrezza
Memorizza l’ultima lettura immediata del pin, senza alcun filtro, serve a capire se il segnale sta cambiando.
statoStabile
Rappresenta lo stato "ufficiale" del pulsante, cioè quello considerato valido dopo il debounce.
istanteUltimaVariazione
Salva il momento in cui il segnale grezzo ha cambiato valore per l’ultima volta.
Da lì il programma comincia a contare i 30 ms di stabilizzazione.
06. Variabili per gli eventi utente
bool attesaSecondoClick = false; unsigned long istantePrimoClick = 0; unsigned long istantePressione = 0; unsigned long ultimoEventoUtente = 0;
Servono per classificare il tipo di interazione.
attesaSecondoClick
Vale true quando il sistema ha ricevuto un primo clic breve e sta aspettando di capire se arriverà un secondo clic.
istantePrimoClick
Memorizza il momento in cui si è concluso il primo clic breve.
istantePressione
Memorizza il momento in cui il pulsante viene premuto, serve per calcolare la durata della pressione.
ultimoEventoUtente
Tiene traccia dell’ultimo momento in cui il pulsante ha generato un’attività valida.
Serve per verificare il timeout.
07. Impostazioni nel setup()
void setup() {
pinMode(PIN_PULSANTE, INPUT_PULLUP);
pinMode(PIN_LED_BREVE, OUTPUT);
pinMode(PIN_LED_DOPPIO, OUTPUT);
pinMode(PIN_LED_TIMEOUT, OUTPUT);
digitalWrite(PIN_LED_BREVE, LOW);
digitalWrite(PIN_LED_DOPPIO, LOW);
digitalWrite(PIN_LED_TIMEOUT, LOW);
Serial.begin(9600);
ultimoEventoUtente = millis();
}
Nel setup() il programma:
- configura il pulsante con pull-up interna;
- configura i LED come uscite;
- spegne tutti i LED all’avvio;
- attiva la seriale per monitorare gli eventi;
- salva il tempo iniziale in ultimoEventoUtente.
Quest’ultimo passaggio evita che il timeout scatti immediatamente dopo l’accensione.
08. Prima fase del loop(): lettura grezza del pulsante
int letturaCorrente = digitalRead(PIN_PULSANTE);
Qui Arduino legge il pin così com’è in quell’istante.
È una lettura "grezza", quindi ancora soggetta a rimbalzi e oscillazioni.
09. Rilevazione di una variazione grezza
if (letturaCorrente != ultimaLetturaGrezza) {
istanteUltimaVariazione = millis();
ultimaLetturaGrezza = letturaCorrente;
}
Se la lettura corrente è diversa dalla precedente, il programma capisce che il segnale è cambiato.
In quel momento:
- aggiorna il tempo dell’ultima variazione;
- salva il nuovo valore come ultima lettura grezza.
Questa parte non conferma ancora il nuovo stato.
Dice solo: "il segnale si è mosso, adesso aspetto per vedere se si stabilizza".
È proprio qui che inizia l’antirimbalzo.
10. Verifica del tempo di debounce
if ((millis() - istanteUltimaVariazione) >= TEMPO_DEBOUNCE) {
Questa condizione controlla se sono passati almeno 30 ms dall’ultima variazione grezza.
Se sì, il programma può considerare il segnale abbastanza stabile da essere affidabile.
Questa è l’idea fondamentale del debounce software:
- un cambiamento immediato non viene accettato subito;
- viene accettato solo se rimane stabile per un certo tempo.
11. Cambio dello stato stabile
if (letturaCorrente != statoStabile) {
statoStabile = letturaCorrente;
ultimoEventoUtente = millis();
digitalWrite(PIN_LED_TIMEOUT, LOW);
Se la lettura stabile è diversa dallo stato stabile memorizzato, allora abbiamo un vero evento.
Il programma fa tre cose:
- aggiorna
statoStabile; - aggiorna
ultimoEventoUtente; - spegne il LED di timeout.
Questo significa che il timeout viene azzerato ogni volta che il pulsante compie una transizione valida, sia in pressione sia in rilascio.
12. Caso 1: pulsante premuto
if (statoStabile == LOW) {
istantePressione = millis();
}
Poiché il pulsante usa INPUT_PULLUP, il valore LOW significa che il pulsante è stato premuto.
In quel momento il programma salva l’istante di inizio pressione.
Non decide ancora che evento sia: si limita a registrare il tempo iniziale.
13. Caso 2: pulsante rilasciato
else {
unsigned long durataPressione = millis() - istantePressione;
Quando lo stato torna HIGH, il pulsante è stato rilasciato.
Ora il programma può calcolare quanto è durata la pressione.
Questo è un punto molto importante: la pressione lunga viene riconosciuta al rilascio, non mentre il pulsante è ancora tenuto premuto.
14. Riconoscimento della pressione lunga
if (durataPressione >= SOGLIA_PRESSIONE_LUNGA) {
Serial.println("Evento: PRESSIONE LUNGA");
lampeggiaLed(PIN_LED_DOPPIO, 2, 120);
attesaSecondoClick = false;
}
Se la durata è almeno 800 ms, il gesto viene classificato come pressione lunga.
Azioni eseguite:
- stampa il messaggio sulla seriale;
- fa lampeggiare due volte il LED del doppio clic;
- annulla l’eventuale attesa di un secondo clic.
Quest’ultimo passaggio è necessario perché una pressione lunga non deve essere confusa con la prima metà di un doppio clic.
15. Caso di pressione breve: inizia la logica clic/doppio clic
if (attesaSecondoClick == true &&
(millis() - istantePrimoClick) <= FINESTRA_DOPPIO_CLICK) {
Serial.println("Evento: DOPPIO CLICK");
lampeggiaLed(PIN_LED_DOPPIO, 1, 250);
attesaSecondoClick = false;
}
Se il programma era già in attesa di un secondo clic e questo secondo clic arriva entro 350 ms, allora riconosce il doppio clic.
Azioni:
- stampa sulla seriale;
- lampeggia il LED del doppio clic;
- chiude la fase di attesa.
16. Primo clic breve: nessuna conferma immediata
else {
attesaSecondoClick = true;
istantePrimoClick = millis();
}
Se non c’è un doppio clic già in corso, il programma interpreta la pressione appena conclusa come primo clic candidato.
Però non lo conferma subito.
Si limita a dire:
- "sto aspettando un secondo clic"
- "memorizzo l’istante del primo clic"
Questo è un passaggio essenziale:
un clic breve non può essere confermato subito, perché bisogna lasciare il tempo all’utente per fare l’eventuale secondo clic.
17. Conferma del clic breve fuori dal blocco principale
if (attesaSecondoClick == true && (millis() - istantePrimoClick) > FINESTRA_DOPPIO_CLICK) {
Serial.println("Evento: CLICK BREVE");
lampeggiaLed(PIN_LED_BREVE, 1, 250);
attesaSecondoClick = false;
}
Questa parte viene eseguita continuamente nel loop().
Se il sistema è in attesa del secondo clic ma la finestra di 350 ms è scaduta, allora il programma conclude che il secondo clic non arriverà.
A quel punto il primo clic viene confermato come clic breve.
Questo è il motivo per cui il clic breve viene rilevato con un piccolo ritardo intenzionale:
serve a non confonderlo con un doppio clic.
18. Gestione del timeout di inattività
if ((millis() - ultimoEventoUtente) >= TEMPO_TIMEOUT) {
digitalWrite(PIN_LED_TIMEOUT, HIGH);
}
Se passano 5 secondi senza eventi validi, il LED di timeout si accende.
Il timeout non usa delay() si basa anch’esso su millis(), quindi il programma continua a funzionare normalmente e può ancora leggere il pulsante.
Appena si verifica un nuovo evento stabile sul pulsante, il LED di timeout viene spento qui:
digitalWrite(PIN_LED_TIMEOUT, LOW);
19. La funzione lampeggiaLed()
void lampeggiaLed(int pinLed, int numeroLampi, int durata) {
for (int i = 0; i < numeroLampi; i = i + 1) {
digitalWrite(pinLed, HIGH);
delay(durata);
digitalWrite(pinLed, LOW);
delay(durata);
}
}
Questa funzione serve solo a dare un feedback visivo.
Riceve tre parametri:
- pinLed > quale LED usare
- numeroLampi > quante volte farlo lampeggiare
- durata > tempo di accensione e spegnimento
Esempio:
lampeggiaLed(PIN_LED_BREVE, 1, 250);
significa:
- usa il LED del clic breve,
- fai un lampeggio,
- 250 ms acceso e 250 ms spento.
Importante
Qui è presente delay(), quindi durante il lampeggio il microcontrollore resta temporaneamente bloccato.
In questa esercitazione è accettabile, perché il lampeggio è usato solo come conferma visiva.
Per una versione più evoluta, si potrebbe sostituire anche questa funzione con una gestione non bloccante basata su millis().
Osservazioni tecniche importanti
- La pressione lunga è riconosciuta al rilascio
Nel codice attuale il sistema capisce che la pressione è lunga solo quando il pulsante viene lasciato. Non mentre è ancora tenuto premuto. - Il clic breve è volutamente ritardato
Non viene segnalato subito, perché il programma deve aspettare per capire se arriverà il secondo clic. - Il debounce lavora sulle transizioni
Il codice non si fida di una variazione immediata, ma la accetta solo se resta stabile per almeno 30 ms. - Il timeout misura l’inattività dell’utente
Viene azzerato ogni volta che il pulsante produce una transizione stabile. - Il lampeggio dei LED usa ancora
delay()
La logica principale è non bloccante, ma il feedback visivo è realizzato in modo semplice e bloccante
Sequenza completa di funzionamento
Riassumo per chiarezza.
Dal punto di vista logico, il programma lavora così:
Caso A: clic breve
-
- il pulsante viene premuto;
- il segnale si stabilizza;
- il programma salva
istantePressione; - il pulsante viene rilasciato;
- la durata è inferiore a 800 ms;
- il programma non conferma subito;
- aspetta 350 ms;
- se non arriva un secondo clic, conferma "CLICK BREVE".
Caso B: doppio clic
- avviene un primo clic breve;
- il programma entra in
attesaSecondoClick = true; - arriva un secondo clic entro 350 ms;
- il programma riconosce "DOPPIO CLICK".
Caso C: pressione lunga
- il pulsante viene premuto;
- il programma salva l’istante iniziale;
- il pulsante resta premuto almeno 800 ms;
- al rilascio, il programma misura la durata;
- riconosce "PRESSIONE LUNGA".
Caso D: timeout
- non avvengono eventi per 5 secondi;
- il LED di timeout si accende;
- al primo evento successivo, il LED si spegne.
In sintesi
Il programma realizza una gestione evoluta di un solo pulsante usando la lettura continua del pin, l’antirimbalzo software e il controllo dei tempi tramite millis(). Quando il segnale del pulsante cambia, il codice attende un breve intervallo di stabilizzazione prima di accettare il nuovo stato. Se il pulsante viene premuto, memorizza l’istante di inizio; quando viene rilasciato, misura la durata della pressione per distinguere una pressione lunga da una breve. Nel caso delle pressioni brevi, il sistema non conferma subito il clic, ma attende una finestra temporale per verificare l’eventuale arrivo di un secondo clic. In questo modo riesce a discriminare clic breve, doppio clic e pressione lunga senza usare delay() nella logica principale. Inoltre, se per alcuni secondi non si verificano eventi, il programma attiva un LED di timeout che segnala l’inattività del sistema.
Esercizio aggiuntivo da svolgere in autonomia

Questo esercizio è basato sull’attività proposta nella lezione, ma richiede l’aggiunta di nuove funzionalità da sviluppare in autonomia. La soluzione verrà pubblicata in un post successivo, così da favorire il lavoro personale di analisi, progettazione e sperimentazione.
Consegna per lo studente
Partendo dall'esercizio principale, realizzare una versione evoluta in cui un solo pulsante
controlla tre profili di funzionamento del LED di stato:
- un clic breve cambia profilo;
- un doppio clic abilita o disabilita il lampeggio del LED di stato;
- una pressione lunga riporta il sistema al profilo 1 e riattiva il lampeggio;
- se l'utente non interagisce per almeno 8 secondi, si accende un LED di timeout.
Di seguito la sequenza logica utile per la realizzazione del diagramma di flusso.
- Inizializzazione di:
- pulsante,
- LED di stato,
- LED di timeout
- variabili temporali.
- lettura continua del pulsante con antirimbalzo software;
- riconoscimento dell'evento: clic breve, doppio clic oppure pressione lunga;
- aggiornamento del profilo o dello stato di abilitazione del lampeggio;
- controllo del timeout di inattività;
- gestione non bloccante del lampeggio in base al profilo selezionato;
Buon Coding a tutti 🙂

