Libreria per la gestione di un display grafico

L’interfaccia video grafica è ormai molto  diffusa su tutte le apparecchiature elettroniche. Il display grafico migliora l’estetica del prodotto  e ne semplifica l’utilizzo, le informazioni  infatti  vengono presentate in modo  completo  e intuitivo. Sviluppare il firmware per la gestione del display grafico potrebbe però occupare una parte consistente del tempo di sviluppo dell’intero progetto. Si devono realizzare le procedure per gestire il testo, per visualizzare bitmap, per creare animazioni…In realtà il mercato offre delle valide alternative come i moduli grafici dell’austriaca Demmel.   Moduli   “intelligenti”  che  includono tutte le funzioni per la configurazione del controller grafico e il disegno e si interfacciano all’hardware con una normale porta RS232 o IIC. Per chi invece volesse realizzare una propria libreria questo articolo descrive la struttura e le funzioni  principali  per la gestione del modulo grafico.

Introduzione

Tutte le funzioni della libreria sono scritte in linguaggio C per facilitare la portabilità del firmware su diversi microcontrollori.  Inoltre, per non vincolare la libreria ad una configurazione hardware predefinita, le funzioni grafiche, non agiscono direttamente sulla RAM del display, ma modificano  un buffer RAM interno  alla libreria stessa denominato ram immagine.  Con questa soluzione la libreria è indipendente dal display grafico utilizzato. La funzione di refresh video del driver di basso livello che gestisce l’interfaccia tra il display e il micro, trasferisce periodicamente la RAM immagine nella RAM del controller  grafico. La scelta di utilizzare la RAM immagine facilita la scrittura delle funzioni grafiche e semplifica la sincronizzazione dei vari compiti che è chiamato a svolgere il microcontrollore.  Vincola però una larga parte della ram del micro per la ram immagine e obbliga il microcontrollore  ad eseguire periodicamente l’operazione di refresh. Nel seguito dell’articolo verrà descritta la struttura della libreria e delle primitive grafiche e saranno discussi anche i limiti evidenziati.

LA LIBRERIA GRAFICA

La libreria grafica può essere suddivisa in tre moduli:

  1. Il   driver   d’interfaccia   tra   micro   e   display. Necessariamente questo driver è personalizzato e dipende dall’hardware utilizzato.
  2. Le primitive grafiche di base: gestione dei pixel, tracciamento di linee e figure geometriche, gestione dei bitmap e del testo.
  3. Le funzioni  di  alto  livello  quali  realizzazione di scroll bar, grafici, tabelle, menu.

Il modulo  d’interfaccia hardware

Si è già osservato in precedenza che questo modulo dipende dal controllore grafico del display e dal microcontrollore  utilizzati e da come i due componenti sono interfacciati. Questo modulo va riscritto di volta in volta e le funzioni che deve contenere sono: La funzione di inizializzazione del controller  grafico da  eseguire alla prima  accensione del  display. Le informazioni necessarie per realizzare questa procedura si trovano nella documentazione tecnica del controller grafico utilizzato. La funzione di refresh video che copia la RAM immagine interna alla libreria nella RAM del display. Funzioni accessorie per i comandi di configurazione del controller grafico come ad esempio la regolazione del contrasto. Per la realizzazione della libreria grafica e degli esempi presentati in questo articolo è stato utilizzato un display grafico monocromatico 128x64pixel con un controllore COG (chip on glass) SED1565D0B interfacciato sul bus del micro uPSD3234A della ST (nulla vieta di utilizzare un hardware più performante). La funzione di refresh del display aggiorna un blocco di 32byte ad ogni chiamata,  e completa il refresh del display dopo 32 chiamate. La funzione è stata inserita nel ciclo principale dell’applicazione e può essere interrotta  da qualunque interrupt.  In questo caso il processo di refresh ha bassa priorità,  per garantire tempi certi di aggiornamento del display la procedura di refresh può essere associata a un interrupt timer (Figura 1 e 2).

Figura 1. La mappa dei pixel nella ram interna

 

Figura 2. Composizione delle pagine grafiche con funzioni logiche OR-AND-XOR

 

La memoria  immagine

Per non fare riferimento a un particolare display nella descrizione delle procedure si utilizzeranno i termini XMAX e YMAX per indicare le dimensioni in pixel dell’area visibile rispettivamente lungo l’asse x e lungo l’asse y del display come indicato in figura 3.

Figura 3. Coordinate fisiche e coordinate logiche del display espresse in pixel

La memoria immagine rappresenta la memoria ram di un display grafico virtuale. Ogni bit della memoria immagine corrisponde ad un pixel del display, i pixel accesi corrispondono a bit di valore 1 i pixel spenti corrispondono a bit  di valore 0. Data la corrispondenza 1:1 pixel bit, nel seguito dell’articolo i due termini saranno utilizzati come sinonimi. Il pixel di coordinate x,y = 0,0 è il pixel dell’angolo in alto a sinistra del display e corrisponde al bit meno significativo del primo byte della memoria immagine. Ogni byte memorizza lo stato di otto pixel considerati in verticale come indicato in figura 1. Perciò il primo  byte memorizza lo stato dei pixel di coordinate x,y che vanno da 0,0 a 0,7, il secondo byte i pixel di coordinate 1,0..1,7 e così via. In altre parole, i primi XMAX byte del buffer  corrispondono  ai pixel delle prime otto  righe  del display. La corrispondenza bit-pixel non è vincolante, la scelta è stata fatta per semplificare la procedura di  refresh del display. Con questa configurazione, infatti, la procedura deve solamente copiare i byte della RAM immagine  nella RAM del display. La dimensione totale della memoria immagine espressa in byte si ottiene con la seguente formula:

XMAX*(YMAX >> 3)

L’operazione YMAX>>3 equivale a YMAX/8. Per il display utilizzato con risoluzione  128x64 pixel la dimensione del buffer è di 1024bytes.

Per rendere più flessibile il driver si possono aggiungere più pagine grafiche alla memoria immagine. Ad ogni pixel del display reale corrisponderà un bit in ogni pagina grafica del display virtuale. Le pagine si possono visualizzare singolarmente oppure  combinate secondo certi criteri  che saranno descritti  in seguito. La definizione della memoria immagine LCDBuffer per la gestione di quattro pagine grafiche diventa:

#define XMAX 128
#define YMAX 64

#define NPAGES 4

#define PAGESIZE  (XMAX * (YMAX >> 3)) unsigned char LCDBuffer[NPAGES * PAGE- SIZE] ;

dove le definizioni di XMAX e YMAX dipendono dal display scelto.

LE PRIMITIVE DI SCRITTURA GRAFICA

L’indirizzo  del pixel

Ogni pixel del display è indirizzato dalle due coordinate x,y. Poiché pixel e bit sono in corrispondenza 1:1, le coordinate x,y definiscono anche l’indirizzo di un bit all’interno della memoria immagine. In realtà se si utilizzano più pagine grafiche è necessario specificare anche il numero della pagina grafica a cui appartiene il bit associato al pixel. (Questa terza coordinata può essere interpretata come coordinata z). La procedura GLCDgotoxy converte l’indirizzo del pixel espresso in coordinate x,y,z nell’indirizzo del byte della RAM immagine che contiene il bit associato al pixel.

// puntatore al byte di coord x,y,z unsigned char * curptr ;

// pixel di coord. x,y=0,0 della 1^pag curptr = LCDbuffer;
curptr = curptr + (pagenbr * PAGESIZE) ;

A questo punto  curptr è l’indirizzo del primo  byte della pagina grafica selezionata. L’indirizzo del byte che contiene il pixel di coordinate x,y si ottiene sommando l’offset all’interno della pagina:

curptr = curptr + (XMAX *(y >> 3)) + x ;

Per ottenere la posizione del bit all’interno del byte selezionato si deve applicare la formula

mask = 1 << (y & 0x07).

Ogni bit di un byte, infatti, corrisponde ad uno spostamento verticale di un’intera riga di pixel come indicato in figura 1. Il listato 1 riporta la funzione completa GLCDgotoxy e include i controlli necessari per evitare un posizionamento errato fuori dal buffer LCDBuffer. La procedura restituisce un valore zero se il pixel è esterno alla pagina grafica.

La procedura di refresh

Questa procedura viene richiamata dal driver di basso livello per aggiornare l’immagine  visualizzata sul display. Poiché la memoria immagine può utilizzare più pagine grafiche è necessario stabilire il criterio con cui queste pagine appaiono sul display. La libreria prevede quattro diverse combinazione delle pagine grafiche secondo le operazioni OR-AND-XOR e POPUP  La modalità  prescelta per una pagina si applica a tutti  i bit della pagina, la procedura di refresh compone i diversi byte secondo le funzioni previste e trasferisce al display il risultato. Per associare una data funzione logica ad una pagina si deve porre a 1 il bit corrispondente alla pagina in una delle variabili ORpages, ANDpagesXORpages, POPpages. Modificando la dimensione della variabili si possono gestire da 8 a 32 pagine grafiche. Nella figura 2 ci sono alcuni esempi che spiegano il meccanismo di composizione delle pagine. Nel listato 2 è riportata la struttura della procedura GLCDdisplaybyte, richiamata dal driver di basso livello ogni volta che deve aggiornare il contenuto di un byte della memoria RAM del display. Il parametro in ingresso è l’offset del byte all’interno della RAM del display. La procedura combina i byte corrispondenti di tutte le pagine grafiche secondo le funzioni logiche prescelte e restituisce il byte da visualizzare.

La modalità POPUP

La modalità POPUP sovrappone, totalmente o parzialmente, il contenuto di una pagina al contenuto attuale del display. Si può immaginare che le pagine POPUP siano sovrapposte al display secondo l’asse z perpendicolare al piano del display. Questa funzione può essere utilizzata ad esempio per realizzare dei menu a scomparsa. Quando la pagina sovrapposta in modalità popup viene rimossa, il contenuto originale del display dev’essere ripristinato.  Per questo l’area interessata all’operazione di popup è memorizzata nel buffer POPmask. Il buffer POPmask ha le dimensioni di una pagina grafica ed è inizialmente configurato con tutti i bit a 1. Una pagina di popup richiama la funzione GLCDPOPUP specificando il numero della   pagina grafica a cui attribuire la modalità popup, l’area rettangolare del popup e lo style del popup.  (Style = 1 apertura del popup  o style = 0 chiusura del popup). L’indice della pagina che  deve  apparire  in  popup  viene  memorizzato nella variabile POPpages. Tutti i bit del buffer POPmask interni all’area di popup sono azzerati. La procedura di refresh video dopo aver composto le pagine grafiche con le operazioni logiche applica al risultato la maschera di popup  eseguendo l’operazione di AND con i byte del buffer POPmask. Questa operazione azzera tutti  i bit che cadono all’interno dell’area di popup.  I bit azzerati sono sostituiti dal contenuto della pagina che appare in popup. Quando  il  popup  sarà rimosso, i  bit  dell’area di popup nel buffer POPmask saranno riportati a 1 e le successive operazioni di AND risulteranno neutre lasciando inalterata l’immagine iniziale. Le pagine di popup possono anche essere sovrapposte  in questo caso si attiva una priorità di visualizzazione.  La pagina0 ha priorità più bassa e la pagina7 ha la priorità più alta. La pagina a priorità maggiore ricoprirà quella a priorità inferiore.

Le coordinate utente

Finora si è utilizzato il sistema di coordinate x,y più semplice, che crea una corrispondenza 1:1 tra le coordinate x,y e i pixel del display. Questa corrispondenza può essere modificata associando all’area visibile delle coordinate logiche che permettono di visualizzare facilmente  grafici  oppure  di  realizzare una funzione di zoom dell’immagine. Con le funzioni GLCDsetviewport e GLCDmapviewport descritte nel listato 3 si possono associare delle coordinate generiche all’area visibile. La prima procedura assegna all’area del display le coordinate logiche scelte dall’utente.  Le coppie x1view,y1view e x2view,y2view definiscono le coordinate x e y dei due angoli in alto a sinistra e in basso a destra del display come indicato in figura 3. La procedura GLCDMAPviewport trasforma le coordinate logiche nelle coordinate fisiche. La trasformazione è una semplice trasformazione lineare delle coordinate. Ogni procedura richiama inizialmente la funzione GLCDmapviewport per convertire le coordinate logiche in coordinate fisiche.

IL DISEGNO

Disegno di linee

L’algoritmo utilizzato per il disegno di una linea è l’algoritmo  di Bresenham. Il riferimento [1] è solo uno dei molti esempi che si possono ritrovare su internet e che descrivono in dettaglio l’algoritmo  di Bresenham. Il listato 4/A è un esempio semplificato di questo algoritmo. Per tracciare la linea sul display si deve stabilire quali pixel appartengono alla linea, o meglio quali pixel si devono accendere per approssimare al meglio il disegno della retta. L’algoritmo di Bresenham fornisce un criterio per stabilire dato un punto (un pixel) qual è il punto  successivo (il  pixel  successivo) che  meglio approssima la linea da tracciare. L’algoritmo ha due vantaggi:

  • Utilizza aritmetica intera
  • Richiede solo operazioni di somma e sottrazione.

Per questo si applica facilmente anche a microcontrollori con risorse limitate. L’algoritmo si basa su una formula ricorsiva che si può  ricavare partendo  dall’equazione della retta. Dato un punto  si valuta quale tra i pixel vicini si discosta meno dalla retta ideale e si procede in questo modo ricorsivamente. Per una trattazione matematica approfondita si rimanda ai riferimenti citati. La formula che si ottiene è la seguente:

p0 = 2*Dy-Dx

Se pk è minore di zero, il prossimo punto della circonferenza sarà (xk+1, yk) e il nuovo valore di p sarà pk+1 = pk+2* Dy. Se pk  è maggiore o uguale a zero, il prossimo punto della circonferenza  sarà (xk+1, yk+1) e il nuovo valore di p sarà pk+1  = pk+2* Dy-2 * Dx.

La variabile linestyle definisce il tratto  con cui sarà tracciata la linea. I pattern previsti per il tratto sono memorizzati nella tabella lnstyle. Il pattern scelto sarà ripetuto ogni otto pixel. Il valore 0xFF disegna una linea con tratto continuo. Il valore 0x33 disegna una linea tratteggiata con ripetizione di tratti di lunghezza due pixel. I valori 0x27 e 0x6F disegnano una linea tratteggiata con tratto punto linea. Il valore 0x00  si può  utilizzare per cancellare una linea. La variabile linebitmask scorre il pattern selezionato ad ogni nuovo punto  della linea e determina se il pixel è 1(acceso) o 0 (spento).

Disegno di rettangoli

La funzione GLCDdrawrect  è stata realizzata nel modo più semplice con quattro chiamate successive alla procedura di disegno GLCDdrawline. Ovviamente la soluzione più  semplice non  è la più ottimizzata in quanto si potrebbero  sfruttare le simmetrie della figura per ridurre i tempi di esecuzione.

Disegno di circonferenze

Anche il disegno di una circonferenza può essere ottimizzato sfruttando le simmetrie della figura. L’algoritmo adottato suddivide il cerchio in otto quarti, per ogni coppia di coordinate traccia otto punti contemporaneamente. Analogamente a quanto fatto per  la retta si costruisce una funzione per valutare se dato un punto  (un pixel) si trova sulla circonferenza o al suo interno o è esterno. I punti  che appartengono  alla circonferenza soddisfano l’equazione:

x2  + y2  = r2

Si può ricavare la formula ricorsiva per valutare se un punto appartiene o meno alla circonferenza p0  = 1-r se pk  è minore di zero, il prossimo punto della circonferenza sarà (xk+1, yk) e il nuovo valore di p sarà pk+1 = pk+2*xk+1+1; se pk  è maggiore o uguale a zero, il prossimo punto della circonferenza  sarà (xk+1, yk-1) e il nuovo valore di p sarà pk+1  = pk+2*(xk+1-yk+1)+1.

Disegno di bitmap

Per visualizzare un immagine bitmap si deve conoscere il suo formato, cioè come è stata memorizzata la sequenza di pixel che compongono  il bitmap. La funzione implementata in questa libreria assume che il bitmap sia monocromatico  e l’immagine sia stata memorizzata come sequenza continua per colonne. Nel  riquadro  di  approfondimento   si spiega come creare un bitmap  con il programma Paint   di Windows e come convertire il file standard con estensione BMP  in un formato  compatibile per la funzione di libreria. La prima versione della funzione GLCDdrawbitmap è riportata nel listato 5/A. La procedura è formata da due loop. Il più esterno è il loop di scrittura sulle colonne del bitmap  mentre il più interno è il ciclo di copia su ogni singola colonna. Con la variabile mask si effettua il test dei singoli bit dell’immagine  bitmap che viene sempre letto per colonne. La seconda versione del listato 5/B invece aggiunge il fattore di scala al bitmap.  L’immagine può essere ingrandita di un fattore prestabilito replicando più volte i singoli bit del bitmap. Per fare questa operazione nel listato 5/B sono stati aggiunti due cicli for, uno per ogni dimensione x,y. La figura 4 è un esempio di scrittura di bitmap e di utilizzo dei fattori di scala.

Figura 4. Visualizzazione di bitmap con diversi fattori di scala espresse in pixel

Un’altra opzione prevista per il disegno di un bitmap è la rotazione dell’immagine.  La lettura del bitmap avverrà sempre allo stesso modo, cambierà invece la procedura di scrittura. Per semplicità è stata implementata solo la rotazione di multipli di 90°. La rotazione di  90° si ottiene  mantenendo  la lettura  del pixel per colonne ma invertendo i loop di scrittura dei pixel. Per rotazioni di -90° oltre a invertire i loop di scrittura si inizia la scrittura all’estremo opposto dell’asse x e si procede per valori di x decrescenti. Per rotazioni di 180° si scrive il bitmap per valori di y decrescenti senza invertire i loop di scrittura.

IL TESTO

Il modo  più  semplice di  rappresentare il  testo sul display grafico è quello di utilizzare i font bitmap (figura 5).

Figura 5. Composizione di un font bitmap

Un font bitmap è caratterizzato dalle dimensioni massime della matrice di pixel che contiene i singoli caratteri. La tabella del font  memorizza in sequenza i bitmap di tutti i caratteri. La procedura di scrittura di un carattere GLCDputch utilizza il codice ascii del  carattere  come  indice  per  l’accesso alla tabella del font e copia nella ram immagine il bitmap corrispondente al carattere con la funzione GLCDdrawbitmap. In  questo modo  il  testo eredita tutte le funzioni associate al bitmap. Sarà così possibile ruotarlo di multipli  di 90° oppure modificare le dimensioni del font in una o entrambe le direzioni. Un’altra utile funzione per la manipolazione del testo che si può realizzare facilmente è la disposizione del testo multilinea.  Le diverse righe di  testo possono essere allineate  a  sinistra,  a  destra  o  centrate. Normalmente  le linee sono allineate a sinistra, per modificare l’allineamento la procedura calcola la dimensione in pixel della linea più lunga. Per un font bitmap questo calcolo è molto semplice perché è la dimensione in pixel del singolo carattere moltiplicata per il numero di caratteri. Per tutte le altre righe di testo si calcola l’offset pari alla differenza tra la lunghezza della riga più lunga e la riga corrente. Per l’allineamento a destra la scrittura della riga corrente viene spostata di un numero di pixel pari all’offset calcolato. Per centrare il testo la scrittura della riga corrente viene spostata di un numero di pixel pari a metà dell’offset calcolato. Il listato 6 riporta la procedura per la visualizzazione di testo su una o più linee con la funzione di allineamento. La funzione nella forma completa dovrà anche tenere conto dell’orientamento del testo e dei fattori  di scala utilizzati. In figura 6 un esempio di visualizzazione sul display.

Figura 6. Fonts bitmap visualizzati su un display

LE FUNZIONI GRAFICHE DI SECONDO LIVELLO

Le funzioni  descritte in precedenza rappresentano il nucleo base di una libreria grafica. Utilizzando queste primitive è possibile realizzare funzioni più complesse per disegno di grafici, istogrammi control bar. L’idea è quella di realizzare delle macro funzioni in grado di svolgere la maggior parte delle operazioni di disegno semplificando il compito del programmatore. Si può ad esempio definire la funzione GLCDgraph per visualiz- zare grafici che si occuperà di tracciare il grafico, disegnare gli assi cartesiani  le etichette, ecc... Un esempio molto semplice è la funzione GLCDhistograph (listato 8) che realizza un istogramma partendo da una serie di dati normalizzati. Il risultato è visibile in figura 7.

Figura 7. Esempio di visualizzazione di un istogramma

Per rendere la libreria più completa si possono realizzare dei menu a scomparsa sfruttando  la modalità popup. Realizzare animazioni mediante la combinazione logica di pagine grafiche e moltissime altre funzioni.

 

REALIZZARE UN BITMAP IN FORMATO COMPATIBILE PER LA FUNZIONE GLCDDRAWBITMAP

Per realizzare un semplice bitmap è possibile utilizzare il programma Paint presente in tutte le versioni di windows. Inizialmente si devono definire gli attributi del bitmap specificando le dimensioni (preferibilmente in pixel) e selezionare la modalità monocromatica del bitmap. Queste opzioni si possono configurare nella finestra che appare premendo i tasti CTRL-E. Terminato il disegno del bitmap, per semplificare la procedura di conversione del file BMP generato nel file leggibile dalla procedura dalla libreria si devono compiere due operazioni:

  • Invertire i colori del bitmap
  • ruotare il disegno di 90° in senso antiorario.

Il programma Paint salva il disegno nel file con formato standard ed estensione BMP. Il file contiene un’intestazione con tutte le informazioni sulle dimensioni del bitmap e della tabella di colori utilizzata. Trattandosi di un’immagine monocromatica le informazioni sulla palette colori non sono necessarie e sono ignorate. L’immagine bitmap viene memoriz- zata per righe ma capovolta cioè a partire dalla riga inferiore. Poiché prima del salvataggio l’immagine è stata ruotata di -90° il bitmap è memorizzato per colonne partendo dalla colonna più a sinistra. Il formato del file BMP prevede che ogni riga memorizzata sia allineata al multiplo  di 32bit immediatamente superiore aggiungendo eventuali bit a zero. Così se il bitmap ha dimensioni di riga 12pixel il file BMP contiene righe di 32bit. Durante la procedura di conversione dei dati questi bit aggiuntivi devono essere eliminati. Il programma di conversione riportato nel listato7 converte il bitmap e inserisce come intestazione la stringa “BMP” seguita dalle dimensioni x,y dell’immagine.

#define NPAGES 4
#define XMAX 128
#define YMAX 64
#define PAGESIZE (XMAX*(YMAX >> 3))
unsigned char LCDBuffer[NPAGES*PAGESIZE] ;
unsigned xpos = 0;
unsigned ypos = 0;
unsigned char* curptr = LCDBuffer ;
unsigned char GLCDgotoxy (unsigned char pagenbr, unsigned x, unsigned y)
{ pagenbr %= NPAGES ;
  // check coordinate in buffer
  if ((x < XMAX) && (y < YMAX))
  { if ((x != xpos) || ((y & 0x0FFF8) != (ypos & 0xFFF8))) // byte ptr update check
    { xpos = x ;
      ypos = y ;
      curptr = LCDBuffer + (pagenbr * PAGESIZE)) ;
      curptr = curptr + (XMAX * (y >> 3)) + x ;
    }
    return (1) ;
 }
 return (0) ;
}
Listato 1
unsigned char ORpagse ;
unsigned char ANDpages ;
unsigned char XORpages ;
unsigned char POPpages ;
unsigned char POPmask[PAGESIZE] ;
unsigned char GLCDdisplaybyte(unsigned offset)
{ unsigned char i=0 ;
  unsigned mask = 1 ;
  unsigned char lcdbyte = 0 ;
  for (i=MINPAGE, mask = 1; i<=MAXPAGE; i++, mask <<= 1)
    if (ORpages & mask) lcdbyte |= LCDBuffer[offset+(i*PAGESIZE)] ;
  for (i=MINPAGE, mask = 1; i<=MAXPAGE; i++, mask <<= 1)
  { if (ANDpages & mask) lcdbyte &= LCDBuffer[offset+(i*PAGESIZE)] ;
    if (XORpages & mask) lcdbyte ^= LCDBuffer[offset+(i*PAGESIZE)] ;
  for (i=MINPAGE, mask = 1; i<=MAXPAGE; i++, mask <<= 1)
  { if (POPpages & mask)
    { lcdbyte &= POPmask[offset] ;
      lcdbyte |= LCDBuffer[offset+(i*PAGESIZE)] ;
    }
 }
 return (lcdbyte) ;
}

void GLCDPOPUP (unsigned char pagenbr, int x1, int y1, int x2, int y2, unsigned char style)
// style = 0 reset popup, style = 1 set popup
{ unsigned offs ;
  unsigned char mask ;
  int x, y, x1v = x1, y1v = y1, x2v = x2, y2v = y2;
  if (style) POPpages |= (0x01 << pagenbr) ;
      else POPpages &= ~(0x01 << pagenbr) ;
  GLCDmapviewport (&x1v, &y1v) ;
  GLCDmapviewport (&x2v, &y2v) ;
  for (y = y1v ; y<=y2v; y++)
  { offs = (XMAX * (y >> 3)) + x1v ;
    mask = 0x01 << (y & 0x07) ;
    for (x = x1v ; x<=x2v ; x++)
  { if (style) POPmask[offs++] &= ~mask ;
       else POPmask[offs++] |= mask ;
     }
  }
}
Listato 2
#define XMAX 128
#define YMAX 64
int x1view, y1view, x2view, y2view;
unsigned xfactor, yfactor ;
void GLCDsetviewport (int x1, int y1, int x2, int y2)
{ x1view = x1 ; y1view = y1 ; x2view = x2 ; y2view = y2 ;
xfactor = x2-x1 ; yfactor = y2-y1 ;
}
void GLCDmapviewport (int* x, int* y)
{ *x = (((XMAX-1) * (*x-x1view))+(xfactor >> 1))/xfactor ;
*y = (((YMAX-1) * (*y-y1view))+(yfactor >> 1))/yfactor ;
}
Listato 3
unsigned char code lnstyle[] = { 0x00, 0xFF, 0x6F, 0x27, 0x33 } ;
void GLCDdrawline (unsigned char pagenbr, int x1, int y1, int x2, int y2, unsigned char linestyle)
{ unsigned deltax, deltay, xEnd;
  int deltaxy, p, x1v = x1, y1v = y1, x2v = x2, y2v = y2;
  unsigned char linebitmask = 0x01 ;
  linestyle %= MAXLNSTYLE ;
  GLCDmapviewport (&x1v, &y1v) ;
  GLCDmapviewport (&x2v, &y2v) ;
  deltax = labs (x2v-x1v) ;
  deltay = labs (y2v-y1v) ;
  p = (deltay << 1) – deltax ;
  deltaxy = 2*(deltay - deltax) ;
  deltay <<= 1 ;
  if (x1v > x2v)
  { tmp = x2v ; x1v = x2v ; x2v = tmp ;
  tmp = y2v ; y1v = y2v ; y2v = tmp ;
  }
  xEnd = x2v ;
  GLCDpixel(pagenbr, x1v, y1v, lnstyle[linestyle] & linebitmask) ;
while (x++ < xEnd)
{ if (p < 0)
  { p += deltay ;
  }
  else
  { y++ ;
    p += deltaxy ;
  }
  linebitmask <<= 1 ; if (linebitmask == 0) linebitmask = 0x01 ;
  GLCDpixel(pagenbr, x1v, y1v, lnstyle[linestyle] & linebitmask) ;
 }
}
Listato 4/A
void GLCDdrawcirclepts(unsigned char pagenbr, int xc, int yc, int x, int y, unsigned char linestyle)
{ int x0, x1, y0, y1;
  x0 = xc+x ; x1 = xc-x ;
  y0 = yc+y ; y1 = yc-y ;
  GLCDmapviewport (&x0, &y1) ;
  GLCDmapviewport (&x1, &y0) ;
  GLCDpixel (pagenbr, x0, y0, linestyle) ;
  GLCDpixel (pagenbr, x1, y0, linestyle) ;
  GLCDpixel (pagenbr, x0, y1, linestyle) ;
  GLCDpixel (pagenbr, x1, y1, linestyle) ;

  x0 = xc+y ; x1 = xc-y ;
  y0 = yc+x ; y1 = yc-x ;
  GLCDmapviewport (&x0, &y1) ;
  GLCDmapviewport (&x1, &y0) ;
  GLCDpixel (pagenbr, x0, y0, linestyle) ;
  GLCDpixel (pagenbr, x1, y0, linestyle) ;
  GLCDpixel (pagenbr, x0, y1, linestyle) ;
  GLCDpixel (pagenbr, x1, y1, linestyle) ;
}
void GLCDdrawcircle(unsigned char pagenbr, int xc, int yc, int r, unsigned char linestyle)
{ int xv = 0, yv = r, xcv = xc, ycv = yc, p = 1 - r;
  unsigned char linebitmask = 0x01 ;
  linestyle %= MAXLNSTYLE ;
  GLCDdrawcirclepts (pagenbr, xcv, ycv, xv, yv, lnstyle[linestyle] & linebitmask) ;
  linebitmask <<= 1 ; if (linebitmask == 0) linebitmask = 0x01 ;
  while (xv < yv)
  { xv++ ;
    if (p<0) { p += 2*xv+1 ; }
     else { yv— ;
            p += 2*(xv-yv) + 1 ; }
    GLCDdrawcirclepts (pagenbr, xcv, ycv, xv, yv, lnstyle[linestyle] & linebitmask) ;
    linebitmask <<= 1 ; if (linebitmask == 0) linebitmask = 0x01 ;
  }
}
Listato 4/B
void GLCDdrawbitmap(unsigned char pagenbr, int xc, int yc, unsigned char bxw, unsigned char
byw, unsigned char* pBitmap, unsigned char scale)
{
  int i, j ;
  int maxx ;
  int maxy ;
  unsigned char* p = pBitmap ; // skip bitmap vidth
  unsigned char mask = 0x01;
  unsigned char* q = p ;
  unsigned char memask = mask ;
  int xcv = xc, ycv = yc, ix , iy;
  unsigned char scalex = scale & 0x0F;
  unsigned char scaley = scale >> 0x04 ;
  GLCDmapviewport (&xcv, &ycv) ;
  maxx = xcv+(scalex*bxw) ;
  maxy = ycv+(scaley*byw) ;
  ix = xcv ;
  while (ix < maxx)
  { memask = mask ;
    q = p ;
    for (j=0 ; j<scalex ; j++)
    { mask = memask ;
      p = q ;
      iy = ycv ;
      while (iy < maxy)
    {
      for (i=0 ; i<scaley ; i++) GLCDpixel (pagenbr, ix, iy++, (*p & mask))
      mask <<= 1 ; if (mask == 0) { p++ ; mask = 0x01 ; }
    }
    ix++ ;
  }
}
Listato 5/A
void GLCDdrawbitmap(unsigned char pagenbr, int xc, int yc, unsigned char bxw, unsigned char
byw, unsigned char* pBitmap, unsigned char scale)
{
  int i, j ;
  int maxx ;
  int maxy ;

  unsigned char* p = pBitmap ; // skip bitmap vidth
  unsigned char mask = 0x01;
  unsigned char* q = p ;
  unsigned char memask = mask

  int xcv = xc ;
  int ycv = yc ;
  int ix , iy ;

  unsigned char scalex = scale & 0x0F ;
  unsigned char scaley = scale >> 0x04 ;

  GLCDmapviewport (&xcv, &ycv) ;
   maxx = xcv+(scalex*bxw) ;
  maxy = ycv+(scaley*byw) ;
  ix = xcv ;
  while (ix < maxx)
  {
    memask = mask ;
    q = p ;
    for (j=0 ; j<scalex ; j++)
    {
      mask = memask ;
      p = q ;
      iy = ycv ;
      while (iy < maxy)
      {
        for (i=0 ; i<scaley ; i++) GLCDpixel (pagenbr, ix, iy++, (*p & mask)) ;
        mask <<= 1 ; if (mask == 0) { p++ ; mask = 0x01 ; }
      }
      ix++ ;
  }
}
Listato 5/B
#define XFONTPIXEL 5
#define YFONTPIXEL 7

#define FONTBITMAPSIZE (((XFONTPIXEL*YFONTPIXEL)+7) >> 3)

unsigned char pFonttbl[] =
{
  {//chars bitmaps},
   ………..
}

void GLCDputchar (unsigned char pagenbr, int x, int y, unsigned char ch)
{
  GLCDdrawbitmap (pagenbr, x, y, XFONTPIXEL, YFONTPIXEL, pFonttbl + (ch * FONTBITMAPSIZE)) ;

}
void GLCDputs (unsigned char pagenbr, int x, int y, unsigned char* pstr)
{
  unsigned char* p = pstr;
  unsigned char* q = p ;
  unsigned maxchars = strlen (p) ;

  int substrlen ;
  int ix = x ;
  int iy = y ;

  if (strchr (q, ‘\n’))
  {
      maxchars = 0 ;
      do
      {
         substrlen = 0 ; for (q=p ; (*q != ‘\n’) && (*q != 0); q++, substrlen++) ;
         if (substrlen > maxchars) maxchars = substrlen ;

         p = q+1 ;
      }
      while (*q != 0) ;

      maxchars *= XFONTPIXEL ;

      p = q = pstr ;
      do
      {
         q = strchr (q, ‘\n’) ;

         if (q) substrlen = q-p ; else substrlen = strlen (p) ;
         substrlen *= XFONTPIXEL ;

            ix = x ; // left justify
            if (LCDattrib & TEXTRIGHT ) ix += (maxchars - substrlen) ;
            if (LCDattrib & TEXTCENTER) ix += ((maxchars - substrlen) >> 1) ;

          while ((*p != ‘\n’) && (*p != 0))
          {
            GLCDputchar (pagenbr, ix, iy, *p++, scale) ;

          }
          q = p+1 ;
     }
     while (*p++ != 0) ; //p++ ptr to next substr (after \n) don’t care if *p=0
     }
     else
     {
       while (*p != 0 )
       {
         GLCDputchar (pagenbr, ix, iy, *p++) ;

      }
   }
}
Listato 6
typedef struct
{
  WORD ID ;
  DWORD size ; // byte
  WORD reserved0 ;
  WORD reserved1 ;
  DWORD dataoffs ;
} T_BMPHEADER ;
typedef struct
{
   DWORD bmpinfohdrsize ; // byte
   DWORD bmpwidth ; // pixel
   DWORD bmpheight ; // pixel
   WORD bmpplane ;
   WORD bitxpixel ;
   DWORD compresison ;
   DWORD Imagesize ;
   DWORD Xpixelxmeter ;
   DWORD YpixelxmeteR;
   DWORD Colorused ;
   DWORD Colorimportant ;
} T_BMPINFOHDR ;
typedef struct
{
  BYTE blue ;
  BYTE green ;
  BYTE red ;
  BYTE reserved ;
} T_RGBQUAD ;
#define SOURCEBMP “source.bmp”
#define TARGETBMP “target.lcd”
#define BMPXsize 128
#define BMPYsize 64
#define BMPROW (BMPYsize >> 3)
#define BMPCOL BMPXsize
#define BMPRECSIZE 32
void BMPconvert ()
{
  T_BMPHEADER BMPhdr ;
  T_BMPINFOHDR BMPinfohdr ;
  T_RGBQUAD BMPrgb ;
  unsigned char SourceBitmap[BMPROW*BMPCOL] ;
  unsigned char TargetBitmap[BMPROW*BMPCOL] ;
  unsigned char Smask, Tmask ;
  unsigned Sndx , Tndx ;
  unsigned row , col ;

  memset (SourceBitmap, 0x00, BMPROW*BMPCOL) ;
  memset (TargetBitmap, 0x00, BMPROW*BMPCOL) ;

  FILE* pF = fopen (SOURCEBMP, “r”) ;
  if (pF)
  {
    //////////////////////////////////////////////////////////////////////////////
    // read source bitmap

   fread (&BMPhdr , sizeof (T_BMPHEADER) , 1, pF) ;
   fread (&BMPinfohdr, sizeof (T_BMPINFOHDR), 1, pF) ;
   fread (&BMPrgb , sizeof (T_RGBQUAD) , 1, pF) ;

   // bitmap init
   fseek (pF, BMPhdr.dataoffs, SEEK_SET) ;

   fread (SourceBitmap, BMPinfohdr.Imagesize, 1, pF) ;

   fclose (pF) ;

//////////////////////////////////////////////////////////////////////////////
    // convert for GLCD
    Sndx = 0 ; Smask = 0x80 ;
    Tndx = 0 ; Tmask = 0x01 ;

    for (row = 0 ; row < BMPinfohdr.bmpheight; row++)
    {
      Smask = 0x80 ;

      for (col=0 ; col < BMPinfohdr.bmpwidth ; col++)
      {
        if (SourceBitmap[Sndx] & Smask) TargetBitmap[Tndx] |= Tmask ;

        Smask >>= 1 ; if (Smask == 0) { Smask = 0x80 ; Sndx++ ; }
        Tmask <<= 1 ; if (Tmask == 0) { Tmask = 0x01 ; Tndx++ ; }
       }

       Sndx += (BMPRECSIZE >> 3) ; Sndx &= 0xFC ; // align 32bit (bmp rec size)
     }
//////////////////////////////////////////////////////////////////////////////
    // write new bitmap

    pF = fopen (TARGETBMP, “w”) ;

   fwrite (“BMP” , 3*sizeof (char) , 1, pF) ;
   fwrite (&BMPinfohdr.bmpheight, sizeof (short), 1, pF) ;
   fwrite (&BMPinfohdr.bmpwidth, sizeof (short), 1, pF) ;
   fwrite (TargetBitmap, Tndx+1, 1, pF) ; // Tndx start from 0
   fclose (pF) ;
  }
}
Listato 7
typedef struct
{
  unsigned char page ; // pagina grafica
  unsigned char gflags; // attributi
  unsigned char nvalues ; // numero dei valori

  unsigned x1 ; // area del grafico
  unsigned y1 ;
  unsigned x2 ;
  unsigned y2 ;

  int min ; // valore minimo
  int max ; // valore massimo
  int values[MAXVALUES] ; // dati

} T_HGRAPH ;

#define FHGRAPHX 0x01
#define FHGRAPHBOX 0x02

void GLCDhistograph(T_HGRAPH xdata *pData)
{
  int i ;
  int Dlcd ;
  int Dval ;
  int tmp ;

T_HGRAPH xdata *p = (T_HGRAPH xdata *) pData ;

  unsigned ix1 ;
  unsigned iy1 ;
  unsigned ix2 ;
  unsigned iy2 ;
  unsigned colwidth ;
  unsigned char pattern = 0 ;

  Dval = p->max - p->min ;
  Dlcd = p->y2 - p->y1 ;

  colwidth = (p->x2 - p->x1) / p->nvalues ;

  tmp = p->y2 - ((Dlcd * (0 - p->min)) + (Dval >>1)) / Dval ;
  iy1 = tmp ;
  ix1 = p->x1 ; ix2 = ix1 + colwidth ;
  for (i=0 ; i<p->nvalues ; i++)
{
   tmp = p->y2 - ((Dlcd * (p->values[i] - p->min)) + (Dval >>1)) / Dval ;
   iy2 = tmp ;

  GLCDdrawrectangle (p->page, ix1, iy1, ix2, iy2, 1) ;
  GLCDfill (p->page, pattern, ix1, iy1, ix2, iy2) ;

  ix1 = ix2 ; ix2 += colwidth ;
  if (++pattern >= MAXPATTERNS) pattern = 0 ;
 }
 if (p->gflags & FHGRAPHBOX)
    GLCDdrawrectangle (p->page, p->x1, p->y1, p->x2, p->y2, 1) ;
}
Listato 8

 

Scarica subito una copia gratis

Una risposta

  1. Avatar photo Maurizio 15 Novembre 2016

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend