Pilotare un LCD grafico con controller KS0108

Partendo dalla necessità di sviluppare uno strumentino da pannello per visualizzare grafici e valori numerici con caratteri di varie dimensioni, ho rinunciato al classico LCD con standard HD44780 per utiizzare invece un pannello “grafico” ovvero con la possibilità di settare un pixel alla volta. Se vi interessa l’argomento vi consiglio davvero di comprenderne a fondo la logica di funzionamento, così potrete creare liberamente effetti grafici performanti adattandoli alle vostre esigenze.

Scegliendo che prodotto acquistare, vanno considerate diverse cose:

  • Il tipo di connettore
  • Il tipo di controller
  • La dimensione in pixel
  • Il colore della retroilluminazione
  • La tensione di contrasto dell’LCD

Riassumendo brevemente questi parametri, ho scelto un prodotto che avesse dei fori sullo stampato a passo standard 2.54mm, così da poter agevolmente saldare dei cavetti/connettori e rendere il tutto flessibile come primo prototipo.

Come controller ho scelto il KS0108, perché cercando online ho trovato molta documentazione ed informazioni sul suo funzionamento nel dettaglio.

Per la dimensione in pixel la mia scelta è ricaduta nel 128×64, il quale internamente è suddiviso in due controller separati ma con le stesse connessioni: inviando l’informazione al primo controller potete lavorare sul riquadro 64×64 pixel in alto, mentre con il secondo controller lavorate sul 64x64px in basso. avessi acquistato un 192×64 pixel avrei trovato tre controller separati. In realtà la gestione è più semplice di quello che pensate, lo vedrete più sotto.

Io non utilizzerò la retroilluminazione perché devo mantenere i consumi bassissimi, ma verificando nel datasheet ad esempio la retroilluminazione bianca consuma molto meno rispetto ad altri colori (tuttavia la vita in ore sembra minore!): da tenere in considerazione.

Come ultimo punto, parlando della tensione di regolazione del contrasto, alcuni LCD richiedono una tensione negativa: diventa una grossa noia perché non è facile ottenere una tensione negativa con una scheda prototipo se non avete i componenti necessari a disposizione, inotre non mi sembrava il caso impegnare un alimentatore da banco solo per ottenere la tensione negativa in questione. Il modello che ho scelto, nonostante abbia questa peculiarità, è provviso di un convertitore DC/DC interno in grado di fornire le tensione negativa necessaria: è sufficiente collegarlo con un trimmer al pin del contrasto ed il gioco è fatto… In realtà ho avuto qualche noia con il contrasto perché ho scoperto -dopo una minuziosa e noiosissima ricerca- che il pin di reset non va collegato a VCC direttamente: va invece utilizzata una resistenza di pull-up da almeno 10kohm per limitare la corrente.

Avendo a disposizione diverse FRDM_KL25Z, che utilizzo come programmatori per micro ARM, ho deciso di sfruttarne una per pilotare il display grafico in questione. Come spiegato in questo articolo KL25Z come programmatore OpenSDA ho prima di tutto preparato la board in modo tale da poterla programmare agevolmente via USB (con il jumper J6 inserito per collegare il clock SDA con il clock del micro della scheda!).

Prima di connettere l’LCD alla board ho verificato nel dettaglio di che connessioni si tratta e come potete notare sono tutti pilotabili con I/O digitali, quindi molto semplice:

  • CS1, CS2: abilitano il controller 1 oppure il controller 2, cioè le due metà dello schermo utilizzabili;
  • DB0-DB7: vanno abililtati per descrivere il byte di dati in uscita verso l’LCD;
  • EN: pin di Enable, il quale avvisa il controller in uso di considerare valido il byte descritto sui pin DB0-DB7;
  • RW/DI: indicano al controller che tipo di operazione voglio effettuare (lettura, scrittura, comando);
  • RST: pin di reset del micro, deve restare a livello alto per mantenere i controller attivi;
  • Vee: contrasto dell’LCD, va collegato con apposito trimmer, vedesi schema più sotto.

Poi ho connesso l’LCD alla board controllando lo schematico della stessa in quanto alcuni pin del micro potrebbero essere open drain/collector (in quel caso per ottenere un livello logico alto è necessario per forza aggiungere un pull-up) oppure come nel caso del pin “PTD1” essere in comune con il LED RGB montato sulla scheda… Direi da evitare se possibile.

Graphical LCD pin diagram

In questo schema potete vedere tutte le connessioni necessarie, ma tenete ben presente la resistenza di pull-up sul pin RST. Mi raccomando: prima di alimentare il tutto assicuratevi di non superare la tensione indicata dal datasheet e di aver collegato correttamente i pin, altrimenti rischiate di compromettere irrimediabilmente i controller! A questo punto non mi resta che buttar giù un pò di codice! Senza entrare nel dettaglio specifico di Codewarrior con compilatore gcc-arm, vi spiego piuttosto come comunicare correttamente con il controller KS0108 così sarete in grado di utilizzarlo indipendentemente dal linguaggio,compilatore o micro utilizzato.

Visto che prima di tutto i controller vanno inizializzati, e per farlo dobbiamo dobbiamo inviare loro dei comandi, prepariamoci prima di tutto il codice necessario per inviare un byte all’LCD.

Nel mio caso, utilizzando CodeWarrior, setto i pin in ProcessorExpert indirizzandoli come indicato nello schema più sopra, e preparo due funzioni per impostarli come uscite e gestire i livelli logici come necessario.

// questa funzione setta tutti i pin I/O relativi ai bit D0-D7 come uscite
void DATA_DIR_OUT() {
	LCD_D0_SetOutput();
	LCD_D1_SetOutput();
	LCD_D2_SetOutput();
	LCD_D3_SetOutput();
	LCD_D4_SetOutput();
	LCD_D5_SetOutput();
	LCD_D6_SetOutput();
	LCD_D7_SetOutput();
}

// questa funzione suddivide il byte da trasmettere in singoli bit
// e setta a livello alto o basso i pin corrispondenti DB0-DB7
void WR_DATA(char lcddata) {
	
	LCD_D0_PutVal((lcddata) >> 1);
	LCD_D1_PutVal((lcddata >> 1) & 1);
	LCD_D2_PutVal((lcddata >> 2) & 1);
	LCD_D3_PutVal((lcddata >> 3) & 1);
	LCD_D4_PutVal((lcddata >> 4) & 1);
	LCD_D5_PutVal((lcddata >> 5) & 1);
	LCD_D6_PutVal((lcddata >> 6) & 1);
	LCD_D7_PutVal((lcddata >> 7) & 1);
}

// manda un semplice impulso sul pin EN
void send_enable() {
	LCD_EN_SetVal();
	Delay100us(1); // delay di 1 microsecondo
	LCD_EN_ClrVal();
}

// seleziona il controller
//    0               1
// CS1 = 1         CS1 = 0
// CS2 = 0         CS2 = 0
void ChipSelect(bool chip)
{   
      if (chip == 0)
      {
            LCD_CS2_ClrVal();
            LCD_CS1_SetVal();
      }
      if (chip == 1)
      {
            LCD_CS1_ClrVal();
            LCD_CS2_SetVal();
      }
}

glcd output bits
Il risultato è di trasmettere il byte bit per bit distribuendolo sui pin DB7-DB0. Questa viene chiamata normalmente trasmissione parallela. Oltre a trasmettere il bit però è anche necessario indicare al controller quali sono le mie intenzioni. Sfogliando il datasheet dell’GLCD ho trovato tutte le informazioni necessarie al driving dei controller.
control bits e data bits
Ho tolto da questo schema tutte le altre funzioni per concentrarci sulla funzione più importante, quella per attivare il pannello “Display on/off” e sulla funzione per scrivere un byte “Write data”. Inoltre ho suddiviso per colore i bit di controllo dai bit dati, rispettando i colori dello schema elettrico. Manca il pin di Enable, “EN” perché quest’ultimo è molto semplice: prima si preparano i bit di controllo ed il bit dati, poi si da un piccolo impulso con il bit EN per dire all’LCD “ok sono pronto, ascoltami”.

Ricapitolando, per attivare il display devo settare RW a 0, DI a 0, ed inviare il BYTE “3F” seguito da un impulso di ENABLE:

void InitDisplay(void)
{
	DATA_DIR_OUT();
	LCD_DI_ClrVal();
	LCD_RW_ClrVal();
	WR_DATA(0x3F);
        ChipSelect(0);
}

Se ora volessimo accendere un pixel sullo schermo, sarebbe sufficiente mandare un byte di dati. Ma un attimo… un byte? E se io volessi accendere un solo bit? Giusto, ma di questo ne parleremo dopo. La domanda ora è: dove comparirebbe questo pixel? Non abbiamo creato nessuna funzione per il posizionamento del “cursore” di scrittura. Torniamo quindi al datasheet ed aggiungiamo qualche dettaglio alla tabella delle funzioni:

ks0108 goto xy istructions

Quindi abbiamo un comando chiamato set page che corrisponde all’asse X, ed un comando set address che corrisponde all’asse Y. Entrambi sembrano lavorare con RW e DI a zero, però mentre set address va da 0 a 63, set page va solo da 0 a 7 (zero compreso, quindi solo 8 posizioni): questo perché sull’asse X scriviamo 8 bit alla volta, ovvero il nostro byte! Ovvio che quindi non serviranno 64 posizioni, bensì 64/8, ovvero 8.

Allora preparariamo le nostre funzioni di posizionamento e di scrittura di un byte:

// selezione della pagina (asse X diviso in 8 byte)
void PageSelect(unsigned char page)
{
        // setto RW e DI come da tabella
	LCD_RW_ClrVal();
	LCD_DI_ClrVal();
        // unisco (B8) ovvero 10111 come da tabella
        // con i 3 bit del numero di pagina e li invio all'LCD
	WR_DATA(0xB8 | page);
	send_enable();
}

// selezione della riga (asse y diviso in 64 punti)
void RowSelect(unsigned char row)
{
        // setto RW e DI come da tabella
	LCD_RW_ClrVal();
	LCD_DI_ClrVal();
	WR_DATA(0x40 | row);
	send_enable();
}

// questa funzione invia un byte al controller attualmente attivo
void WriteByte(char chardata)
{
DATA_DIR_OUT(); // setto pin DB0-DB7 come uscite
LCD_RW_ClrVal(); // RW a livello logico 0
LCD_DI_SetVal(); // DI a livello logico 1
Delay100us(1); // attendo 1uS per la ricezione del comando
WR_DATA(chardata); // setto bit per bit le uscite
send_enable();
}

Fatto questo non ci resta che provare a mandare un primo byte allo schermo, nella posizione 0,0px, modificando il main in questo modo:

InitDisplay(void);  // inizializzo display
ChipSelect(0);      // selezioni chip 0
PageSelect(0);      // x = 0
RowSelect(0);       // y = 0
WriteByte(0xFF);    // accendo 8 pixel di fila
while(1);           // ciclo infinito di attesa

Se avete fatto tutto correttamente, vi apparirà una linea di 8px. Cambiando 0xFF con ad esempio 0x01 si accenderà un solo pixel, mentre 0xAA alterna pixel acceso con pixel spento. Potete anche provare a cambiare i valori di PageSelect e RowSelect, per vedere che effetto fa sul posizionamento dei pixel.

glcd byte sent

Da questo iniziale risultato è possibile partire per sviluppare tutte le funzioni grafiche come la scrittura di caratteri oppure la visualizzazione di immagini bitmap, ma di questo ne parlerò in un secondo articolo!

4 commenti

  1. I’ll right away clutch your rss as I can’t to find your email subscription hyperlink or newsletter service. Do you’ve any? Please permit me recognize so that I could subscribe. Thanks.|

  2. I’ve been browsing online more than 3 hours today, yet I never found any interesting article like yours. It is pretty worth enough for me. In my view, if all website owners and bloggers made good content as you did, the net will be a lot more useful than ever before.|

    1. Thank you so much! I’ve learned a lot with web articles, now i’m trying to do something good for the developers community.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *