BoxIdee

Display TFT: tutorial & tricks


La passione per i display

Confesso di essere affascinato e stimolato dai display LCD/TFT.
Mi piace studiarli e provare a farli funzionare, anche se spesso non so poi che farmene.
Ne ho acquistati parecchi, quasi tutti dalla Cina, famosa per non rilasciare documentazione oppure fornire misteriosi documenti in quasi-Inglo-Cinese, praticamente inutili.
I miei possedimenti vanno dai semplici LCD 1x16 su fino a TFT con diverse dimensioni e risoluzioni, da 1.44" del Nokia 5510 con 84x68 pixels ai 3.5" a 480x320 pixels dell'ultimo modulo su cui ho lavorato. I moduli pre-assemblati che si acquistano sul WEB hanno controllori tra i più vari e sempre diversi; solo per citarni alcuni sui quali ho messo le mani:

  • HD44780
  • PCD8544
  • WG24064A
  • T6963c
  • ADM12864H
  • ILI9320
  • ILI9327
  • ILI4341
  • ILI9481
  • SSD1289
  • ST7920
  • ST9327

Per fortuna in rete di trova praticamente di tutto per questi dispositivi, da librerie specifiche a quelle (quasi) universali come la UTFT di Henning Karlsen.
Gran parte di queste librerie sono per sistemi 'evoluti' che non conosco e uso (esempio: le MCU STM) oppure per gli omnipresenti Arduino e derivati, che non mi interessano, anzi...
Per carità, non ho nulla contro Arduino, anzi gli sono riconoscente per quante appassionati a fatto avvicinare al mondo dell'elettronica e della programmazione. Solo che non è l'approccio giusto per me.
Io sono assolutamente fedele ai PIC di Microchip, prima ai PIC16, ora ai PIC18, con qualche puntatina ai PIC24/32.

La mia scelta, come detto in altre parti, è di farmi 'tutto da me', almeno per quanto possibile, sennò che divertimento c'è; oltretutto se scrivo la mia libreria la capisco (forse!) e posso metterci le mani in qualsiasi momento.
Le mie librerie rappresentano anche un ottimo strumento di verifica dei miei progressi nella programmazione, una piccola cronistoria dello studio di nuovi linguaggi e metodologie di sviluppo. Per questo, ho librerie scritte in pseudo Basic, in Assembler per arrivare ora al C; ci ho messo anni per decidermi a studiarlo, ma ora incomincio a muovermi.

Quello che segue vuole essere un semplice pseudo-tutorial, con qualche modesta indicazione su piccoli trucchi (tricks) introdotti con l'esperienza per migliorare le prestazioni, in genere non eccezionali dato l'HW a disposizione.

I codici dei Driver che ho deciso di pubblicare li trovate alla pagina TFT Drivers, che contiene anche il resoconto di un divertente elenco di errori di programmazione, utile per capire quanto sia facile sbagliare.



Tricks

Le routine maggiormente utilizzate, e quindi critiche per le prestazioni, sono la funzione 'Dot (o Pixel) e la funzione Box, richiamata anche da Clear_Screen.
Dot è usata per il disegno di linee (non perpendicolari), cerchi e per il testo. Queste applicazioni sono di norma ridotte in dimensioni (pochi pixel) per cui la velocità di esecuzione di Dot è importante, ma non critica.
Box è invece usata per aree di grandi dimensioni (fino allo schermo intero) per cui impatta parecchio sulla velocità globale.
Qui si può intervenire in maniera poco 'pulita' ma molto efficiente, accettando un modesto compromesso. Di norma, per molti controller, la procedura di riempimento di aree può essere schematizzata in questo pseudo-codice:

  1. predispone la finestra di disegno
  2. metti il colore voluto (16 bit) sulle porte di uscita
  3. esegui i cicli di invio dati per quanti sono i bit nell'area interessata

Il passo critico è ovviamente il 3; il codice generato dal compilatore per eseguire il ciclo di scrittura richiede un tempo superiore (spesso di parecchio) al quello di effettiva scrittura del dato.
Il primo intervento è di pre-calcolare il numero di pixel da disegnare ed eseguire un solo ciclo di ripetizioni, invece dei classici <Loop Y - Loop X>.
Ancor meglio, il loop deve avvenire dal valore massimo (+1) a 0, con decremento della variabile di controllo; questo, rispetto all'incremento da 0 al massimo consente un guadagno di velocità anche del 20% in funzione del tipo di contatore.
Per il ciclo si può utilizzare un FOR-NEXT oppure un WHILE; quest'ultimo è più veloce.
In sostanza, si ottiene questo codice (pseudo codice senza abilitazioni di scrittura) :

void Box(short X0, short Y0, short X1, short Y1, short colore)
{
unsigned long maxCnt;
	maxCnt = (long) ((Y1-Y0+1) * (X1-X0+1) + 1);	//numero di pixel da disegnare
	Set_Window_Area(X0,Y0,X1,Y1);	//predispone l'area di stampa
	Write_Color(colore);	//invia sulle porte d'uscita il valore del colore

	while(maxCnt--)
  {
  	ToggleWrite()	//es. pin_write=0; ritardo; pin_write=1;
  }
}

E qui viene il bello!
Con display medio-grandi (superiori a 280 pixel in una direzione), il numero totale di pixel è superiore a 64K. Questo comporta l'uso di una variabile maxCnt di tipo Long a 32bit; l'incremento/decremento di questo tipo è impegnativo per il compilatore.
La soluzione è semplice: ridurre la lunghezza del ciclo sotto il limite dei 64K per poter utilizzare una variabile Short a 16bit.
In base alle dimensioni del display, questo comporta dividere maxCnt per 2 o 4 (o più) e contemporaneamente ripetere l'istruzione di scrittura per 2 o 4 (o piò) volte. La contropartita è che non si potranno disgnare rettangoli con lato minimo inferiore al divisore utilizzato, ma non credo che questo sia un problema pratico (posso usare DrawPixel in questi casi).
L'esempio che segue prevede la divisione per 4 (x2 in ogni dimensione):

void Box(short X0, short Y0, short X1, short Y1, short colore)
{
unsigned short maxCnt;
	maxCnt = ((Y1-Y0+1)>>1) * ((X1-X0+1)>>1) + 1;	//numero di pixel da disegnare
	Set_Window_Area(X0,Y0,X1,Y1);	//predispone l'area di stampa
	Write_Color(colore);	//invia sulle porte d'uscita il valore del colore

	while(maxCnt--)
  {
  	ToggleWrite()	//es. pin_write=0; ritardo; pin_write=1;
  	ToggleWrite()	//es. pin_write=0; ritardo; pin_write=1;
  	ToggleWrite()	//es. pin_write=0; ritardo; pin_write=1;
  	ToggleWrite()	//es. pin_write=0; ritardo; pin_write=1;
  }
}

Con questo banale trucco ottendo la riduzione del tempo impiegato fino al 13% del tempo originale.
In sintesi, dalle mie sperimentazioni, per la pulizia schermo di un display da 320x240 (76,800 pixel), con fattore di divisione per 16, sono passato da 101 msec a 13 msec; un bel guadagno di velocità! Il refresh dello schermo è pressochè inavvertibile.



P.S.: Questione di feeling

I codici pubblicati sono scritti in C (XC8 in ambiente MPLABX) in versione Free, quindi senza le ulteriori ottimizzazioni dell'Omniscent Code Generator (OCG) della versione a pagamento.
La domanda che sorge spontanea è: quanto devo essere fiducioso in XC8 Free che, oltre a produrre un codice 'enorme' rispetto a quanto sarebbe lecito attendersi, appare così poco efficiente nell'ottimizzare il codice?

Click su qualsiasi immagine per ingrandire


Display LCD 20x4 con HD44780


Display Nokia 5110


Display 2.2'' con ILI9341


TFT 3.2'' 320x240 con SSD1289


TFT 3.5" 480x320 con ILI9481

ì