PrettyPrint

domenica 29 ottobre 2023

Un altro debounce (non bloccante) per Arduino Uno

Nell'impiego di interruttori, commutatori o pulsanti meccanici, si verifica quasi sempre un inconveniente che, nella maggioranza dei casi, costituisce causa di malfunzionamento nei circuiti digitali: l'elemento mobile che realizza il contatto, quando viene spostato da una posizione all'altra rimbalza (bouncing) diverse volte prima di stabilizzarsi al valore impostato, sia nella fase di pressione che in quella di rilascio.


Questi continui rimbalzi, che avvengono in un tempo brevissimo (generalmente fra 0,1 e 5ms), vengono percepiti dal software in esecuzione  come ripetute pressioni del pulsante e quindi erroneamente conteggiati. Il problema viene aggirato impiegando soluzioni cosiddette di anti-rimbalzo (debouncing) che possono essere circuitali o software. Una delle soluzioni software più semplici è quella di far eseguire un'istruzione di attesa, di un tempo pari, o leggermente superiore, a quello di rimbalzo, in modo che il programma ignori sicuramente queste innumerevoli commutazioni. Al termine di questo periodo, per sicurezza, si torna a leggere lo stato del pulsante: se è corrispondente a quello attivo (es. livello alto) il pulsante è stato dunque "de-rimbalzato" (debounced) e quindi può essere eseguito il codice previsto all'occorrenza della pressione del bottone.


HARDWARE                                  

Il pulsante N.A. è collegato fra il pin di ingresso (2) e la massa; il resistore di pull-up è quello interno dell'ATmegax8: quindi la pressione del pulsante comporta, a regime, la lettura sul pin 2 di un livello logico LOW, mentre il rilascio comporta, a regime,  la lettura di un livello logico HIGH. Sul pin 9 è collegato un LED (attraverso una resistenza da 220..330 Ohm), che, alla pressione del pulsante, commuterà il suo stato da acceso a spento e viceversa. 




SOFTWARE                                 

Lo sketch è imperniato sull'intercettazione della pressione del pulsante (ovvero del rilascio) attraverso un'istruzione di selezione semplice if, che va a "testare" lo stato corrente del pulsante (memorizzato nella variabile statoP1)  e quello della precedente esecuzione del loop (memorizzato nella variabile statoP1prec).  Se i due stati sono diversi vuol dire che c'è stata una variazione (da HIGH a LOW: pressione oppure da LOW a HIGH: rilascio), ma per essere sicuri che sia la prima variazione - ovvero la pressione o il rilascio - e quindi non una successiva variazione dovuta ai rimbalzi, occorre testare una terza variabile di flag che assume valore 0 solo quando non si è ancora verificata la prima variazione.

Se dunque si verifica la condizione logica (stato attuale pulsante NOT EQUAL a quello precedente AND flag=0) dalla pressione (ovvero dal rilascio) , attraverso un marker temporale (variabile t1) viene "segnato" l'istante conteggiato dalla funzione millis() e ovviamente settata a 1 la variabile flag, essendosi verificato l'evento di prima variazione.

Successivamente , a ogni loop, si andrà a leggere il valore  millis() e a confrontarlo con t1, se la differenza è MAGGIORE O UGUALE a 5ms (tempo tipico di attesa per l'esaurirsi del debounce; potrebbe essere necessario aumentarlo, se la qualità dei pulsanti è bassa) si andrà allora a testare lo stato del pulsante: se LOW, allora è una condizione di pressione e quindi si va ad eseguire il codice previsto all'occorrenza della pressione del pulsante.

Diversamente, verrà eseguito il codice restante dello sketch, cioè quello non relativo all'evento della pressione del pulsante, senza alcuna pausa. Infatti l'uso di millis()rende la gestione "non bloccante" dell'intero software, a differenza delle istruzioni di delay(): infatti il "costo" temporale delle istruzioni di check del tempo trascorso sono assolutamente trascurabili rispetto alle decine di millisecondi che avrebbe richiesto una delay.

Ad ogni loop, ovviamente, occorrerà aggiornare lo stato precedente del pulsante per il successivo loop, che ovviamente sarà uguale a quello attuale. Generalmente questa istruzione viene messa come ultima nel corpo della loop().

MIO_DEBOUNCE.INO

/*
 *  Debounce con millis() sulla pressione e sul rilascio
 *  del pulsante.
 *
*/
const int P1=2;   // pulsante 
const int L1=9;   // LED

int statoP1=LOW;       // stato P1 (premuto LOW / rilasciato HIGH)
int statoP1prec=LOW;   // stato precedente P1
int statoL1=LOW;       // stato LED (spento LOW / acceso HIGH)
int flag=0;            // 1: pressione o rilascio
int T=5;               // tempo attesa bouncing (5ms)
unsigned long t1;      // marker temporale di millis()

void setup() {
  pinMode(P1, INPUT_PULLUP);  // attiva la R pull-up interna
  pinMode(L1, OUTPUT);
  digitalWrite(L1, statoL1);
}

void loop() {

  statoP1 = digitalRead(P1);

  // rilevata una pressione o un rilascio
  if (statoP1 != statoP1prec && !flag)
  {
     flag=1;        // flag di avvenuta pressione/rilascio
     t1=millis();   // marca inizio conteggio tempo
  }

  // se si è esaurito il rimbalzo
  if ( millis()-t1 >= T && flag) 
  {
     flag=0;    // azzera flag  (per successive pressioni/rilasci)
// se è una pressione if (statoP1==LOW) { // istruzioni da eseguire alla pressione di P1: statoL1=!statoL1; digitalWrite(L1, statoL1); } } // istruzioni da eseguire al di fuori della pressione di P1: statoP1prec = statoP1; }