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.