3.11. Debouncing#
Un interruttore viene rappresentato come un contatto perfettamente aperto o chiuso, ma i contatti di un interruttore reale non passano in modo netto tra i due stati. Essi rimbalzano – stabiliscono e interrompono il contatto elettrico molte volte nell’arco di pochi millisecondi prima di stabilizzarsi. Un ingresso GPIO che legge il pin lo percepisce come una raffica di fronti; un ciclo di polling incauto conta diverse «pressioni» per una sola pressione reale, e un gestore di interrupt viene eseguito più volte per ogni pressione effettiva.
Un interruttore che rimbalza produce una raffica di transizioni rapide prima di stabilizzarsi.#
Il debouncing è la pratica di filtrare il rimbalzo in modo che ogni pressione fisica venga registrata come un singolo evento. Due approcci risolvono questo problema – software (una regola temporale nel firmware) o hardware (un piccolo filtro sul filo). Non si escludono a vicenda.
3.11.1. Debouncing software#
L’idea è ricordare quando l’ingresso è cambiato l’ultima volta e rifiutare ulteriori cambiamenti entro una breve finestra a partire da quel timestamp. Il rimbalzo dei contatti dura tipicamente meno di 10 ms; una pressione reale richiede 50 – 100 ms; una finestra di 30 – 50 ms cattura tutti i rimbalzi senza bloccare le pressioni reali.
In un ciclo di polling, leggi il pin, confrontalo con l’ultimo valore stabile e accetta un cambiamento solo dopo che è trascorsa la finestra di debounce:
import time
from machine import Pin
button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_state = 1
last_change = 0
DEBOUNCE_MS = 50
while True:
now = time.ticks_ms()
state = button.value()
if state != last_state and time.ticks_diff(now, last_change) > DEBOUNCE_MS:
last_change = now
last_state = state
if state == 0:
do_action()
time.sleep_ms(10)
Per le letture guidate da interrupt, applica la stessa regola temporale all’interno del gestore, quindi passa la pressione reale al contesto principale tramite micropython.schedule() (vedi Ingresso GPIO):
import time
import micropython
from machine import Pin
button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_irq = 0
DEBOUNCE_MS = 50
def handle_press(pin):
do_action()
def on_press(pin):
global last_irq
now = time.ticks_ms()
if time.ticks_diff(now, last_irq) < DEBOUNCE_MS:
return
last_irq = now
micropython.schedule(handle_press, pin)
button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)
L’ISR filtra i rimbalzi tramite timestamp e accoda il callback; handle_press viene eseguito nuovamente nel contesto principale, dove l’allocazione e l’I/O lento sono sicuri.
3.11.2. Debouncing hardware#
Il debouncing hardware filtra il rimbalzo elettricamente, prima ancora che raggiunga il pin. Lo strumento standard è un condensatore.
Un condensatore è un componente a due terminali che immagazzina carica elettrica. Fisicamente, è costituito da due piastre conduttrici tenute a breve distanza l’una dall’altra, separate da un isolante (il dielettrico).
Un condensatore a piastre parallele: due conduttori separati da uno strato isolante.#
L’applicazione di una tensione attraverso i suoi terminali spinge cariche uguali e opposte sulle due piastre; la relazione è
Q = C × V
dove Q è la carica immagazzinata (coulomb), V è la tensione ai capi del condensatore e C è la sua capacità (farad). La capacità è fissata dalla costruzione del dispositivo; una maggiore capacità significa più carica immagazzinata alla stessa tensione.
La conseguenza: un condensatore non può cambiare la sua tensione istantaneamente. La carica che entra o esce deve attraversare qualunque resistenza si trovi nel percorso, e tale resistenza determina la velocità con cui la tensione può cambiare.
3.11.2.1. Costante di tempo RC#
Caricare un condensatore attraverso un resistore produce un aumento esponenziale graduale verso la tensione di alimentazione, non un gradino. Il tempo caratteristico di questo aumento è la costante di tempo RC:
τ = R × C
Dopo un τ, il condensatore ha raggiunto circa il 63 % della tensione di alimentazione. Dopo 5 τ, supera il 99 % – «completamente carico» a fini pratici.
Un condensatore si carica lungo una curva esponenziale. τ = RC è il tempo necessario per raggiungere il 63 % della tensione finale.#
La scarica attraverso un resistore segue l’immagine speculare: la tensione cala esponenzialmente dal suo valore iniziale verso zero, scendendo al 37 % della tensione di partenza dopo un τ, e sotto l’1 % dopo 5 τ.
Un condensatore si scarica lungo un decadimento esponenziale. τ = RC è il tempo necessario per scendere al 37 % della tensione di partenza.#
3.11.2.2. Il circuito di debounce#
Un condensatore tra un pin di ingresso e la massa, alimentato attraverso un resistore in serie, forma un filtro passa-basso. I picchi rapidi non hanno il tempo di caricare o scaricare il condensatore attraverso quel resistore; il pin rimane vicino alla tensione che aveva prima del picco. I cambiamenti lenti – una pressione deliberata – caricano o scaricano il condensatore e la lettura li segue.
R1 tira il lato alto dell’interruttore verso Vcc, producendo un segnale grezzo dell’interruttore che rimbalza. R2 e C poi filtrano questo segnale in passa-basso verso il pin:
Debouncing hardware: R2 e C filtrano in passa-basso il segnale grezzo dell’interruttore prima che raggiunga il pin.#
Valori tipici: R1 = 10 kΩ (pull-up), R2 = 10 kΩ (serie), C = 100 nF.
Quando l’interruttore è aperto, la corrente scorre Vcc → R1 → R2 → condensatore (in serie), caricando il condensatore a Vcc con τ_charge = (R1 + R2) × C = 2 ms.
Quando l’interruttore si chiude, il nodo dell’interruttore viene portato a massa e il condensatore si scarica attraverso il solo R2 verso tale massa con τ_discharge = R2 × C = 1 ms.
Entrambi i fronti sono filtrati RC. Poiché il condensatore si trova sul proprio nodo, a valle di R2 rispetto all’interruttore, oscilla in modo netto tra Vcc (aperto) e 0 V (chiuso) – nessuna corrente deve scorrere attraverso R1 a regime in nessuno dei due casi.
3.11.3. Come scegliere tra i due#
Il software è l’opzione predefinita. Non costa nulla in termini di componenti, la soglia è facile da regolare e funziona su qualsiasi pin che la CPU legge.
L’hardware vale i componenti aggiuntivi quando il rimbalzo raggiunge qualcosa di diverso dal codice di polling della CPU – un interrupt che non deve attivarsi due volte, un contatore hardware, una periferica priva di un proprio filtro.
Il debouncing software e quello hardware coesistono anche pacificamente: un piccolo filtro RC sopprime i picchi peggiori, e una finestra di debounce software copre ciò che resta.