Il debouncing o antirimbalzo è un problema apparentemente banale ma che spesso è la causa di malfunzionamenti. Ecco come gestire il rimbalzo degli interruttori o dei pulsanti (debouncing) applicati all’ingresso di un microcontrollore.
Benché risultino praticamente impercettibili ai sensi umani, i rimbalzi meccanici degli interruttori possono provocare transizioni spurie degli ingressi ai quali sono applicati, equivalenti a pressioni consecutive del medesimo tasto. La figura 1 mostra la tipica connessione di un interruttore in ingresso ad un microcontrollori con la tipica resistenza di pull-up.
Tale resistenza ha un duplice scopo: uno è quello di mantenere a livello alto l’ingresso del micro quando il tasto è aperto (funzione di pull-up), l’altro è quello di limitare la corrente durante la pressione del tasto. Nella stessa figura è riportato anche l’andamento della tensione V ai capi dell’interruttore, nell’ipotesi in cui questo venga chiuso all’istante 0. Si noti che la tensione in ingresso al microcontrollore non passa bruscamente dal valore alto a zero (come ci si aspetterebbe in una situazione ideale) ma oscilla tra i valori massimo e minimo prima di assestarsi sullo zero logico. Viene definito tempo di rimbalzo l’intervallo di tempo tra l’istante in cui il segnale passa per la prima volta a livello basso e quello in cui vi rimane senza alcuna ulteriore ambiguità. Nella figura 2 sono riportati alcune tipologie di rimbalzi ricavate dall’analisi all’oscilloscopio di diversi pulsanti.
Tipicamente il tempo di rimbalzo è dell’ordine di 10ms, un tempo impercettibile per l’utente, ma relativamente lungo per il microcontrollore. Ecco alcune tecniche di programmazione firmware per ovviare al problema del rimbalzo.
Gestione dei ritardi
Un primo modo molto semplice di gestione del rimbalzo per lo schema di figura 1, è quello di attendere che l’ingresso sia tornato a livello alto e che contemporaneamente sia trascorso un tempo di 20msec. Se l’ingresso torna a livello alto prima dello scadere dei 20msec è sicuro che si tratta di un rimbalzo quindi non dovrà essere considerato. Questa tecnica può essere implementata, per un PIC, con una macro il cui codice assembler è riportato nella Listato 1.
Debounce macro HiLo, Port, Bit if HiLo == Lo btfss Port,Bit ; Pulsante premuto? else btfsc Port,Bit endif goto $ - 1 ; Si - attesa rilascio movlw InitDlay movwf Dlay ; Impostazione ritardo 20 msecs movlw 0 if HiLo == Lo btfss Port,Bit ; Se il pulsante è premuto attendi ancora else btfsc Port,Bit endif goto $ - 6 ifndef Debug addlw 1 ; Incremento contatore ritardo btfsc STATUS, Z else nop nop endif decfsz Dlay goto $ - 5 endm
Listato 1 |
Pulsante macro porta, pin, statopremuto, ritardo, cicli, contatore, statofinale, indirizzo local PulsanteEnd incf contatore, w ; Incrementa il contatore if ((statopremuto == 0) && (statofinale == 0))||(( statopremuto == 1) && (statofinale == 1)) btfsc Port, Pin ; Se 0 allora lo stato è valido else btfss Port, Pin ; Se 1, allora lo stato è valido endif clrw ; non premuto, azzera contatore movwf contatore ; Salva il conteggio movlw ritardo & 0x07F subwf contatore, w ; finito il debouncing? btfsc STATUS, Z goto indirizzo ; Si, esci dal ciclo if ((ritardo & 0x080) != 0) ; Si è in modalità Autorepeat? btfsc STATUS, C decf contatore ; No – Decrementa se maggiore di ‘ritardo’ else btfss STATUS, C goto PulsanteEnd xorlw cicli ; terminati i cicli in autorepat? btfsc STATUS, Z goto PulsanteEnd ; No – Mantieni l’incremento movlw ritardo ; Si, resetta il contatore al valore precedente e ripeti movwf contatore goto indirizzo endif PulsanteEnd endm
Listato 2 |
Ciclo btfss Hi ; Attesa che l’ingresso sia stato riconosciuto a livello alto goto $ - 1 btfss Lo ; Attesa che l’ingresso sia stato riconosciuto a livello basso goto $ - 1 btfsc PORTB, 2 ; è stato premuto il pulsante? goto NoPress ; No, azzera il bit 2 della porta B bsf PORTB, 2 ;pulsante premuto goto Ciclo NoPress ;pulsante non premuto bcf PORTB, 2 goto Ciclo
Listato 3 |
L’uso di questa macro permette di monitorare qualsiasi pin del PIC purché configurato come ingresso, e funziona qualunque sia il valore del clock a patto di ricalcolare la costante InitDelay che tiene conto dei 20msec di attesa per il debounce. Tale costante può essere calcolata con la formula:
InitDelay=1+ (Td*F/4)/(256*7)
Dove Td è il tempo di debounce (20msec) ed F è la frequenza del clock del micro (il quarzo esterno o il valore impostato per l’oscillatore RC). Chiaramente il valore ottenuto dalla formula precedente dovrà essere arrotondato all’intero più vicino. Volendo quindi un tempo di debounce di circa 20msec si dovrà impostare a 12 il valore di InitDelay.
Una tecnica Interrupt-like
La gestione dei ritardi può essere effettuata con una tecnica molto simile a quella degli interrupt. Tale tecnica consiste nel lanciare una macro passandogli alcuni parametri generici necessari al monitoraggio di uno specifico ingresso. La macro viene richiamata ricorsivamente in un ciclo interrotto solo alla fine del debouncing. Il possibile ciclo che richiama la macro Pulsante con i dovuti parametri, potrebbe essere quello del listato 4.
ciclo Pulsante porta, pin, statopremuto, ritardo, cicli, contatore, statofinale, indirizzo Delay goto ciclo Indirizzo: ;indirizzo al quale si salta dopo il debouncing
Listato 4 |
org 4 Int movwf _w ; Salvataggio del contesto movf STATUS, w movwf _status movlw 256 - 0x09C ; Impostazione del Timer0 per un timeout di 20msec movwf TMR0 btfss INTCON, INTF ; è stato premuto il tasto? goto IntTimerOF ;no, o timeout di Timer0 IntTMR0Reset ;reset del flag di interrupt per Timer0 bcf INTCON, INTF bsf STATUS, RP0 ;cambia il fronte attivo di generazione interruzione movlw 0x040 xorwf OPTION_REG ^ 0x080 bcf STATUS, RP0 bcf Lo ;Azzeramento dei flag bcf Hi goto IntEnd IntTimerOF ;Timeout di Timer0 o Debounce completato bcf INTCON, T0IF ; Reset del flag interrupt per il timer bsf Lo ; Gestione dei flag bcf Hi movlw 1 btfsc Button addwf flags IntEnd movf _status, w ; Ripristino del contesto movwf STATUS swapf _w swapf _w, w retfie ; Fine Interrupt
Listato 5 |
Come si può notare dal codice del listato 4, la macro Pulsante viene chiamata con una serie di parametri:
- porta, pin – è l’ingresso su cui è collegato il pulsante (es. PORTB, 1);
- statopremuto – lo stato dell’ingresso quando il pulsante è premuto;
- ritardo – il numero massimo di iterazioni prima di uscire dal ciclo (max. 127). Se impostato a 0 viene effettuato il salto all’indirizzo di uscita non appena viene rilevata la pressione del tasto (quindi senza eseguire il debouncing). Se il bit 7 di ritardo viene messo ad 1 si disabilita la modalità auto-repeats della macro quindi non viene considerato il parametro cicli;
- cicli – se la macro è in modalità auto-repeats (bit 7 di ritardo a 0) è il numero di cicli prima di uscire dalla ricorsione;
- contatore – variabile che tiene conto del numero di ritorsioni effettuate;
- statofinale – lo stato (dell’ingresso) a cui la macro risponde (valori possibili: 0 o 1);
- indirizzo – l’indirizzo al quale si salta in uscita dal ciclo.
Nel listato 2 una possibile implementazione della macro Pulsante.
Gestione mediante interrupt
Uso di interruzioni su RB0/INT
Un’altra tecnica per la gestione dei tasti è sfruttare la possibilità offerta dai PIC di generare una interruzione al cambiamento di stato di uno degli ingressi della porta B. Il primo esempio, riportato nel listato 6, illustra la gestione di una interruzione su RB0/INT.
org 4 Int movwf _w ; Salvataggio del contesto movf STATUS, w movwf _status movlw 256 - 0x09C ; Impostazione del Timer0 per un timeout di 20msec movwf TMR0 btfss INTCON, RBIF ; è stato premuto il tasto? goto IntTimerOF ;no, timeout di Timer0 IntTMR0Reset movf PORTB, w bcf INTCON, RBIF ; reset del flag di interrupt per Timer0 clrf flagLo ; Azzeramento dei flag clrf flagHi goto IntEnd IntTimerOF bcf INTCON, T0IF movf PORTB, w ; acquisizione dello stato del pulsante xorlw 0x0F0 andlw 0x0F0 movwf flagLo ; impostazione flag Low movf PORTB, w ; impostazione flag High andlw 0x0F0 movwf flagHi IntEnd movf _status, w ; ripristino del contesto movwf STATUS swapf _w swapf _w, w retfie ; fine interruzione
Listato 6 |
In questo caso l’handler gestisce il debouncing e imposta il valore di due flag (Hi e Lo) che verranno analizzati in polling dal programma principale. Il programma avrà dunque la struttura riportata nel listato 3.
L’handler all’indirizzo 0x04 che andrà in esecuzione a seguito dell’interruzione avrà la struttura del listato 5. Come si può notare, la routine di interruzione del listato 5 prevede la modifica del registro OPTION al fine di attivare alternativamente l’interruzione sul fronte di salita e sul fronte di discesa del segnale su RB0. L’handler può essere dunque ottimizzato utilizzando le interruzioni sulla porta B (e non solo su RB0) come illustrato di seguito.
Uso di interruzioni su PORTB
Usando le interruzioni sulla porta B (quindi gestendo il flag di interruzione RBIF) non solo si ottimizza il codice, ma è possibile gestire quattro pulsanti con la stessa routine. In questo caso l’interrupt handler dovrà provvedere alle seguenti operazioni: salvataggio del contesto, inizializzazione di Timer0 per il ritardo di debouncing, impostazione dei flag a notificare il tasto premuto, ripristino del contesto. Nel listato 6 l’handler da utilizzare in questo caso. Ovviamente per la corretta esecuzione della routine sarà necessario definire i flag “Hi” e “Lo” anche usando la direttiva #DEFINE.
Un cenno all’hardware debouncing
Oltre al debouncing software, è possibile gestire il fenomeno anche con accorgimenti hardware. Il più semplice è quello riportato nella figura 3 che prevede l’inserimento di una capacità in parallelo allo switch. Resistenza e capacità vanno dimensionate in modo che la costante tempo della rete (data dal prodotto RC) sia dell’ordine del tempo di debounce (assunto pari a 20ms). Per rendere netto il fronte del segnale è possibile anche inserire un buffer a trigger di Schmitt tra il circuito di debouncing e l’ingresso del microcontrollore. Poiché il ritardo di debouncing varia a seconda del tipo di switch utilizzato, usare la soluzione precedente implica calibrare la rete RC in base al tipo di rimbalzo del tasto. Per ovviare a questo inconveniente è possibile inserire un latch RS come mostrato in figura 4.
Nella stessa figura è riportata anche la temporizzazione dell’uscita in cui si nota l’evidente filtraggio del rimbalzo. Il sistema sfrutta il fatto che il latch RS è caratterizzato da uno stato di conservazione in cui l'uscita permane al valore precedente. Nell'ottica del risparmio di componenti e semplificazione dello sbroglio dell'eventuale circuito stampato, spesso si preferisce adottare la soluzione firmware. Questa scelta consente inoltre di modificare il comportamento dell'applicazione senza intervenire sull'hardware.
Leggi anche:
Elettronica Digitale (per principianti) parte seconda
La gestione dell’antirimbalzo viene spesso sottovalutata dai progettisti, si mette il condensatore e via. Invece ogni applicazione deve avere una valutazione specifica sul problema. Ad esempio è capitato di fare progetti SENZA il condensatore (per motivi di costo e spazio) e quindi caricare tutto il peso dell’antirimbalzo sul firmware.
La soluzione migliore è senza dubbio l’inserimento del condensatore esterno abbinato alla gestione del ritardo nel firmware o utilizzare l’apposita periferica che gestisce il cambiamento di stato dell’ingresso con l’interrupt.
Io personalmente preferisco una soluzione firmware piuttosto che quella hardware, sopratutto (nel mio caso specifico) se l’input digitale è generico e un implementazione hardware mi precluderebbe l’utilizzo dell’ingresso in acluni casi.
Con micro performanti come gli attuali cortex ci si può permettere un pò di overhead lato firmware, oppure se gli input sono pochi una combinazione tra external interrupt come trigger per timer può ridurre i carichi sul micro sfruttando le periferiche interne spesso sottoutilizzate.
Come sempre, un problema e mille soluzioni, basta scegliere quella più adatta al proprio sistema.
Ottimo argomento!
Purtroppo però se in fase di progettazione il cliente chiede di minimizzare al massimo gli ingombri, bisogna eliminare il condensatore e non solo. Uso spesso la tecnica del “listato 1” e configuro la porta d’ingresso in pull-up, così elimino anche la resistenza esterna.
Non è il massimo dal punto di vista della progettazione ma bisogna anche accontentare il finanziatore 🙂
Pull-up esterni e condensatore sarebbero sempre la soluzione migliore ma….
Ricordo anni fa, ero consulente per la progettazione elettronica di una importante ditta di antifurto. La produzione era di decine di migliaia di pezzi annui e l’attenzione per i condensatori era maniacale. Costavano troppo!
Mi trovai in quel periodo a parlare con un amico, anche lui progettista elettronico, consulente per una azienda che produceva telecomandi tv. in quel settore si parlava di centinaia di migliaia di pezzi.
Gli esposi le mie preoccupazioni nel tagliare così drasticamente e gli dissi: “tu come reagisci a tali imposizioni?”
La risposta fu: “i tuoi clienti sono migliore dei miei, a me non fanno mettere nemmeno le resistenze. Guarda il circuito del microcontrollore, non mettiamo nemmeno il circuito di reset, colleghiamo il MCLR direttamente a VCC!”
E a quei tempi non esistevano micro con internal brown-out…..