Questa sezione descrive il metodo per scrivere nel display 128 x 64 caratteri di larghezza massima 11 punti e altezza massima 16 punti, codificati in formato bmp. Per prima cosa vediamo come sono codificati caratteri che poi saranno letti dal programma e mostrati nel display. Prendiamo per esempio il carattere a maiuscolo.
Questa è la codifica e le cifre sono informato decimale:
0, 0, 192, 120, 14, 3, 14, 120, 192, 0, 0,
240, 30, 3, 1, 1, 1, 1, 1, 3, 30, 240
Ora passiamo dal formato decimale al formato binario
00000000, 00000000, 11000000, 01111000, 00001110, 00000011, 00001110, 01111000, 11000000, 00000000, 00000000
11110000, 0001 1110, 00000011, 00000001, 00000001, 00000001, 00000001, 00000001, 00000011, 00011110, 11110000
Mettiamo in colonna i primi 11 byte e notiamo che si forma la parte superiore del carattere A:
00000000
11000000
01111000
00001110
00000011
00001110
01111000
11000000
00000000
00000000
e poi i secondi 11, che formano la parte inferiore del carattere A:
11110000
00011110
00000011
00000001
00000001
00000001
00000001
00000001
00000011
00011110
11110000
Una volta trasposti e allineati mostrano il carattere corretto
00000100000
00001110000
00001010000
00011011000
00010001000
00010001000
00110001100
00100000100
00111111100
01100000110
01000000010
01000000010
11000000011
10000000001
10000000001
10000000001
La funzione che vedremo effettua tutte queste operazioni, posiziona il carattere nel punto desiderato dello schermo e può aggiungere il carattere ha gli oggetti dello schermo esistenti oppure sostituirli. Ricordiamo che la funzione è stata testata nell'ambiente NECTO Studio v6.1.0
Gli argomenti da passare alla funzione sono abbastanza intuitivi: la posizione orizzontale del carattere, la posizione verticale, il carattere da scrivere, uno 0 nel caso il carattere deve essere sovrapposto al contenuto già esistente dello schermo oppure un 1 se il carattere deve sostituire il contenuto presente nello schermo.
Ricordiamo che all'interno della funzione si troverà la variabile matrice che rappresenta il contenuto del display. Avere questa variabile nel microcontrollore rende più veloce verificare quali pixel sono accesi nel display ed evita di leggere il contenuto della memoria del display, che è un'operazione lenta
Istruzioni presenti nel main:
void glcd_putchar_11_16(unsigned char x, unsigned char y, unsigned char ch, bool sovrascrivi);
unsigned int matrice[16][32];
Funzione:
void glcd_putchar_11_16(unsigned char x, unsigned char y, unsigned char ch, bool sovrascrivi)
{
const unsigned char FONTWIDTH = 11; //larghezza del carattere
unsigned char t1, t2; // 1 byte
unsigned int XX, YY, ch0 = 0, maschera, new_val; // 2 byte
unsigned char font[11];
ch0=(ch-32)*22; /* opero sul numero corrispondente al carattere passato dall'utente in modo che questo numero corrisponda alla riga in cui è codificato questo carattere nel file di testo che contiene il font. Il primo carattere contenuto nel file e lo spazio, e questo corrisponde al numero 32 nel codice ASCII. Per tenere conto che il file non inizia al numero 0 sottraggo 32 al valore inserito dall'utente. Poiché ogni carattere è descritto da 22 numeri, la distanza tra un carattere e l'altro nel file sarà proprio 22. Per questo motivo moltiplico per 22 il valore ottenuto precedentemente. */
font[0] = font_11_16[ch0 + 0];
font[1] = font_11_16[ch0 + 1];
font[2] = font_11_16[ch0 + 2];
font[3] = font_11_16[ch0 + 3];
font[4] = font_11_16[ch0 + 4];
font[5] = font_11_16[ch0 + 5];
font[6] = font_11_16[ch0 + 6];
font[7] = font_11_16[ch0 + 7];
font[8] = font_11_16[ch0 + 8];
font[9] = font_11_16[ch0 + 9];
font[10] = font_11_16[ch0 + 10];
/* Ho appena letto i primi 11 numeri che compongono il font e li ho salvati in un vettore. A fronte di una certa occupazione di memoria causata da questa scelta ho il vantaggio di eseguire più velocemente le operazioni di trasposizione in ciascuna delle righe. */
/* Ora parto dalla riga più alta e compongo il carattere riga per riga. Ad ogni passo del ciclo for costruisco una riga. Con questo primo ciclo for costruisco le prime 8 righe del carattere */
for(t1 = 0; t1 < 8; t1++) // t1 va da 0 a 7
{
new_val = 0b0000000000000000; /* inizializzo la Word che poi andrà a passata allo schermo. Ricordiamo che allo schermo non si può passare un singolo punto ma un'intera Word alla volta */
XX = 0b0000000000000000; YY = 0b0000000000000000; /* inizializzo i valori che conterranno le coordinate della word da scrivere nello schermo. Si nota che queste variabili conterranno al massimo valori che arriveranno a 128 per cui sarebbe bastato un tipo char. Eppure, utilizzando il tipo char ho sperimentato continui problemi che si manifestavano come punti accesi casualmente nello schermo anziché un carattere. Dopo molti tentativi, seppur non capendo il motivo, ho risolto il problema utilizzando un tipo int */
if ((y + t1) >= 32) /* se la riga che sto per scrivere ha un valore di ordinate maggiore o uguale a 32 dobbiamo scriverla nella seconda metà dello schermo. Per farlo devo riportare i valori di ordinate tra zero e 31, e aggiungere 8 al valore dell'ascisse */
{ YY = y - 32;
XX = (x / 16) + 8; } /* converto i valori in pixel ovvero tra 0 e 127 nelle corrispondenti valori in word che compongono la riga del display. Ricordiamo che una riga del display è composta da 8 word */
else /* entro in questa parte se la riga che sto scrivendo appartiene alla metà superiore del display */
{ YY = y;
XX = x / 16; }
/* nelle istruzioni che seguono componiamo le righe del carattere da scrivere utilizzando il vettore font da 11 posizioni che ho precedentemente riempito */
for(t2 = 0; t2 < 11; t2++) {
/* nella prima riga del ciclo for viene eseguito ciò che segue ovvero vengono presi i bit meno significativi dei primi 11 byte del font e inseriti nelle prime 11 posizioni della word a iniziare da sinistra */
//new_val = ((ch0 & 0b0000000000000001) << 15) | ((ch1 & 0b0000000000000001) << 14) | ((ch2 & 0b0000000000000001) << 13) | ((ch3 & 0b0000000000000001) << 12) | ((ch4 & 0b0000000000000001) << 11) | ((ch5 & 0b0000000000000001) << 10) | ((ch6 & 0b0000000000000001) << 9) | ((ch7 & 0b0000000000000001) << 8) | ((ch8 & 0b0000000000000001) << 7) | ((ch9 & 0b0000000000000001) << 6) | ((ch10 & 0b0000000000000001) << 5);
/* qui di seguito è esplicitato ciò che viene eseguito nella seconda iterazione del ciclo for. Ovvero prendo il secondo bit da destra da tutti gli 11 elementi del vettore font e li trasferisco nella seconda riga del carattere, esattamente nelle prime 11 posizioni da sinistra */
//new_val = ((ch0 & 0b0000000000000010) << 14) | ((ch1 & 0b0000000000000010) << 13) | ((ch2 & 0b0000000000000010) << 12) | ((ch3 & 0b0000000000000010) << 11) | ((ch4 & 0b0000000000000010) << 11) | ((ch5 & 0b0000000000000010) << 10) | ((ch6 & 0b0000000000000010) << 9) | ((ch7 & 0b0000000000000010) << 8) | ((ch8 & 0b0000000000000010) << 7) | ((ch9 & 0b0000000000000010) << 6) | ((ch10 & 0b0000000000000010) << 5);
if (15-t2-t1 < 0)
{new_val = new_val | ((font[t2] & (0b0000000000000001 << t1)) >> -(15-t2-t1)); }
/* nel costruire la variabile new_val bisogna fare attenzione che nella 7° e 8° riga (15-t2-t1)<0 per cui devo spostare a destra (>>) anziché a sinistra (<<) */
else
{new_val = new_val | ((font[t2] & (0b0000000000000001 << t1)) << (15-t2-t1)); }
}
glcd_write_instruction(0x80 | (YY + t1)); /* invio al display le coordinate della word in cui scrivere la riga del carattere che ho appena composto */
glcd_write_instruction(0x80 | XX);
if (YY+t1 < 32) /* questo controllo non dovrebbe servire perché appena entrati nel ciclo for faccio sempre in modo che i valori di ordinate siano compresi fra 0 e 31. Eppure, se non metto questa istruzione, apparentemente superflua, alcuni pixel del display si accendono casualmente */
{
/* le due istruzioni che seguono servono a scrivere il font eliminando il contenuto che c'era precedentemente nello schermo. In questo modo posso scrivere un nuovo carattere e quello che c'era precedentemente viene automaticamente cancellato. Questa funzione è leggermente complicata perché non voglio eliminare tutto il contenuto della word nella quale sto scrivendo ma voglio eliminare solamente il contenuto degli 11 pixel nei quali voglio scrivere la riga del carattere. Per fare questo mi creo una maschera costituita da 11 uni che poi posso spostare a destra come spiegato successivamente. */
if (sovrascrivi) // sostituisce
{ maschera = 0b1111111111100000 >> (x % 16);
matrice[XX][YY+t1] = (matrice[XX][YY+t1] & ~maschera) | (new_val >> (x % 16));
}
/* con il comando (matrice[XX][YY+t1] & ~maschera) prelevo solamente i pixel precedentemente scritti nel display e che non si trovano nella stessa posizione del carattere che sto per scrivere */
/* Ora, teniamo conto che il carattere ha una larghezza di 11 pixel, che dobbiamo scrivere all'interno di una word, per cui le word delle diverse righe apparirebbero come segue
0000010000000000
0000111000000000
0000101000000000
0001101100000000
0001000100000000
0001000100000000
0011000110000000
0010000010000000
0011111110000000
0110000011000000
0100000001000000
0100000001000000
1100000001100000
1000000000100000
1000000000100000
1000000000100000
Questa disposizione è corretta se l'utente volesse scrivere nelle posizioni x = 0 oppure 16 o 32 ecc. Ovvero (x % 16) = 0. Ma se l'utente volesse scrivere nella posizione x uguale uno allora dovrei traslare il font di una posizione a destra prima di inviarlo al display. In questo modo:
0000001000000000
0000011100000000
0000010100000000
0000110110000000
0000100010000000
0000100010000000
0001100011000000
0001000001000000
0001111111000000
0011000001100000
0010000000100000
0010000000100000
0110000000110000
0100000000010000
0100000000010000
0100000000010000
Questa traslazione è ottenuta con il comando (new_val >> (x % 16)) il quale calcola il resto di x su base 16. Questa operazione è eseguita all'interno dell'else che segue
*/
else {matrice[XX][YY+t1] = matrice[XX][YY+t1] | (new_val >> (x % 16)); }
/* notiamo che nella riga precedente effettuiamo un OR logico ovvero aggiungiamo la riga del font al contenuto precedente che c'era nello schermo ed è contenuto in matrice[XX][YY+t1] */
glcd_write_data(matrice[XX][YY+t1] >> 8); /* invio al display i dati relativi agli 8 bit più significativi della word */
glcd_write_data(matrice[XX][YY+t1]); /* invio al display gli 8 bit meno significativi della word */
}
/* potrebbe capitare che l'utente abbia inserito una posizione di ascisse che porta il carattere a cavallo tra due Word. Questo capita quando ((x % 16)+FONTWIDTH) >= 16
In altre parole, può succedere ciò che si vede di seguito:
0000000000000001 0000000000000000
0000000000000011 1000000000000000
0000000000000010 1000000000000000
0000000000000110 1100000000000000
0000000000000100 0100000000000000
0000000000000100 0100000000000000
0000000000001100 0110000000000000
0000000000001000 0010000000000000
0000000000001111 1110000000000000
0000000000011000 0011000000000000
0000000000010000 0001000000000000
0000000000010000 0001000000000000
0000000000110000 0001100000000000
0000000000100000 0000100000000000
0000000000100000 0000100000000000
0000000000100000 0000100000000000
Questo viene gestito nella parte seguente del codice
*/
if (((x % 16)+FONTWIDTH) >= 16)
/* scrivo la parte di font che si trova nel secondo byte */
{
/* invio al display le coordinate della word in cui scrivere la riga del carattere che ho appena composto */
glcd_write_instruction(0x80 | (YY + t1));
glcd_write_instruction(0x80 | (XX + 1)); // in questo caso la word successiva alla prima
if (YY+t1 < 32) /* questo controllo non dovrebbe servire perché appena entrati nel ciclo for faccio sempre in modo che i valori di ordinate siano compresi fra 0 e 31. Eppure, se non metto questa istruzione, apparentemente superflua, alcuni pixel del display si accendono casualmente */
{
if (sovrascrivi) { // stessi commenti della sezione precedente
maschera = 0b1111111111100000 << (16-(x % 16));
matrice[XX+1][YY+t1] = (matrice[XX+1][YY+t1] & ~maschera) | (new_val << (16-(x % 16)));
} //matrice[XX+1][YY+t1] = (new_val << (16-(x % 16)));
else {matrice[XX+1][YY+t1] = matrice[XX+1][YY+t1] | (new_val << (16-(x % 16))); } // aggiunge al contenuto precedente
glcd_write_data(matrice[XX+1][YY+t1] >> 8);
glcd_write_data(matrice[XX+1][YY+t1]);
}
}
}
/* ora leggo la metà inferiore del font e ripeto le operazioni viste in precedenza */
font[0] = font_11_16[ch0 + 11];
font[1] = font_11_16[ch0 + 12];
font[2] = font_11_16[ch0 + 13];
font[3] = font_11_16[ch0 + 14];
font[4] = font_11_16[ch0 + 15];
font[5] = font_11_16[ch0 + 16];
font[6] = font_11_16[ch0 + 17];
font[7] = font_11_16[ch0 + 18];
font[8] = font_11_16[ch0 + 19];
font[9] = font_11_16[ch0 + 20];
font[10] = font_11_16[ch0 + 21];
for(t1 = 8; t1 < 16; t1++) { //scrivo il carattere riga per riga
new_val = 0b0000000000000000;
XX = 0b0000000000000000; YY = 0b0000000000000000;
if ((y + t1) >= 32) // metà inferiore dello schermo
{ YY = y - 32;
XX = (x / 16) + 8; }
else // metà superiore
{ YY = y;
XX = x / 16; }
glcd_write_instruction(0x80 | (YY + t1));
glcd_write_instruction(0x80 | XX);
for(t2 = 0; t2 < 11; t2++) {
if (15-t2-t1+8 < 0)
{new_val = new_val | ((font[t2] & (0b0000000000000001 << (t1-8))) >> -(15-t2-t1+8)); } // per la 7° e 8° riga (15-t2-t1+8)<0 per cui devo spostare a destra anziché a sinistra
else
{new_val = new_val | ((font[t2] & (0b0000000000000001 << (t1-8))) << (15-t2-t1+8)); }
}
if (YY+t1 < 32)
{
if (sovrascrivi) //sostituisce
{ maschera = 0b1111111111100000 >> (x % 16);
matrice[XX][YY+t1] = (matrice[XX][YY+t1] & ~maschera) | (new_val >> (x % 16));
}
else {matrice[XX][YY+t1] = matrice[XX][YY+t1] | (new_val >> (x % 16)); } // aggiunge al contenuto precedente
glcd_write_data(matrice[XX][YY+t1] >> 8);
glcd_write_data(matrice[XX][YY+t1]);
}
if (((x % 16)+FONTWIDTH) >= 16) // se il carattere da scrivere si trova tra due byte
{ // scrivo la parte che sta nel secondo byte
glcd_write_instruction(0x80 | (YY + t1));
glcd_write_instruction(0x80 | (XX + 1)); // vado a destra
if (YY+t1 < 32)
{
if (sovrascrivi) // sostituisce
{ maschera = 0b1111111111100000 << (16-(x % 16));
matrice[XX+1][YY+t1] = (matrice[XX+1][YY+t1] & ~maschera) | (new_val << (16-(x % 16)));
}
else
{matrice[XX+1][YY+t1] = matrice[XX+1][YY+t1] | (new_val << (16-(x % 16))); } // aggiunge al contenuto precedente
glcd_write_data(matrice[XX+1][YY+t1] >> 8);
glcd_write_data(matrice[XX+1][YY+t1]);
} // chiude if
} // chiude if
} // chiude for
}