Corso programmazione PICMicro in C – Approfondimenti – Gestione di un tastierino a matrice
Ecco un’ altra diavoleria elettronica dal costo irrisorio e davvero caruccia da utilizzare per le nostre applicazioni: il tastierino a matrice (matrix keypad).
Questi tastierini si trovano comunemente nei formati 3×4 (che è in pratica uguale a quello usato sui telefoni, ovvero numeri da 0 a 9 e quindi asterisco e cancelletto) oppure come quello in foto da 4×4 (che ha in aggiunta le lettere ABCD), che è quello che userò per questa prova. Questi tastierini sono in realtà una semplice serie di pulsanti collegati in maniera un po’ particolare in maniera da minimizzare i collegamenti necessari. Il collegamento è a matrice, ovvero: sono organizzati in una serie di righe e colonne ed ogni pulsante mette in comunicazione una riga con una colonna.
Vi schematizzo qui come sono effettuati i collegamenti dei pulsanti in un tastierino a matrice 4×4:
Se premiamo il pulsante 1 metteremo in comunicazione la riga 1 con la colonna 1, premendo la C metteremo in comunicazione la riga 3 con la colonna 4 e così via. Gestire una cosa del genere, per chi è alle prime armi, può risultare una cosa abbastanza complicata. In realtà vi dimostrerò che è più semplice di quello che si immagina e che inoltre esiste più di un sistema per poterlo fare.
Indice dei contenuti
Vari sistemi di lettura di un tastierino a matrice
Uno dei sistemi che prevede l’utilizzo delle tastiere a matrice richiede addirittura un solo pin del picmicro! Si tratta in pratica di collegare i rami del tastierino a resistenze di precisione con valore noto e diverso tra loro: premendo ogni pulsante si avrà in uscita, su un unico ramo, una tensione caratteristica per ogni pulsante premuto, la quale ovviamente, per essere letta, avrà bisogno di un picmicro dotato di convertitore A/D abbastanza veloce e con una buona risoluzione.
Vi illustro qui lo schema concettuale prelevato dall’Application Note 234 della Microchip (scaricabile in fondo all’articolo):
Anche se tale soluzione permette l’utilizzo di un tastierino a matrice anche con i picmicro a 8 pin (ma anche con quelli a 6pin!), è abbastanza complicato perchè richiede resistenze di precisione e soprattutto un accurato calcolo dei loro valori in maniera tale che ogni tasto produca in uscita una tensione caratteristica e ben distinta da quella degli altri tasti. Non mi occuperò di questo sistema in questo articolo (magari in un prossimo).
Un altro sistema per ridurre il numero di I/O necessari al collegamento dei tasterini a matrice è quello di utilizzare appositi circuiti integrati come il 74C922 e il 74C923 (datasheet in fondo all’articolo). Il 74C922 è un encoder per tastiere a 16 tasti: si collegano le 8 uscite della tastiera e l’integrato riporta il tasto premuto su 4 uscite (ABCD), il debounce è eseguito dall’integrato stesso. Il 74C923 è per tastiere a 20 tasti e quindi riporta il tasto su 5 pin (ABCDE). Tali integrati sono comunque molto costosi e a mio avviso non ne vale la pena.
Mi occuperò invece del sistema classico che prevede una linea di I/O per ogni riga e per ogni colonna. Utilizzando un tastierino da 4×4 avremo quindi bisogno di 8 linee di I/O e quindi di un banco di porte completo.
Il concetto che sta alla base del funzionamento è molto semplice. Basta seguire questo schema mentale:
- Le 4 righe sono collegate a 4 pin predisposti come ingressi, questi ingressi hanno una resistenza di pullup (esterna o integrata) che permette loro di trovarsi a livello logico alto in condizioni di riposo.
- Le 4 colonne sono collegate a 4 pin predisposti come uscite.
- Partiamo da una situazione in cui le 4 uscite si trovano a livello logico 1.
- Poniamo quindi, una alla volta, le colonne a livello logico basso.
Le 4 colonne seguiranno in pratica questa sequenza di stati logici:
0111 > 1011 > 1101 > 1110 per poi ricominciare daccapo. - Subito dopo aver posto una colonna a zero, scansioneremo una alla volta le 4 righe per controllare se una di loro si trova a livello logico basso indicando che un pulsante è stato premuto.
- Confrontando quale colonna e quale riga si trovano a livello logico basso, è possibile risalire al pulsante premuto.
Ovviamente si sarebbe potuto fare anche il contrario: righe come uscita e colonne come ingressi, non sarebbe cambiato nulla. Basta partire da una base di ragionamento.
In questo esempio vi illustrerò il mio personale sistema software per controllare i pulsanti su una tastiera a matrice e visualizzare il tasto premuto su un display lcd.
Questa è una buona base di partenza per potersi realizzare da soli svariati sistemi come ad esempio serrature elettroniche a combinazione o semplici menù su dispositivi elettronici.
Collegamento di un tasterino a matrice sulla scheda Freedom II
Come sempre utilizzerò la scheda di sviluppo Freedom II, per cui l’esempio sarà riferito all’utilizzo di tale scheda.
Per prima cosa dobbiamo procurarci un tastierino a matrice, su Ebay si trovano a prezzi irrisori, se ne avete uno da 3×4 anzichè da 4×4 va bene lo stesso: penso che chi è arrivato a leggere i miei articoli fino a questo sarà sicuramente in grado di adattarsi da solo il codice per eliminare una colonna di tasti.
Con un tester controllate per bene quali sono i pin delle righe e quali sono quelli delle colonne e il loro ordine, se sapete usare il tester e seguite lo schema di come sono collegati i pulsanti, illustrato più in alto, non vi dovrebbe essere difficile capirlo.
Comunque vi illustro qui la piedinatura tastierino che ho usato io, il vostro al 90% dovrebbe seguire la stessa disposizione o comunque variare di poco:
Collegherò il tastierino al banco PORTB, individuiamo quindi sulla FreedomII dove andare a fare i collegamenti:
Trovandomi sulla Freedom II le porte pari da un lato e quelle dispari dall’altro, personalmente ho fatto una scelta che forse potrebbe risultare “infelice” a livello di gestione del codice (ma non tanto: tutto infatti sta ad “organizzarsi mentalmente”), ma sicuramente più semplice a livello di cablaggio: ho collegato le colonne alle porte pari e le righe alle porte dispari. Ovviamente ognuno è libero di fare come vuole, basta poi saper adattare il codice.
Se, come me, sulla FreedomII avete un connettore a pettine (cioè maschio), e se, come me, adorate per i vostri progetti farvi tutto in casa con quello che avete a disposizione, per collegare il tastierino potete utilizzare il mio semplicissimo sistema, che vi illustro:
Da uno strip femmina, utilizzando un seghetto o un minitrapano (tipo dremel) con dischetto diamantato, ritagliatene due spezzoni da 4 pin e rifiniteli per benino con una limetta (può andare bene pure quella per le unghie se vostra moglie non si arrabbia):
Prendete poi una piattina da 8 fili saldatela ai due connettori così ottenuti, isolando i pin con degli spezzoni di termorestringente di opportuno diametro, fino ad ottenere una cosa del genere:
I due connettori li ho poi incollati insieme con una goccia di super-attak per ottenere un connettore 4×2.
Si sarebbe potuto utilizzare direttamente un connettore femmina 4×2 ma poi sarebbe stato molto più difficile eseguire le saldature. Tenete conto difatti che questo tipo di connettore è da stampato e non da cavo.
Infine l’altra estremità della piattina la collegate al vostro tastierino saldandola oppure tramite un altro connettore. Il risultato è sicuramente pulito e riutilizzabile facilmente:
Ci vorrà un secondo per innestarlo in maniera semplice sulla nostra FreedomII:
L’importante è che vi ricordiate perfettamente dove avete collegato le righe e dove le colonne, per questo motivo l’utilizzo di una piattina colorata è sicuramente d’aiuto. Come già detto, io seguirò questo schema di collegamento:
- Colonna 1 > RB0
- Colonna 2 > RB2
- Colonna 3 > RB4
- Colonna 4 > RB6
- Riga 1 > RB1
- Riga 2 > RB3
- Riga 3 > RB5
- Riga 4 > RB7
Per cui il mio codice sarà ottimizzato e fatto apposta per tali connessioni.
Il software di gestione del tastierino numerico
Partiamo dal presupposto che sapete utilizzare l’LCD e avete letto le precedenti lezioni che illustrano come utilizzare la libreria che ho fornito qui.
Innanzitutto, dovendo utilizzare l’LCD per la visualizzazione, mi definirò alcune variabili che mi permetteranno di andare a capo in automatico quando avrò raggiunto la fine della riga e di ritornare alla prima cella della prima riga una volta giunto alla fine del display.
Tali variabili e defines rappresentano in pratica il numero di righe e di colonne del display a nostra disposizione, andranno quindi variati se avete un display diverso, così come dovranno essere variati anche nel file lcd.c come già spiegato nella lezione sull’LCD.
Nel nostro file settings.h avremo quindi:
unsigned char lcdXpos=0; // posizione colonna attuale unsigned char lcdYpos=0; // posizione riga attuale #define MAX_X 16 // numero di colonne del display #define MAX_Y 2 // numero di righe del display |
Mi definisco quindi un array, da scansionare con un ciclo FOR, che mi permetterà di mettere a massa le colonne una alla volta e riportare a livello logico alto le altre:
unsigned char colMask[]= { 0b11111110, // Colonna 1 => RB0 a massa 0b11111011, // Colonna 2 => RB2 a massa 0b11101111, // Colonna 3 => RB4 a massa 0b10111111 // Colonna 4 => RB6 a massa }; |
andando quindi dal valore colMask[0] al valore colMask[3], porterò a massa le colonne una alla volta. L’indice dell’array (ovvero il numeretto da mettere tra parentesi quadre per identificare quale elemento dell’array sto utilizzando attualmente) lo memorizzo in un’altra variabile:
unsigned char colScan=0; // va da 0 a 3 per scansionare l'array colMask |
Se non sapete cos’è un array e come lo si utilizza, cercate informazioni su un testo qualsiasi che parla del linguaggio C, come il Tricky C presente anche nell’area risorse.
Allo stesso modo mi creo un altro array che mi servirà per effettuare la scansione delle righe e controllare quindi quale riga è andata a massa, e di conseguenza una variabile per contenere l’indice della riga scansionata:
unsigned char rowMask[]= { 0b00000010, // Riga 1 0b00001000, // Riga 2 0b00100000, // Riga 3 0b10000000 // Riga 4 }; unsigned char rowScan=0; |
Tutte queste operazioni di verifica e confronto andranno fatte utilizzando gli operatori logici come già spiegato in un precedente articolo, ecco perchè scrissi un articolo a parte solo per spiegare questi concetti: se vogliamo che i nostri programmi siano davvero efficienti e occupino il minimo indispensabile di risorse, dobbiamo imparare ad utilizzarli.
Mi creo, inoltre, ancora un altro array che contiene i simboli disegnati sui pulsanti del tastierino, disposti in un certo ordine:
unsigned char keys[]={'1','4','7','*','2','5','8','0','3','6','9','#','A','B','C','D'}; |
Da notare che, come già detto altre volte, i simboli definiti sono racchiusi tra singoli apici (‘). In questo modo il picmicro in realtà non leggerà “il numero 1” ma il suo corrispondente valore ascii, ovvero 49. Per cui l’array si sarebbe potuto pure scrivere come:
unsigned char keys[]={49,52,55,42 ...
Come vedete il simbolo 1 si trova in posizione zero, il simbolo 4 in posizione 1 ecc: ad ogni elemento del tastierino ho in pratica associato in qualche modo un peso numerico che segue l’ordine di scansione dei pulsanti schematizzato qui:
Dal momento che la tastiera ha 16 tasti potete ben capire perchè gli anglosassoni chiamano questi dispositivi Hex Keypad.
Questo mi renderà le cose estremamente semplici in seguito, in quanto facendo una semplice operazione confrontando riga e colonna, mi tirerò fuori un numero che mi permetterà appunto di risalire al pulsante premuto.
Infine ho ancora altre due variabili: una che mi conterrà appunto il numero risultato dell’operazione appena descritta e un flag che mi segnalerà l’avvenuta pressione di un pulsante qualsiasi e attiverà quindi la routine che si occuperà di scrivere sull’LCD il simbolo disegnato sul tasto:
unsigned char keypressed=0; // peso numerico del pulsante premuto bit keyok; // flag di pulsante premuto |
Per il resto, le porte andranno settate opportunamente per avere le colonne come uscita e le righe con ingresso e predisponendo tutti i pin a livello logico alto e (importante!) attivando le resistenze di pullup su portB ponendo a zero l’apposito bit:
PORTB=0xFF; TRISB=0b10101010; RBPU=0; // resistenze di pullup su portb attivate |
Le resistenze di PullUp su porta B ovviamente saranno attive soltanto sui pin definiti come ingressi.
Nel main, oltre all’inizializzazione dell’LCD, ecco che finalmente arriva il codice.
Mi effettuo la scansione delle colonne contando da zero a 3 e ponendo i relativi pin a livello logico basso uno alla volta e ripristinando a 1 gli altri facendo uso dell’array colMask che ho descritto prima:
for (colScan=0; colScan<4; colScan++) // porto a massa una colonna alla volta { PORTB &= colMask[colScan]; // porto a zero la colonna attuale |
Per ogni colonna posta a zero mi effettuo quindi un ciclo di scansione delle 4 righe più o meno allo stesso modo:
// mi controllo le righe for (rowScan=0; rowScan<4; rowScan++) { |
Controllo quindi se la riga attuale si trova a massa effettuando un AND con l’elemento dell’array rowScan che mi permetterà di verificare soltanto il bit che mi interessa su tutta la porta:
if (!(PORTB & rowMask[rowScan])) // Riga rowScan trovata a massa |
Questa verifica andrà fatta come sempre due volte con un piccolo ritardo per evitare il rimbalzo (anti-rimbalzo).
Se viene confermato che un pulsante è stato premuto, imposto il valore che permette di risalire al tasto e indico al programma che un pulsante è stato premuto:
keypressed=rowScan+(4*colScan); // numero di pulsante premuto keyok=1; // è stato premuto un pulsante |
Come vedete l’operazione è semplice: moltiplico per 4 l’indice di colonna attualmente messa a livello bass0 e sommo l’indice di riga che è stato trovato a massa, in questo modo ottengo un numero da 0 a 15 diverso per ogni pulsante premuto.
Alla fine della scansione, se un pulsante è stato premuto (verificando che keyok=1) attivo un’altra routine che mi permette appunto di stampare sull’LCD il valore impresso sul tasto facendo uso dell’array “keys” definito prima e del valore numerico calcolato:
LCD_PUTCH(keys[keypressed]); |
Utilizzo la funzione LCD_PUTCH che mi permette di stampare un singolo carattere. In più alla pressione di un tasto si sentirà un piccolo suono dal cicalino.
In aggiunta, dal momento che mi piace fare le cose per benino, ho fatto in modo da non permettere la stampa continua se si tiene premuto il pulsante.
Senza prevedere una cosa del genere, se si tenesse premuto il pulsante, il carattere relativo sarebbe stampato di continuo, e questo è davvero una seccatura se decidessimo di utilizzare questo programma per crearci una serratura elettronica a combinazione. Per tale motivo metto in pausa il programma fino a che il tasto non viene rilasciato:
// rimango in un ciclo continuo fino a che il pulsante non viene rilasciato PORTB=0b10101010; while(PORTB != 0b10101010) {continue;} |
In pratica: dopo aver stampato il carattere, metto il banco B in uno stato in cui tutte le colonne sono a massa, se qualche pulsante risulta premuto, subito dopo la porta B non si troverà nello stato 10101010 per cui il while mi farà rimanere in un ciclo infinito che mi impedirà quindi di proseguire nel programma.
Rilasciato il pulsante, la condizione non sarà verificata per cui il ciclo non verrà eseguito e il programma potrà ripartire e continuare ad operare normalmente.
Downloads
- Corso Programmazione PICMicro in C - Approfondimenti - Gestione di un tastierino a matrice (1060 download)
- Microchip Application Note 234 - Hardware Techniques for PICmicro Microcontrollers (2534 download)
- Datasheet 74C922 - 16 Key Encoder (1919 download)