Perché un pulsante con Arduino registra cinque pressioni invece di una?

I pulsanti non stabiliscono un contatto netto: le lamelle metalliche a molla rimbalzano fisicamente per diversi millisecondi, registrando transizioni multiple sui microcontrollori veloci. Ecco come funziona il rimbalzo dei contatti e come risolverlo senza delay().
Quando Arduino registra cinque o sei conteggi a fronte di una singola pressione fisica di un pulsante, il microcontrollore non è rotto e il codice non è impazzito. Il pulsante sta semplicemente rimbalzando a livello meccanico. Un pulsante tattile racchiude due sottili lamelle metalliche a molla che sbattono l'una contro l'altra quando vengono premute. A scala umana, il clic sembra istantaneo. Alla scala del microcontrollore — dove un comune ATmega328P a 16 MHz esegue circa sedici istruzioni ogni microsecondo — le lamelle di contatto entrano in collisione, rimbalzano, fanno nuovamente contatto e si stabilizzano nell'arco di una finestra temporale compresa tra due e venti millisecondi. Per Arduino, questo tremolio meccanico equivale a cinque pressioni distinte del pulsante.
Come si presenta davvero il rimbalzo meccanico dei contatti
In un circuito standard su breadboard che impiega un resistore di pull-down esterno oppure il resistore interno INPUT_PULLUP, il pin del microcontrollore monitora una transizione di tensione compresa tra 0V e 5V. Se il codice controlla il pin all'interno di un ciclo veloce void loop() mediante una semplice condizione:
if (digitalRead(buttonPin) == LOW) {
pressCount++;
}si verificano due problemi distinti contemporaneamente:
- Rilevamento di livello rispetto a rilevamento di fronte: il codice incrementa il conteggio a ogni singola iterazione del ciclo fintanto che il pulsante rimane premuto. Se il ciclo impiega 200 microsecondi per completarsi, tenere premuto il pulsante per mezzo secondo accumulerà 2.500 pressioni.
- Rimbalzo dei contatti (contact bounce): anche rilasciando l'interruttore istantaneamente, la tensione oscilla tra livello alto e basso diverse volte nell'arco di 5-15 millisecondi mentre le lamelle metalliche si stabilizzano. Il processore è ampiamente abbastanza veloce da intercettare ogni singolo rimbalzo come una transizione valida.
Il riflesso standard di chi è alle prime armi è affidarsi a delay(). Funziona in un test rudimentale, ma in qualsiasi progetto reale genera problemi ben più gravi di quello che intende risolvere.
Perché delay(50) degrada rovinosamente il comportamento del robot
Se diciamo ad Arduino di attendere cinquanta millisecondi subito dopo aver rilevato una pressione — delay(50); — l'interruttore esaurisce la fase di rimbalzo prima che il microcontrollore controlli di nuovo il pin. Il contatore ora avanza regolarmente di un'unità.
Tuttavia, delay() è una funzione bloccante. Interrompe completamente l'elaborazione delle istruzioni. Finché il chip si trova all'interno di quella pausa di 50 ms, non può aggiornare la modulazione di larghezza di impulso (PWM) dei motori, non può interrogare il pin di eco di un sensore a ultrasuoni, non può registrare il passaggio di un encoder e non può decodificare i comandi seriali in arrivo da un ESP32 o da un modulo Bluetooth. In un contatore statico, un ritardo bloccante si nota appena. In un robot su ruote o in una sbarra automatica, un blocco di 50 ms ogni volta che scatta un finecorsa provoca passaggi saltati, scatti nei motori e una navigazione instabile.
Il debouncing hardware — ovvero l'eliminazione dei rimbalzi tramite l'aggiunta di un condensatore ceramico da 100 nF sui contatti del pulsante o l'impiego di un filtro passa-basso RC con trigger di Schmitt — pulisce il segnale prima che raggiunga il microcontrollore. È una soluzione elettricamente elegante, ma richiede scorte aggiuntive in laboratorio, appesantisce il circuito su breadboard o con saldature e aumenta i costi dei componenti in una classe di 30 studenti. In quasi tutti i contesti scolastici, il tracciamento dello stato via software rappresenta la risposta più corretta.
La soluzione corretta: rilevamento del fronte e millis()
Risolvere il problema in modo pulito a livello di codice richiede due passaggi logici distinti: rilevare quando lo stato cambia (rilevamento del fronte o edge detection) e verificare che sia trascorso tempo a sufficienza dall'ultima transizione prima di accettarne una nuova (temporizzazione di antirimbalzo o debounce timing).
Questo approccio richiede tre variabili:
- La lettura attuale del pin.
- La lettura precedente del pin dalla precedente iterazione del ciclo.
- Una marca temporale (timestamp) di tipo unsigned long che registra quando lo stato è cambiato per l'ultima volta.
Un'implementazione affidabile e non bloccante si presenta così:
const int buttonPin = 2;
int buttonState = HIGH;
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 40; // millisecondi
int counter = 0;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
int reading = digitalRead(buttonPin);
// Verifica se lo stato del pin è cambiato per un rimbalzo o una pressione fisica
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
// Valuta lo stato solo se è rimasto stabile più a lungo del nostro intervallo di blocco
if ((millis() - lastDebounceTime) > debounceDelay) {
// Se lo stato confermato è cambiato
if (reading != buttonState) {
buttonState = reading;
// Si attiva solo alla pressione (fronte di discesa per INPUT_PULLUP)
if (buttonState == LOW) {
counter++;
Serial.print("Verified presses: ");
Serial.println(counter);
}
}
}
lastButtonState = reading;
// Il resto di loop() viene eseguito senza interruzioni né ritardi
}In questa architettura, millis() agisce come un orologio di sistema non bloccante. Il codice ispeziona continuamente il pin, ripristina il conto alla rovescia ogni volta che si verifica un'oscillazione e accetta un cambiamento di stato solo quando il pin rimane stabile per 40 millisecondi consecutivi. Tra un controllo e l'altro, il processore rimane libero al 100% per eseguire calcoli PID o monitorare gli arresti d'emergenza.
Come spiegarlo alla lavagna in dieci minuti
Gli studenti faticano a comprendere il debouncing quando viene presentato come una sequenza fitta di istruzioni condizionali. Per insegnarlo efficacemente, separiamo la meccanica dalla sintassi usando una semplice analogia da classe:
- La porta che sbatte: disegnate una pesante porta di legno che sbatte per chiudersi. Non si ferma di colpo contro lo stipite: la serratura vibra nello scrocco per una frazione di secondo. Spiegate che un pulsante è fatto di molle in ottone o acciaio che fanno esattamente la stessa cosa.
- La guardia giurata iperattiva: chiedete alla classe: "Se una guardia deve contare chiunque varchi una porta, ma batte le palpebre ogni cinque minuti, cosa succede?" Perde persone per strada. "E se la guardia potesse controllare la porta sedicimila volte al secondo?" Conterebbe ogni minima vibrazione della serratura come un nuovo visitatore che entra. Questo è ciò che fa Arduino eseguendo
digitalRead(). - Stato contro fronte: scrivete sulla lavagna le parole "È" e "È DIVENTATO". Mostrate loro che il primo codice chiedeva: "Il pulsante è premuto?" (Vero per 200.000 cicli). Il nuovo codice deve invece chiedere: "È appena diventato premuto?"
- La regola dei 40 ms: spiegate che invece di chiudere gli occhi (
delay), la guardia consulta il proprio orologio (millis). Se la porta vibra, azzeriamo un piccolo timer da 40 millisecondi. Non registriamo alcun visitatore finché la porta non rimane del tutto ferma per quaranta rintocchi.
Se state allestendo un laboratorio scolastico o introducendo il physical computing nelle classi della Senior Phase, sono disponibili pacchetti curricolari strutturati e kit di componenti collaudati tramite il negozio Sheen Robotics, progettati appositamente per evitare componenti da breadboard inaffidabili che complicano inutilmente la diagnostica meccanica.
Quando gli studenti comprendono che gli interruttori sono oggetti meccanici imperfetti che interagiscono con calcolatori ultraveloci, il debouncing smette di sembrare un misterioso bug del software e diventa la loro prima vera lezione di ingegneria dei sistemi embedded.



