Corso programmazione PICMicro in C – Approfondimenti – Studio su come controllare 16 pulsanti con un solo I/O

Abbiamo visto in un articolo precedente come utilizzare in maniera semplice un tastierino a matrice da 16 pulsanti. Oltre al sistema classico, che prevede 8 I/O (4 per le 4 righe e 4 per le 4 colonne) abbiamo illustrato sommariamente gli altri sistemi utilizzabili per ridurre il numero di pin necessari alla rilevazione dei pulsanti premuti.

In questo articolo ci occuperemo del sistema “estremo”, ovvero l’utilizzo di un unico I/O per distinguere la pressione di ben 16 diversi pulsanti.

Inutile dire che l’I/O che andremo ad utilizzare deve essere munito di convertitore A/D ad almeno 10 bit di risoluzione, per cui il sistema che andrò ad esporre è applicabile solo ai microcontrolli dotati di tale caratteristica. Abbiamo già visto, inoltre, che un’idea per realizzare questo è fornita dall’Application note 234 della Microchip (scaricabile in fondo all’articolo), che suggerisce di utilizzare delle resistenze collegate in un certo modo:

Collegamento delle resistenze

Anche se di soluzioni circuitali per il collegamento ne esistono più d’una, ho voluto adottare quella suggerita dall’AN Microchip con qualche piccola variazione: una resistenza per ogni pin della tastiera, con le righe verso la linea di massa e le colonne verso il punto di uscita. Lo schema da assemblare è il seguente:

Come vedete andremo a realizzare un partitore di tensione formato da R1 (da 1,5KΩ) e da una resistenza, che da questo momento in poi chiamerò Rx, formata dalle due resistenze in serie collegate dalla pressione del pulsante. Sappiamo che premendo il pulsante “1”, ad esempio, mettiamo in comunicazione la riga 1 con la colonna 1, se premiamo il pulsante “0” si mette in comunicazione la riga 4 con la colonna 2 e così via, secondo lo schema della tastiera utilizzata:

La pressione di ogni singolo pulsante, quindi, mette in serie la resistenza collegata alla riga (R_Rx) con la resistenza collegata alla colonna (R_Cx). Come vedete per la prima riga e per la prima colonna ho scelto di non mettere nessuna resistenza, quindi possiamo assumere una resistenza ideale di 0Ω.

I valori delle resistenze li ho scelti dopo alcune prove pratiche (e anche in base a quello che avevo nei cassetti!): l’importante comunque è fare in modo che la pressione di ogni singolo pulsante produca un valore di resistenza unico e quanto più possibile diverso dagli altri. E’ inoltre necessario utilizzare resistenze con una tolleranza bassa (1%) in maniera tale da poter semplificare i calcoli successivi.  Lo schema, quindi, si riduce al classico partitore di tensione:

Nel quale sappiamo che la tensione in uscita (Vout) è data da:

Dove Vin è la tensione di alimentazione (5V) e Rx è la somma delle resistenze di riga e di colonna.

Collegando il punto Vout ad un convertitore A/D a 10 bit (il quale fornisce cioè valori da 0 a 1023, dove 1023 corrisponde a Vin), sappiamo che questo fornirà un valore numerico pari a:

Dove, lo ricordo ancora una volta, Vin nel nostro caso è 5V e 1023 è il massimo valore ottenuto in uscita al convertitore AD quando al suo ingresso vengono letti 5V (1023 = 5V).

Se può esserci utile, sostituiamo Vout usando la formula del partitore e vediamo una cosa interessante:

Ovvero che il valore in uscita dal convertitore AD ora è indipendente dalla tensione di alimentazione e lo possiamo calcolare solo usando i valori di resistenza.

Non avendo messo nessuna resistenza sulla riga 1 e sulla colonna 1, premendo il pulsante “1” il punto “Vout” va a massa e quindi il convertitore AD leggerà un valore pari a zero o molto vicino ad esso.

Ho specificato molto vicino ad esso in quanto anche il pulsante, essendo generalmente composto da una “ventosa” con su applicato del materiale conduttivo che mette in comunicazione due contatti, presenta una propria resistenza e quindi premendo il pulsante “1” non avremo proprio il valore zero ma un valore molto basso (io in uscita al convertitore AD ho trovato valori tra 3 e 6).

Calcoli

Mentre cercavo un buon sistema di gestire il tastierino con le resistenze mi sono preparato un foglio excel che vi ho messo a disposizione per il download in fondo all’articolo.

Il foglio Excel in questione contiene delle Macro che mi sono servite per velocizzare alcuni calcoli. Se intendete utilizzare tali funzioni dovete necessariamente impostare il livello di protezione delle macro su basso. Gli utenti di OpenOffice.Org possono aprire il file ma non possono utilizzare le macro.

Analizziamo la tabella principale:

Supponiamo di premere sul tastierino il pulsante D (pulsante n°15). Sappiamo che il pulsante D mette in comunicazione la Riga 4, sulla quale ho messo una resistenza di 330Ω, con la colonna 4 che ha una resistenza di 1,5KΩ. La resistenza risultante è quindi di 1830Ω che nel punto di uscita del partitore dovrebbe produrre una tensione ideale di 2,748Volts (valore nella colonna Vout).

Il convertitore A/D a 10Bit fornirà un valore numerico pari a 562 (colonna lettura ADC / Teorico). Ovviamente tale valore è quello ideale: non stiamo difatti tenendo conto di vari fattori:

  • Resistenza della linea di collegamento
  • Resistenza offerta dal pulsante
  • Tolleranza della resistenza
  • Sfiga

I primi 3 fattori possono essere ritenuti trascurabili nel caso in cui la linea non è molto lunga, la tastiera non è cinese e la resistenza è molto precisa. Pur essendo molto accorti avremo inevitabilmente un valore diverso da quello teorico a volte anche determinato dall’ultimo fattore elencato precedentemente. Personalmente, per il pulsante D, ottengo al convertitore AD un valore pari a 556 (valori che ho trascritto nella colonna “reale” – i vostri saranno sicuramente differenti), ovvero distante 6 unità  (colonna “delta”, espressa come valore assoluto) dal valore teorico. E’ comunque un buon valore e come vedete nessun pulsante supera le 10 unità di differenza dal valore teorico (che corrispondono all’incirca a 14 ÷ 15Ω) con una media di 6 unità (circa 8 ÷ 9Ω) . Tenete presente che tutti questi calcoli sono sempre fatti tenendo conto di R1 = 1,5KΩ.

Per comodità, volendo realizzare più di un circuito utilizzante tale sistema, ma ovviamente tutti con tastiere e resistenze appartenenti agli stessi batch/modelli, forse (e dico forse!) potrebbe risultare più logico tenere conto di un valore “medio” tra il teorico e il pratico… e quindi ecco spiegato il significato della colonna “Val. Cen.” (Valore Centrale) che calcola un valore intermedo tra il teorico e il reale. Oppure ancora, un’altra buona idea (non computata in tale tabella) potrebbe essere quella di sommare al valore teorico le 6 unità medie di scarto dal valore pratico… Oppure ancora utilizzare proprio il valore pratico….

Insomma… dovete decidere voi come operare, questi sono unicamente miei esperimenti e risultati di prove pratiche che ho eseguito personalmente, per cui nulla vieta che esista un altro modo di procedere più corretto di questo che vi sto esponendo… Questo è quello che mi è venuto in mente per primo e con il quale ho avuto buoni risultati per cui non ho ritenuto opportuno trovarne altri.

In ogni caso, qualsiasi scelta decido di effettuare, nella mia applicazione, leggerò il valore in uscita dal convertitore A/D e se sarà uguale ad uno dei valori contenuti in questa o in quell’altra colonna, in un certo range, allora potrò capire quale pulsante è stato premuto.

Come vedete ho specificato in un certo range. E’ ovvio che non posso fare la verifica del valore preciso: sarebbe completamente assurdo, difatti la resistenza potrebbe anche variare con la temperatura, con l’usura dei pulsanti ecc. Per cui farò il confronto tra il valore letto dal convertitore AD con un range (valore centrale scelto ± Δ). Come calcolare questo Δ?

In questo ambito rientra la questione dell’importanza della scelta delle resistenze: bisogna fare in modo, oltre che i valori di resistenza forniti dai singoli pulsanti siano unici, anche che i valori  di tensione in uscita siano il più distanti possibile tra loro in maniera tale che i range di controllo per ogni pulsante non si accavallino tra loro.

A tal scopo, affianco alla tabella precedente ne ho riportata un’altra che serve appunto a calcolare la “distanza” minima tra un valore e l’altro:

Questa tabella riporta i valori di una delle 3 colonne delle letture ADC in senso crescente. Spiego come si utilizza.

Ricordo di nuovo che questa funzione che illustrerò di seguito, la si può utilizzare unicamente con Microsoft Excel con il livello di protezione delle Macro impostato su basso.

Posizioniamoci col puntatore del mouse sul rettangolino verde posto al di sopra della colonna “Val. Cen.”

Il sistema funziona anche con le colonne valore teorico e valore reale: vedete che anche al di sopra di queste due colonne ho piazzato un rettangolino verde.

Il puntatore assume la forma di una mano con l’indice teso. Clicchiamo.

I valori contenuti nella colonna “Val. Cen.” vengono ricopiati nella colonna “AD Reads” in senso crescente. La colonna “Delta2” rappresenta la differenza tra un valore e quello precedente: da questa colonna viene estratto il valore minimo e riportato nella cella “Delta2 Min”: nel mio caso tale valore è 16  (purtroppo con questi valori di resistenze non sono riuscito ad ottenere di meglio, se qualcuno fa di meglio… scriva tra i commenti le resistenze o il sistema che ha utilizzato).

Il valore da utilizzare per il calcolo del range è pari a tale valore diviso due, ovvero 8, perchè voglio verificare che la lettura del convertitore rientri in un range (valore centrale – Δ) ÷ (valore centrale + Δ) ma il range del valore precedente e del valore successivo non devono accavallarsi al range che sto controllando.

Faccio un esempio per cercare di chiarire questa questione:

Personalmente scelgo di utilizzare come valori di controllo quelli contenuti nella colonna “Val. Cen.” piuttosto che quelli trovati sperimentalmente (voi siete liberi di fare come credete più opportuno ma i valori teorici di sicuro non vi garantiranno buoni risultati, specie se la tastiera utilizzata è di scarsa qualità). Quindi se premo il pulsante A il mio valore di riferimento centrale è 509.

Utilizzando 8 come Δ per il calcolo del range, dovrò verificare, per la pressione del pulsante A, che il valore letto dal convertitore AD si trovi tra 509-8=501 (compreso) e tra 509+8=517 (non compreso).

Vedo inoltre dalla tabella che il valore “di pulsante” più vicino a 509 è 525, che corrisponde al tasto B.

Il range per il tasto B sarà quindi compreso tra 525-8=517 (compreso) e 525+8=533 (non compreso) e così via. In questo caso il valore 517 come vedete costituisce la fine del range di un tasto e l’inizio di quello successivo (inteso come valore di lettura del convertitore AD e non come tasto successivo). Per tale motivo ho deciso di non includere il valore corrispondente all’estremo superiore e di includere quello corrispondente all’estremo inferiore in maniera tale che i valori “estremi” non si accavallino in nessun caso. Capite anche perchè il valore della colonna Delta2 debba essere diviso per 2.

Ovviamente i valori che hanno distanze molto elevate da quelli adiacenti lasceranno scoperti un certo range di valori che non saranno computati da questo sistema e quindi non associati a nessun tasto, ma questo è inevitabile.

Facciamo qualche altro calcolo. Prendiamo sempre come esempio il pulsante B che deve essere verificato in un range di valori pari a 525±8. La verifica per il pulsante B andrà quindi fatta nel range 517 ÷ 532 (ricordo che il 533 è il limite superiore e ho deciso di non comprenderlo), ovvero:

B è premuto se: (lettura_AD ≥ 517) AND (lettura_AD < 533)

Tale range corrisponde ad un valore di tensione rientrante nel campo: 2.527V ÷ 2.600V, che equivale, tenendo sempre R1 a 1500Ω e Vin a 5V, ad un range di resistenza pari a 1532.75Ω ÷ 1625Ω. Per facilitarvi i calcoli vi riporto qui la formula inversa:

Al pulsante B, in realtà, corrisponde una resistenza ideale di 1600Ω. Utilizzando delle resistenze con una tolleranza dell’1%, il valore complessivo reale della resistenza ottenuta rientrerà nel range 1584Ω ÷ 1616Ω. Questo range di resistenza produrrà al partitore una tensione compresa tra 2.568V e 2.593V che, tradotta in termini di lettura del convertitore AD è pari ad un valore numerico compreso tra 525 e 530 (solo 5 unità usando resistenze all’1%!).

Come vedete questo range rientra in quello da noi imposto di 517 ÷ 532. Nel peggior caso la “discrepanza” minima è quindi di 2 unità sul valore più alto (532-530) che corrisponde a circa 2,9Ω consentendoci di far rientrare in questo range anche una certa resistenza parassita offerta dai pulsanti.

Questo sicuramente non basta dal momento che abbiamo visto che le resistenze parassite sono più alte, ma stiamo comunque ragionando su valori estremi: dovremo avere, ad esempio, entrambe le resistenze di riga e colonna “sballate” nella stessa direzione (verso l’alto in questo caso) e una resistenza parassita superiore a 2,9Ω… In questo caso ecco entrare in gioco il fattore S e quindi dobbiamo rifarci i calcoli e magari utilizzare unicamente il valore sperimentale.

Ovviamente possiamo anche utilizzare, in ogni caso, i valori trovati sperimentalmente piuttosto che quelli calcolati come valore centrale tra il teorico e lo sperimentale: in questo caso riusciremo a centrare ancora di più i pulsanti premuti e ad avere un margine di lavoro più ampio. Ma poi non è detto che il codice scritto riesca ad essere utilizzabile per tutti i tipi di tastierina.

Non saremmo riusciti ad ottenere buoni risultati utilizzando resistenze con tolleranze più elevate o un convertitore AD con una risoluzione minore.

Un altro sistema sarebbe quello di specificare manualmente i limiti superiore ed inferiore per ogni pulsante senza ricorrere ad un valore fisso ed una costante (Δ=8 nel mio caso) da aggiungere e sottrarre al valore fisso. Ma questo sistema non mi piace poi molto perchè sto cercando di fare una cosa funzionale con il minimo spreco possibile di risorse.. ma comunque è una buona possibilità da tenere in considerazione.

Il codice

Dopo fatte tutte le  opportune considerazioni matematiche… Si passa alla pratica e vediamo che in realtà fare solo un confronto del convertitore AD con un certo range, cacolato o sperimentale, non è sufficiente perchè entrano in gioco numerosi fattori alcuni dei quali normalmente non hanno ragione di venire considerati quando invece si legge un pulsante tramite un I/O digitale. Analizziamo questi fattori uno per uno.

Queste sono considerazioni alle quali sono giunto dopo aver fatto alcune prove pratiche.

I rimbalzi

Anche qui dobbiamo tenerne conto. Teniamo presente che dobbiamo leggere una tensione analogica e non dobbiamo verificare uno zero o un uno. Se cominciamo il campionamento nel momento in cui il rimbalzo causa una mancata pressione del pulsante, ecco che il valore in uscita al convertitore AD ci fornirà un dato sbagliato. Quindi un buon modo di procedere, secondo me, è quello di eseguire più letture della linea analogica e farne una media. Una sola lettura non è sufficiente perchè potrebbe capitare nel “punto” sbagliato. Scelgo ovviamente di eseguire un numero di letture tale che poi la divisione può essere eseguita semplicemente con un bitshift.

La “rampa”

Premendo il pulsante più o meno forte o in maniera più o meno decisa, sul convertitore A/D otterremo un valore di tensione che, nel nostro caso, scende progressivamente fino a stabilizzarsi.

Date un occhio allo schema che sto usando:  c’è R1 che tiene sempre il convertitore A/D a 5V e quindi in stato di “pulsante non premuto” . Premendo un pulsante la tensione scende dato che le resistenze sono collegate verso massa.

Sull’oscilloscopio osservo in pratica questo andamento:

In pratica: a sinistra c’è il livello di tensione nello stato di pulsante non premuto (circa 5V), premendo il pulsante la tensione scende (andando verso destra) fino a stabilizzarsi sul valore determinato dal partitore dopo circa 800μS nel peggiore dei casi.

Questo andamento è causato dall’effetto capacitivo dei pulsanti: all’atto della pressione questa sorta di condensatore si scarica con un andamento più o meno esponenziale. In realtà il tempo di 800μS è piuttosto elevato anche se questo è un caso estremo. Ricordiamo comunque che, indipendentemente dalla capacità parassita causata dai pulsanti e dalle linee di collegamento, abbiamo comunque una capacità fissa di circa 5pF indotta dall’ADC.

Ringrazio Luca Gallucci e Mauro Laurenti per la delucidazione.

Immaginate quindi di iniziare il campionamento nel punto di discesa della rampa: anche se facciamo la media, se il primo valore campionato cade nel punto più alto della rampa, la media potrebbe essere alterata comunque non consentendoci di rilevare correttamente il pulsante.

Per tale motivo decido di iniziare il campionamento, saltare le prime letture e quindi prendermi solo le successive, sulle quali eseguire la media.

Passaggio per lo zero

L’ho chiamato così anche se non è il termine corretto… In pratica voglio controllarmi la pressione del pulsante solo nel caso in cui il convertitore AD non mi legge il valore 1023 (o giù di li) che indica che nessun pulsante è premuto. Per tale motivo nel foglio excel ho aggiunto quella casella con l’indicazione “NO_PRESS”, che mi fornisce il valore minimo da considerare come stato di pulsante non premuto. Nel codice ho messo un valore un po’ più alto di quello indicato li: 800. Quando il convertitore AD legge un valore superiore a questo sono sicuro che nessun pulsante è stato premuto e quindi non eseguo nessuna routine di controllo pulsante.

Sempre per scongiurare i rimbalzi e le rilevazioni errate, il controllo di tale valore lo eseguo sempre .

In pratica ho fatto in modo, nel mio codice, che dopo la pressione di un pulsante, se non si passa per tale valore (passaggio per lo zero) non eseguo un ulteriore controllo del valore in uscita al convertitore AD. Questo mi scongiura anche rimbalzi che avvengono dopo il rilascio del pulsante o la rampa al contrario in salita.

Il codice

Spero che siate arrivati a questo paragrafo dopo aver letto tutto il “polpettone” scritto sopra… Lo so, forse sono stato un po’ prolisso ma lo ritenevo inevitabile per vari motivi… Innanzitutto mi sono fatto uno studio approfondito del problema perchè un po’ dappertutto si dice: usa le resistenze! Ma secondo me il problema non è mai stato affrontato a dovere, difatti senza molti degli accorgimenti che ho illustrato qui, il sistema era inutilizzabile perchè spesso il pulsante rilevato non era quello corretto, per cui sentivo il dovere di documentare per bene tutte le prove che ho fatto.

Il codice, come mio solito, funziona soprattutto tramite interrupt. Abbiamo un interrupt sul timer0 che scatta ogni millisecondo ed incrementa un contatore. Arrivati a 5 millisecondi viene avviata la conversione AD.

Quando scatta il flag di fine conversione AD e non ci troviamo nello stato di “elaborazione del pulsante premuto in precedenza”, memorizzo il valore letto dal convertitore. Se tale valore è maggiore di quello che impostato per il rilevamento dello stato di “pulsante non premuto”, mi setto anche un flag per indicare che mi trovo in uno stato in cui nessun pulsante è premuto.

Abilito quindi in ogni caso la routine di calcolo nel main.

La routine di calcolo mi incrementa un contatore. Se il valore letto dal convertitore AD è pari a “Pulsante non premuto”, il contatore viene azzerato di continuo e quindi le funzioni seguenti non verranno eseguite in quanto il contatore non raggiungerà il valore prefissato. Se invece si rileva un pulsante premuto, il contatore viene incrementato:

if (valore >= NO_PRESSED) // nessun pulsante premuto
 {
 contavalori=0; // torno indietro
 }
 else
 {
 contavalori++; // incremento il numero di valori letti
 }

In questo modo elimino anche i rimbalzi. Successivamente faccio in modo da saltarmi le prime 10 letture per i motivi che ho descritto sopra:

if (contavalori>9)
 {
 media+=valore; // sommo il valore
 }

In pratica la media (che qui non è ancora media ma è solo la somma), mi verrà calcolata solo se i valori conteggiati sono superiori a 10 (da 0 a 9 sono 10 valori, ok? Niente domande stupide!).

Quando ho eseguito 8 letture passo a calcolare la media tramite un bitshift a destra di 3 posizioni (divisione per 8):

// abbiamo raggiunto 8 letture?
 if (contavalori==17) //17=18 letture => 10 saltate + 8 eseguite
 {
 media >>= 3; // shift a destra di 3 posizioni equivale a divisione per 8
 contavalori=0; // azzero il contatore dei valori sommati

A questo punto bisogna dire che nel file settings.h mi sono creato un array che contiene i valori di lettura del convertitore AD per ogni pulsante (nella sequenza: 1,2,3,A,4,5,6,B,7,8,9,C,*,0,#,D). Tale array è riempito con i valori che si possono ricavare dal foglio excel di cui vi ho parlato:

signed int keyvalues[]={3,240,400,506,65,276,423,522,129,316,449,540,182,349,471,556};

Nel settings c’è anche, oltre ad altra roba che potrete capire facilmente perchè è abbondantemente commentata, quel valore di Δ di cui vi ho tanto parlato:

#define DELTA 8

Bene… Nel main quindi, dopo essermi calcolato la media in uscita al convertitore AD, mi passo ad identificare il pulsante premuto con un ciclo:

// adesso confronto il valore letto con la tabella valori
 for (c=0; c<16; c++)
 {
 // il valore rilevato deve rientrare in quello scansionato +- il DELTA
 if ((media >= (keyvalues[c]-DELTA)) && (media < (keyvalues[c]+DELTA)))
 {
 keypressed=c; // il tasto premuto è questo
 keyok=1; // c'è un tasto premuto
 break; // esco dal ciclo
 }
 }

In pratica la variabile keypressed, alla fine del ciclo, conterrà l’indice numerico del tasto premuto. Viene anche settato un flag che servirà ad indicare nel main che un pulsante è stato premuto.

Il programma è pensato per essere usato sulla FreedomII con il PIC16F877A quarzato a 20MHz , con un display 2×16 ed utilizza il pin RA2 come ingresso analogico. Utilizzando altri pic con altre frequenze dovrete adattarvi il codice, soprattutto per quanto riguarda la gestione dell’interrupt sul timer0.

Sul display compariranno due righe: sulla prima riga viene scritto il valore letto dal convertitore AD (quello medio, dopo tutte le opportune pulizie). Sulla seconda riga viene scritto il pulsante premuto.

Se nessun pulsante viene rilevato, verrà scritto “?”. Leggendo sul display il valore letto dal convertitore AD con il vostro tastierino, vi potete riscrivere la tabella excel con i valori che ottenete voi e quindi rendere il programma più adatto. Se poi vi funziona con i valori che ho messo io… Vuol dire che avete molto fattore Q.

Downloads

Foglio Excel per il calcolo dei valori da usare con il keypad (589 download) Sorgenti - Usare una tastiera a 16 tasti con un solo IO (582 download) Microchip Application Note 234 - Hardware Techniques for PICmicro Microcontrollers (2511 download)

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)