Gestire un’interfaccia utente (UI) in maniera semplice e veloce con un bus bidirezionale

 

Uno dei problemi più comuni e più complessi da risolvere quando si lavora con i microprocessori in ambito hobbistico è sempre stato quello dell’interfacciamento con l’utente che, tipicamente, riesce a comprendere solamente messaggi presentati in modo opportuno su display grafici o alfanumerici nonché spie di segnalazione.
Analogamente, l’unico mezzo che l’utente ha per passare delle informazioni ad un sistema a microprocessore è spesso riconducibile alla pressione di tasti da una tastiera più o meno complessa, piuttosto che da un touch screen o tramite un mouse e dei menu guidati.
Se è vero che oggi i computer e gli smartphone dotati di sistemi operativi moderni riescono ad eseguire comandi impartiti tramite voce, è altresì vero che sistemi piccoli e concettualmente semplici come apparecchi di misura piuttosto che gadget, ancora necessitano di tastiere e display di qualche tipo.

Inoltre la visualizzazione di valori su scale e funzioni (pensate per esempio ad un apparato in grado di leggere contemporaneamente tensione e corrente su due scale di visualizzazione differenti, e di calcolarvi in CosFi nel caso di un’applicazione in alternata) differenti sullo stesso display nel caso di strumenti e apparati da laboratorio, necessita sempre dell’intervento dell’operatore che agisce su tasti e manopole.

Da qualsiasi parte la si guardi, sovente è necessario equipaggiare di tasti e display un sistema a microprocessore, poiché in nessun altro modo di facile attuazione si potrebbe implementare la tipica interfaccia utente.
A tal proposito, oggi il mercato offre valide alternative alla realizzazione in totale autonomia di tastiere e sinottici: per esempio i display LCD sono disponibili in tantissime forme e colori, a prezzi ragionevoli e con interfacce di tipo parallelo o seriale. Stesso discorso vale per le tastiere che sono disponibili a pochi tasti così come complete come quelle dei PC.
In questo articolo descriveremo la scheda LED&KEY che trovate su questo sito con il codice LEDKEY, contenente 8 display a 7 segmenti + punto decimale, 8 LED di segnalazione e 8 pulsanti, che possono essere gestiti piuttosto facilmente attraverso una linea seriale sincrona bidirezionale.

Schema elettrico

Partiamo subito descrivendo lo schema elettrico di tale scheda, basata sul TM1638 LED/key controller prodotto dalla TITAN, azienda cinese che realizza numerosi circuiti integrati per la gestione di tastiere e display.
Il TM1638 viene largamente utilizzato in piccole schede equipaggiate di LED, display e tasti, utili sia in ambito hobbistico che non.
Le caratteristiche principali di questo integrato sono le seguenti:

  • gestisce fino a 10 display a 7 segmenti + punto decimale;
  • lavora sia a catodo che anodo comune;
  • controlla fino a 24 pulsanti;
  • dispone di 8 livelli di luminosità;
  • lavora con interfaccia sincrona a 3 fili con dati, clock e strobe.

Fig. 1

Per ulteriori informazioni potete consultare il datasheet, scaricabile dalla scheda del prodotto. Per completezza riportiamo in Fig. 2 la piedinatura dell’integrato TM1638. Analizziamo uno ad uno i suoi piedini partendo da DIO (piedino 26) che è l’ingresso/uscita dati: in scrittura, il dato è trasferito sul fronte di salita del CLK; in uscita, sul fronte di discesa. In trasmissione, è un open drain e richiede un pull-up.
CLK è l’ingresso di clock e fa capo al pin 27, mentre lo strobe è localizzato sul pin STB (28): inizializza l’interfaccia sul fronte di discesa per ricevere istruzioni; il primo byte che segue tale fronte è considerato un’istruzione.

Quando viene processata un’istruzione gli altri processi vengono bloccati. Con STB a livello alto, il CLK viene ignorato. K1-K3 sono le linee della tastiera (rispettivamente piedini 1, 2, 3) e corrispondono all’ingresso dei pin dati dalla tastiera (hanno resistori di pull-up interni da 10 kohm). SG1-KS1/SG8-KS8 sono le uscite di comando dei segmenti (pin da 5 a 12) ovvero uscita di comando dei segmenti (P channel open drain) in multiplex e scansione tasti letti attraverso K1÷K3.

GRID1-GRID8 (uscite dei digit) ovvero piedini 19-24, permettono l’attivazione in multiplex delle cifre del display ad anodo comune; sono linee open-drain facenti capo a MOSFET a canale N.
Infine Vdd (piedino 15) è l’alimentazione positiva del chip, riferita a massa (ossia a GND, pin 18).

Fig. 2

 

La programmazione

Vista la natura didattica di questo articolo, abbiamo pensato di spiegare come utilizzare la nostra scheda LedKey. In prima istanza siamo stati tentati dall’utilizzare l’immancabile Arduino, quindi abbiamo iniziato a cercare in rete degli spunti per aiutarci a realizzare delle librerie nostre di facile utilizzo e da condividere con i nostri lettori.
Ma cercando e cercando, abbiamo trovato molte librerie di terze parti che consentono di utilizzare questo dispositivo sia con Arduino che altri sistemi embedded.
Al contrario, non siamo riusciti a trovare alcunchè per applicazioni in PICBASIC MELABS, pertanto abbiamo pensato di dare il nostro contributo creando delle librerie di lettura e scrittura della scheda preconfezionate: a voi basterà includere due file specifici in due punti precisi del vostro listato picBasic, e chiamare le varie subroutine nei modi che andremo a spiegarvi in questo articolo teorico.

Le librerie per PicBasic

In realtà, non dovremmo chiamarle librerie poiché si tratta di due spezzoni di codice vero e proprio scritte in PicBasic Melabs, inseriti in due file distinti che sono:

LED_KEY_VARS.PBP
LED_KEY_SUBS.PBP

Il primo contiene tutte le variabili e le costanti necessarie per il funzionamento delle varie subroutines. Le variabili sono molte, tutte di tipo “byte” e “bit”, e vengono utilizzate in maniera piuttosto intensiva in tutte le varie subroutine.
Il secondo contiene tutte le subroutines con cui è possibile leggere e scrivere la scheda: le subroutine devono essere invocate dopo aver preparato il pacchetto di dati che esse devono processare qualora si tratti di una scrittura a display; nel caso di una lettura dei pulsanti, invece, è sufficiente invocare la lettura e immediatamente dopo leggere una variabile che contiene il risultato della lettura per poterla poi gestire dal programma principale.

 

Utilizzo in pratica

Andiamo dunque a sperimentare un po’ con la scheda LED&KEY, premettendo che faremo riferimento alla configurazione hardware proposta nello schema di connessione visibile in queste pagine e chiamato schema microcontrollore; come vedete, in questa applicazione immaginiamo di lavorare su un PIC16F88 e pertanto i pin cui faremo riferimento negli esempi saranno quelli di tale microcontrollore.
Il connettore LEDKEY serve a connettere la nostra interfaccia utente, PICKIT2 proviene dal programmatore che, oltre a programmare il PIC16F88, fornisce anche la tensione di 5Vcc (opzione questa supportata dal sistema) per alimentare e testare il circuito. Infine DS1302 serve perché tra i nostri esperimenti abbiamo realizzato un orologio digitale.
Bene, ciò detto andiamo subito agli aspetti firmware; la prima cosa da fare è inserire nel vostro listato, all’inizio delle dichiarazioni di variabile, la seguente istruzione:

INCLUDE LED_KEY_VARS.PBP

La dichiarazione di include deve tenere conto del percorso che il compilatore deve fare per cercare il file (path), per cui, per comodità di utilizzo e di ricerca, conviene copiare il file nella stessa cartella dove è contenuto il file del vostro progetto.
In questo modo quando si compilerà il programma, l’ambiente di sviluppo sostituirà in automatico alla riga di INCLUDE tutto il contenuto del file, inserendo di fatto la lista di variabili e costanti esattamente come fareste voi a mano.
Successivamente, occorre inserire nel vostro codice, ad esempio per comodità alla riga precedente la chiusura di programma END, verso il basso, dove sapete che non aggiungerete altro codice, la seguente istruzione (anche qui vale il precedente discorso del path):

INCLUDE LED_KEY_SUBS.PBP

Trattandosi di subroutine, è comunque conveniente posizionarle virtualmente alla fine del vostro listato, lasciandovi visibilità sulle sub di vostra ideazione.
Infine è necessario dire al vostro programma quali sono i tre pin con cui il micro comunicherà con la scheda.
Per fare ciò si devono aggiungere, nella sezione di definizione degli I/O, le tre linee di codice che definiscono i tre pin di comunicazione con la scheda. Nell’esempio sotto riportato abbiamo ipotizzato di utilizzare i primi tre bit della porta C di un PIC, ma nulla ci vieta di usare altri pin di altre porte, anche mischiati. Infatti nel nostro esercizio abbiamo usato i PB0, PB1 e PB2.

SYMBOL STROBE = PORTC.2
SYMBOL SCL = PORTC.1
SYMBOL SDA = PORTC.0

A questo punto il vostro programma tipo assomiglierà come struttura a quello visibile nel Listato 2. Se non ci sono errori nella vostra parte di codice, lanciando una compilazione il sistema genererà un HEX senza alcuna segnalazione di errore.

Le subroutine

Vediamo ora le varie subroutine nel dettaglio, tenendo presente che quello che noi volevamo ottenere è qualcosa di facile da comprendere ed utilizzare, senza appesantire troppo il vostro sorgente.
L’integrato adottato in questa scheda è molto potente e consente di fare molte cose, come ad esempio l’indirizzamento diretto sia per la scrittura di un LED o display specifico, sia per la lettura di un tasto specifico. Consente anche la gestione in scrittura ad incremento automatico della posizione del carattere, così come la scrittura per singolo bit di ogni elemento luminoso…
Tutto ciò è molto utile e molto interessante, nonché molto bello da avere e usare, se si hanno a disposizione tanti kB di memoria sacrificabili.
Ma noi volevamo che questo oggetto fosse utilizzabile anche con microprocessori molto piccoli, cosi abbiamo ideato una serie di routine generalizzate, un po’ “chiuse” se vogliamo, ma praticamente “plug&play” (usando un termine ormai sdoganato)..
Queste routine sono:
CLEAR_ALL: spegne tutti i display e i led. Non necessita di alcun parametro.
SET_ALL: accende tutti i display e led. Non necessita di alcun parametro.
CLEAR_DIGITS: spegne tutti i display. Non necessita di alcun parametro.
SET_DIGITS: accende tutti i display. Non necessita di alcun parametro.
CLEAR_LEDS: spegne tutti i LED. Non necessita di alcun parametro.
SET_LEDS: accende tutti i LED. Non necessita di alcun parametro.
WRITE_DOTS: scrittura diretta dei LED. Parametro DOTS. Prima di essere chiamata, occorre scrivere nella variabile DOTS i LED da accendere o spegnere. Ad esempio:
-DOTS = %00000000: tutti spenti
-DOTS = %11111111: tutti accesi
-DOTS = %10000000: acceso solo il primo a sinistra.
PRINT_TEXT: contiene il testo da mandare ai display a 7 segmenti. Necessita dei parametri DGT(0÷7) e DP. Prima di invocarla, occorre impostare le variabili DGT da 0 a 7 (array di 8 byte) e la variabile DP che contiene i punti decimali da visualizzare. DGT(0) è il display a sinistra. DGT(7) quello a destra. Nelle variabili da DGT(0) a DGT(7) si deve inserire il carattere alfanumerico da visualizzare a display mentre nella variabile DP si devono definire in modo binario i punti decimali da accendere.
Nel seguente esempio si vuole scrivere a display la parola “PAUSA”, partendo dal primo display a sinistra, e si vuole che ogni carattere abbia acceso anche il suo punto decimale.
DGT(0) = “P”
DGT(1)= “A”
DGT(2)= “U”
DGT(3)= “S”
DGT(4)= “A”
DGT(5)= “ “
DGT(6)= “ “
DGT(7)= “ “
DP= %11111000
GOSUB PRINT_TEXT
DISPLAY_BRIGHT: controlla la luminosità del display. Necessita del parametro DISP_BRIGHT, che deve essere posto uguale ad una delle contanti da BRIGHT_0 a BRIGHT_8, che definiscono la luminosità rispettivamente dal spento (BRIGHT_0) al massimo (BRIGHT_8) su 8 livelli.
Ad esempio:

DISP_BRIGHT = BRIGHT_8 ‘ massima luminosità
GOSUB DISPLAY_BRIGHT ‘ invoca la sub.

GET_SWITCH: legge i tasti. La lettura viene restituita sulla variabile SWITCH dove, in binario,
MSb = tasto tutto a sinistra
LSb = tasto tutto a destra.
Ad esempio se dopo una GOSUB GET_SWITCH la variabile SWITCH vale %11110000, significa che sono premuti i quattro tasti a sinistra. Analogamente %00000000 direbbe che nessun tasto è premuto come %11111111 direbbe che tutti i tasti sono premuti.
WRITE_DISPLAY, WRITE_MODE, SEND_CHAR sono routine di supporto a tutte le altre e non andrebbero mai invocate, dato che ci pensano le altre sub a chiamarle al momento opportuno, anche se, volendo approfondire il discorso dell’indirizzamento diretto, i più esperti potrebbero anche utilizzarle per velocizzare le operazioni. È infatti logico che scrivere ogni volta 8 display e 8 led necessita più cicli macchina di quanti ne occorrerebbero per puntare ad un indirizzo direttamente e scriverci dentro un valore preciso.

Schema microcontrollore

Vediamo ora le routine nel dettaglio, secondo l’ordine di livello ossia partendo da quelle a più basso livello, più semplici, e salendo via via verso quelle più complesse, che richiamano a loro volta altre subroutines annidiate.

SEND_CHAR

A meno che non sia utilizzata da un utente esperto che ha approfondito il discorso dell’indirizzamento diretto delle memorie, essa non viene mai invocata se non dalle varie sub in automatico ogni volta che il micro deve inviare un’istruzione di qualche tipo alla scheda.
Le istruzioni possono essere di tipo “leggi tastiera” o “scrivi display e LED”. È la routine più utilizzata, poiché è asservita a tutte le altre: in pratica invia, attraverso le linee preposte alla comunicazione, il dato di 1 byte contenuto nella variabile DATA_IO, utilizzando il protocollo seriale sincrono. Utilizza la sola funzione picbasic SHIFTOUT e il contenuto di DATA_IO non viene normalmente stabilito dall’utente, se non in alcuni casi, come ad esempio quando si imposta la luminosità del display, ma anche in questo caso in modo indiretto.

SEND_CHAR:
shiftout SDA,SCL,4,[data_io]
‘ LSB first, CLOCK idle HIGH
return

Fig. 3

 

GET_SWITCH

Legge i pulsanti e il suo utilizzo è molto semplice: la si invoca ed immediatamente dopo (o quando servirà) si legge il contenuto della variabile SWITCH bit a bit. Il bit 7 corrisponde al primo pulsante a sinistra e il bit 0 all’ultimo (lettura bitwise).
Quando viene chiamata, per prima cosa imposta la variabile DATA_IO a READ_MODE ($42) quindi RESETTA lo strobe per iniziare la scrittura del comando (chiama la SEND_CHAR già vista).
A questo punto inizia la lettura di quattro byte (memorizzati nelle variabili da SW1 a SW4) tramite la funzione SHIFTIN, operazione terminata dallo strobe SETTATO.
Infine la variabile SWITCH viene scritta bit a bit verificando lo stato dei bit 6 e 2 delle quattro variabili SW1÷SW4. Va notato che l’integrato TM1638 è in grado di gestire una matrice di tasti molto estesa, mentre qui sono usati solamente 8 pulsanti, mappati dal costruttore della scheda nei bit 6 e 2 delle quattro variabili, secondo la maschera nel codice nel riquadro.
Il perché di questa scelta, piuttosto che inserirli tutti in una unica variabile, non ci è dato di sapere; comunque la parte difficile l’abbiamo fatta noi, a voi basta leggere il contenuto della singola variabile SWITCH per ricavare lo stato dei vari pulsanti.

GET_SWITCH:
data_io = read_mode ‘ Impostazione per lettura di 4 byte.
low strobe
‘ Inizio lettura
gosub SEND_CHAR: ‘
shiftin sda,scl,4,[sw1,sw2,sw3,sw4] ‘ Legge i 4 byte.
high strobe
‘ Fine lettura.
‘ MSB primo, legge il dato dopo il clock.
switch.7 = sw1.6 ‘ Converte i 4 byte letti
switch.6 = sw2.6 ‘ in un singolo byte
switch.5 = sw3.6 ‘ contenuto nella var
switch.4 = sw4.6 ‘ SWITCH.
switch.3 = sw1.2 ‘
switch.2 = sw2.2 ‘
switch.1 = sw3.2 ‘
switch.0 = sw4.2 ‘
return ‘

CLEAR_ALL e SET_ALL

Come intuibile dai nomi, la prima spegne completamente tutti i LED e display, mentre la seconda accende tutto; entrambe le operazioni vengono effettuate con 16 scritture ricorsive di 0 o 255 a tutte le locazioni, partendo da un indirizzo base, dove si trova il primo display a sinistra, e impostando l’incremento automatico dell’indirizzo.
Di fatto, anche se ha due nomi distinti, che altro non sono che due LABEL di programma, la routine è una sola.
L’inizio avviene SETTANDO o RESETTANDO il bit SET_CLEAR il cui valore, 1 o 0, determina l’accensione o lo spegnimento totale. Quindi è chiamato il modo scrittura con l’istruzione GOSUB WRITE_MODE, routine questa che esegue le operazioni seguenti:
assegna alla variabile DATA_IO il valore AUTO_ADDR ($40), che dice alla scheda di passare automaticamente al carattere successivo ad ogni nuovo carattere ricevuto;
abbassa il pin di STROBE, manda il carattere e alza il bit di STROBE per finire la scrittura del carattere.

Ora il programma torna alla routine principale, che mette in DATA_IO l’indirizzo di partenza ossia FIX_ADDR ($C0) che punta al primo display di sinistra.
Viene attivato ancora lo STROBE per avviare una nuova scrittura a display e ancora una volta invocata la SEND_CHAR già vista in precedenza.
Parte quindi un ciclo di 16 iterazioni in cui a seconda dello stato di SET_CLEAR viene mandato al display o $00 o $FF per 16 volte.
Tutti i cicli pari azzerano o accendono un display, mentre tutti i cicli dispari accendono o spengono un led, secondo la mappatura del TM1638.
La routine termina quindi con la chiamata alla sub DISPLAY_BRIGHT, che imposta runtime la luminosità del display. Come già anticipato è possibile impostare 9 valori da spento a massimo nella variabile DISP_BRIGHT semplicemente ponendola uguale a una costante compresa tra BRIGHT_ 0 (spento) e BRIGHT_8 (massima luminosità) prima della GOSUB DISPLAY_BRIGHT. Volendo essa può essere invocata a piacimento, dopo avere scritto in DISP_BRIGHT il valore di luminosità voluto, ad esempio per enfatizzare l’esito di un’operazione facendo brillare più o meno i caratteri.

CLEAR_ALL: ‘ Spegne display e led.
low SET_CLEAR ‘
goto UPDATE_ALL ‘
SET_ALL: ‘ accende display e led.
high SET_CLEAR ‘
UPDATE_ALL: ‘
gosub WRITE_MODE: ‘chiama autoincremento
STROBE = 0 ‘inizio scrittura
data_io = FIX_ADDR ‘Imposta indirizzo
gosub SEND_CHAR: ‘ Scrittura
for counter = 1 to 16 ‘
if SET_CLEAR=0 then ‘
data_io = 0 ‘ spegne tutto
else ‘
data_io = 255 ‘ accende tutto
endif ‘
gosub SEND_CHAR ‘ Scrittura
next ‘
Strobe = 1 ‘ Fine scrittura
gosub DISPLAY_BRIGHT ‘ Imposta luminosità.
return ‘
WRITE_MODE:
DATA_IO = AUTO_ADDR
strobe = 0
gosub send_char:
strobe = 1
return
DISPLAY_BRIGHT:
data_io = disp_bright
strobe = 0
gosub SEND_CHAR:
strobe = 1
return

CLEAR_DIGITS e SET_DIGITS

Anche in questo caso il nome la dice lunga: la prima azzera tutti i display a 7 segmenti e la seconda li accende tutti. Funzionalmente è simile alla CLEAR_ALL/SET_ALL, ma ci sono delle differenze.
Innanzitutto, invece di eseguire 16 scritture in autoincremento, ne esegue solamente 8 nelle posizioni pari da 0 a 14, occupate dai display, utilizzando l’indirizzamento diretto alla locazione di memoria relativa al display. Poi viene coinvolta un’altra subroutine asservita, la WRITE_DISPLAY, il cui compito è quello di mandare prima l’indirizzo dove scrivere e poi il carattere da scriverci. Infine, il contatore di loop ora è rappresentato dall’indirizzo del display (DISP_ADDR) che ad ogni ripetizione viene incrementato di 2 unità. Si fa infine uso di una nuova variabile, la SEG_VAL, qui scritta direttamente a 0 o 255, che come vedremo contiene la conversione dal carattere alfanumerico al suo equivalente (con la migliore approssimazione possibile) in 7 segmenti.

CLEAR_DIGITS: ‘ Spegne tutti i display.
low SET_CLEAR ‘
goto UPDATE_DIGITS ‘
SET_DIGITS: ‘ Accende tutti i display
HIGH SET_CLEAR ‘
‘GOTO UPDATE_DIGITS ‘

UPDATE_DIGITS: ‘
for DISP_ADDR = 0 to 14 step 2
‘ parte il loop a ind. diretto
if SET_CLEAR=0 then ‘
SEG_VAL = 0 ‘ Spegne i display.
ELSE ‘
seg_val=255 ‘ Accende i display.
endif ‘
gosub write_display:
‘ Chiama scrittura indirizzamento diretto.
next ‘
gosub display_bright ‘ Imposta luminosità.
return ‘

WRITE_DISPLAY:
data_io = FIX_ADDR + DISP_ADDR
‘ imposta indirizzo.
strobe = 0
‘ strobe 0 per inizio scrittura
gosub send_char: ‘ manda indirizzo
data_io = SEG_VAL ‘ imposta il digit
gosub send_char: ‘ manda il digit
STROBE = 1 ‘ finita scrittura
gosub display_bright:‘ manda luminosità.
return ‘

CLEAR_LEDS e SET_LEDS

Come nelle precedenti CLEAR_DIGITS e SET_DIGITS è funzionalmente identica come concetto e come variabili di servizio; utilizza sempre l’indirizzamento diretto per puntare alle locazioni di memoria, ma in questo caso a quelle dispari, che contengono l’informazione di led (assegnato al bit 0 della cella di memoria) acceso o spento. Stiamo parlando dei LED posti sopra i display.

CLEAR_LEDS: ‘ Spegne tutti i led.
low SET_CLEAR ‘
GOTO UPDATE_LEDS ‘
SET_LEDS: ‘ Accende tutti i led.
high SET_CLEAR ‘
‘goto UPDATE_LEDS ‘
UPDATE_LEDS: ‘
for DISP_ADDR = 1 to 15 step 2
if set_clear=0 then ‘
SEG_VAL = 0 ‘ Spegne i led.
else ‘
seg_val = 1 ‘ accende i led
endif ‘
gosub write_display:
‘ Chiama scrittura indirizzamento diretto.
next ‘
gosub display_bright ‘ Imposta luminosità
return ‘

WRITE_DOTS

Utilizza l’indirizzamento diretto della scrittura per accendere e spegnere in base alle necessità i led posti sopra i caratteri a 7 segmenti. Prima di chiamarla occorre impostare bit a bit la variabile DOTS, che contiene lo stato di ON/OFF di ogni LED. L’MSb corrisponde al primo led a sinistra mentre l’LSb corrisponde all’ultimo a destra. Ogni bit della variabile DOTS ha un alias che lo contraddistingue: ad esempio DOT7 è il bit 7 di DOTS, che rappresenta il primo LED a sinistra, posto sopra il primo display a sinistra. Analogamente DOT0 è l’ultimo LED a destra. Si inizia impostando la variabile DOTS quindi si invoca la WRITE_DOTS che punta agli indirizzi dispari di memoria in un ciclo di 8 volte, in cui viene incrementato l’indirizzo di scrittura e assegnato ad esso il livello logico del bit. Nel primo ciclo la ruotine assegna al primo LED a sinistra il contenuto di DOT7, nel secondo ciclo assegna al secondo LED il contenuto di DOT6 e così via fino alla fine.

WRITE_DOTS:
for DISP_ADDR = 1 to 15 step 2
‘ led tutti digit A display dispari
select case disp_addr ‘
case 1 ‘
seg_val = dot7 ‘
case 3 ‘
seg_val = dot6 ‘
case 5 ‘
seg_val = dot5 ‘
case 7 ‘
seg_val = dot4 ‘
case 9 ‘
seg_val = dot3 ‘
case 11 ‘
seg_val = dot2 ‘
case 13 ‘
seg_val = dot1 ‘
case 15 ‘
seg_val = dot0 ‘
end select ‘
gosub write_display ‘
Scrive i led.
next ‘
return ‘

PRINT_TEXT

La più lunga delle sub, serve per scrivere a display i messaggi richiesti (Listato 1). Lavora a indirizzamento diretto, ma dal punto di vista dell’utilizzo questo non traspare. Prima di chiamarla, deve essere messo il testo da scrivere nell’array DGT. DGT(0) è il primo display a sinistra e DGT(7) l’ultimo a destra.
Occorre anche impostare bit a bit la variabile DP, in cui il bit 7 rappresenta il punto decimale del primo display a sinistra e il bit 0 il punto del display a destra.
Una volta preparate le 9 variabili (si veda l’esempio nel codice in cui si scrive CIAO seguita da quattro spazi vuoti e ogni lettera ha il punto decimale acceso), viene invocata con l’istruzione GOSUB PRINT_TEXT.
La prima cosa che fa è avviare un ciclo di 8 ripetizioni in cui va a leggere ogni singolo carattere dell’array e lo converte nel corrispettivo in formato 7 segmenti. Per esempio, nel nostro caso il primo carattere a sinistra, DGT(0), è “C” che in 7 segmenti corrisponde ai segmenti d(bit3), e(bit4) e g(bit6) ossia %01011000. La conversione viene effettuata attraverso un select case in cui sono stati inseriti tutti i caratteri alfanumerici di interesse insieme alla relativa conversione.
Le conversioni vengono poi memorizzate in un altro array di 8 byte, chiamato SEG7; quindi il carattere contenuto nella variabile DGT(0) avrà il suo corrispondente a 7 segmenti nella variabile SEG7(0) e così via.
Terminata la conversione degli 8 caratteri, occorre assegnare ad essi il punto decimale, se necessario, sapendo che esso si trova sempre nel bit 7.
Per fare ciò occorre mettere in OR bit per bit il contenuto di ogni variabile SEG7 con la maschera %10000000, contenuta nella costante DP_MASK, ma solo se il relativo bit nella variabile DP è settato.
In altre parole, se il bit 7 di DP è a 1 significa il carattere contenuto in DGT(0) deve avere il punto decimale, quindi il suo equivalente a 7 segmenti, contenuto in SEG7(0), deve essere messo in OR con DP_MASK per settare il bit 7 del punto decimale. Il risultato è che se prima SEG7(0) valeva %01011000 ( c ), dopo l’OR varrà %11011000 (c.).
Naturalmente lo stesso discorso vale per ogni carattere. Dopo aver convertito tutti i caratteri nell’equivalente a 7 segmenti e assegnato ad essi il punto decimale se necessario, non rimane che avviare il loop di 8 scritture ad indirizzamento diretto sugli indirizzi pari (caratteri) per mostrare a display il testo richiesto.

Listato 1

‘ DGT(0) = “C”
‘ DGT(1) = “I”
‘ DGT(2) = “A”
‘ DGT(3) = “O”
‘ DGT(4) = “ “
‘ DGT(5) = “ “
‘ DGT(6) = “ “
‘ DGT(7) = “ “
‘ DP = %11110000
‘gosub PRINT_TEXT:
PRINT_TEXT: ‘ scansione carattere e ricodifica a 7seg.
for CNT = 0 to 7 ‘
digit = DGT(CNT) ‘ variabile di servizio usata per confronto.
SELECT CASE digit ‘ a seconda del valore cambia array SEG7.
case “0”
seg7(CNT) = %00111111
case “1”
seg7(CNT) = %00000110
“tutti i case per i numeri”
case “9”
seg7(CNT) = %01101111
case “ “
seg7(CNT) = %00000000
case “a”,”A”
seg7(CNT) = %01110111
case “b”,”B”
seg7(CNT) = %01111100
case “c”,”C”
seg7(CNT) = %01011000
“tutti i case delle lettere”
case “z”,”Z”
seg7(CNT) = %01011011
case “-”
seg7(CNT) = %01000000
case “_”
seg7(CNT) = %00001000
case “.”
seg7(CNT) = %10000000
END SELECT
next CNT
‘ aggiunge il punto decimale dove necessario.
‘DP = %11111111 solo per prova.
if dp.7= 1 then seg7(0) = seg7(0) | dp_mask ‘ OR bitwise con bit7.
if dp.6= 1 then seg7(1) = seg7(1) | dp_mask
if dp.5= 1 then seg7(2) = seg7(2) | dp_mask
if dp.4= 1 then seg7(3) = seg7(3) | dp_mask
if dp.3= 1 then seg7(4) = seg7(4) | dp_mask
if dp.2= 1 then seg7(5) = seg7(5) | dp_mask
if dp.1= 1 then seg7(6) = seg7(6) | dp_mask
if dp.0= 1 then seg7(7) = seg7(7) | dp_mask
for disp_addr = 0 to 14 step 2 ‘
seg_val = seg7(disp_addr /2) ‘
gosub write_display: ‘ chiama scittura diretta.
next ‘
gosub display_bright: ‘ Imposta luminosità.
return ‘

 

CODICE DI ESEMPIO

Nel Listato 2 vedete un programma generico di test, che a distanza di un secondo accende tutto, spegne tutto, accende e spegne i soli LED, accende e spegne i soli display, avvia un ciclo che legge i pulsanti e ne mostra la condizione sia sui LED che sul display, scrivendo 1 sul display 1 se il tasto 1 è premuto, 2 per il successivo, 3 per il successivo ancora e così via. La definizione dei registri del micro è legata al micro stesso, pertanto è a vostra discrezione. Come potete notare, è sufficiente includere variabili e costanti in cima al sorgente e le subroutine in coda, in modo da avere visuale libera per quanto riguarda il codice sorgente utente.

 

Listato 2

‘ Definizione dei registri del micro. Dipendono dal micro scelto.
‘ Questa parte è a discrezione dell’utente.
…
‘ Queste definizioni sono obbligatorie; i pin sono a titolo di esempio
‘ e possono essere diversi.
SYMBOL STROBE = PORTA.2
SYMBOL SCL = PORTA.1
SYMBOL SDA = PORTA.0
…
‘ da qui in poi ci sono le definizioni di pin del programma utente.
…
‘-------------------------------------------------------------------
‘ Definizione di tutte le variabili e costanti utente.
INCLUDE LED_KEY_VARS.PBP ‘ obbligatoria insieme alle variabili
‘ da qui in poi le variabili utente.
…
‘-------------------------------------------------------------------
GOSUB SET_ALL ‘ accende tutto
pause 1000
GOSUB CLEAR_ALL ‘ spegne tutto
pause 1000
GOSUB SET_LEDS ‘ accende tutti i led
pause 1000
GOSUB CLEAR_LEDS ‘ spegne tutti i led
pause 1000
GOSUB SET_DIGITS ‘ accende tutti i digit
pause 1000
GOSUB CLEAR_DIGITS ‘ sopegne tutti i digit
pause 1000
MAIN: ‘ loop principale.
gosub GET_SWITCH ‘ legge i pulsanti
DOTS = SWITCH ‘ li trasferisce
gosub WRITE_DOTS ‘ sui led
if DOT7 = 0 then ‘ se pulsante 1 a riposo
DGT(0) = “-” ‘ scrive “-“ su display 1
Else ‘ altrimenti
DGT(0) = “1” ‘ scrive “1”
Endif ‘
if DOT6 = 0 then ‘ se pulsante 2 a riposo
DGT(1) = “-” ‘ scrive “-“ su display 2
else ‘ altrimenti
DGT(1) = “2” ‘ scrive “2”
Endif ‘ e così via.
if DOT5 = 0 then
DGT(2) = “-”
else
DGT(2) = “3”
endif
if DOT4 = 0 then
DGT(3) = “-”
else
DGT(3) = “4”
endif
if DOT3 = 0 then
DGT(4) = “-”
else
DGT(4) = “5”
endif
if DOT2 = 0 then
DGT(5) = “-”
else
DGT(5) = “6”
endif
if DOT1 = 0 then
DGT(6) = “-”
else
DGT(6) = “7”
endif
if DOT0 = 0 then
DGT(7) = “-”
else
DGT(7) = “8”
endif
gosub PRINT_TEXT ‘ scrive tutti i display
pause 100 ‘ pausa.
GOTO MAIN ‘
‘-------------------------------------------------------------------
INCLUDE LED_KEY_SUBS.PBP ‘ include tutte le “librerie”
‘-------------------------------------------------------------------
END ‘ fine programma

Download del codice per Led&Key

Il codice completo per l’interfaccia universale può essere scaricato direttamente da questo link

CONCLUSIONI

Come premesso all’inizio dell’articolo, in queste pagine volevamo proporvi una soluzione Hardware/Firmware molto semplice da utilizzare, che permettesse di aggiungere ad una qualsiasi piattaforma a microprocessore o controllore PIC, anche di dimensioni contenute, un’interfaccia utente dotata sia di pulsanti che display e led, compatta e semplice da utilizzare sia dal punto di vista HW che FW. Come hardware direi che ci siamo: compatta, rettangolare, necessita solo di 5 fili e 4 fori di fissaggio, più una feritoia per il display su un eventuale pannello. A tal proposito, vi ricordiamo che qualora voleste realizzare un contenitore ad hoc, Futura Elettronica offre un efficiente servizio di taglio laser.
Per quanto riguarda il FW, a noi il risultato ottenuto sembra piuttosto buono, seppur certamente migliorabile come dimensioni ed efficienza. A voi il compito di divertirvi utilizzandolo ed eventualmente migliorarlo, nel puro spirito della didattica che portiamo avanti. Continuate a seguirci perché nei prossimi numeri pubblicheremo il progetto di un orologio molto semplice, che abbiamo realizzato per sperimentare tanto la scheda a display quanto una scheda di cui non abbiamo ancora parlato, che monta un real time clock modello DS1302.

 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

Menu