Cercando di ottenere un deterrminato carattere da utilizzare in un display LCD per Arduino, in particolare quello che mi serviva era una sorta di cursore lampeggiante, mi sono fatto prendere la mano ed imbattendomi nei caratteri personalizzati per LCD ho deciso di volerne capire il funzionamento finendo per realizzare una simpatica demo del famoso gioco anni'80... PAC-MAN che gira su un display LCD 20x4 (20 caratteri per 4 righe). Premetto che lo stesso tipo di carattere personalizzato è ottenibile anche su display LCD 1602 (16 caratteri per 2 righe).
Senza scendere in particolari tecnicismi, per i quali esistomo già guide accurate in rete, la possibilità di creare dei caratteri personalizzati è data dalla caratteristica di alcuni display LCD di avere una porzione di memoria vuota, e pertanto scrivibile, di 64byte. Considerando che ogni carattere occupa 8 byte abbiamo la possibilità di utilizzare fino a 8 caratteri personalizzati (richiamabili con le posizioni da 0 a 7 come indicato poco sotto nell'esempio dell'articolo).
Ogni carattere è formato da 8 righe contenenti 5 pixel ciascuno. Per la realizzazione dei caratteri creativi esistono diversi tool online che agevolano il lavoro permettendo, tramite interfaccia, di accendere e spegnere i singoli pixel fino all'ottenimento del carattere desiderato e copiare il relativo codice generato automaticamente, personalmente ne ho provati due o tre e alla fine mi sono trovato bene con questo: https://maxpromer.github.io/LCD-Character-Creator/.
Con tale strumento ho creato i miei caratteri personalizzati per comporre la demo di PAC-MAN, nel caso specifico: 3 caratteri per lo scenario, 2 per il personaggio principale (uno orientato verso destra e uno verso sinistra), 2 per i fantasmi (uno per il fantasma cattivo e l'altro per il fantasma spaventato) e 2 per il cibo (uno per il cibo piccolo e l'altro per il cibo speciale). Penserete a questo punto che ho sbagliato a fare i miei conti perchè 3+2+2+2 fa 9 quando invece ho detto poco fa che il numero massimo di caratteri personalizzati utilizzabili è 8... Ebbene si, ho detto 'utilizzabili' di proposito, perchè di caratteri di questo tipo ne posso impostare quanti ne voglio, sarò io poi ad andare a definire in quale delle 8 posizioni dovranno trovarsi e considerando che i fantasmi in PAC-MAN possono essere o tutti cattivi o tutti spaventati (dopo che PAC-MAN ha mangiato il cibo speciale), vado a sostituire all'occorrenza l'uno e l'altro carattere a runtime nella funzione loop di Arduino.
Dopo questo doveroso preambolo ecco un breve video di 18 secondi che mostra la demo realizzata:
E finalmente la parte più interessante, lo sketch completo per Arduino (per il collegamento del display LCD ad Arduino ho utilizzato una scheda I2C ma chi non ne avesse una a disposizione può collegare semplicemente i cavi a ciascun pin tenendo presente di modificare lo sketch per quanto riguarda la libreria LiquidCrystal e le sue opportune inizializzazioni):
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
// caratteri pacman: personaggi e cibo
byte pacmanopenrightChar[8] = {
B00000,
B01110,
B11101,
B11111,
B11000,
B11111,
B01110,
B00000
};
byte pacmanopenleftChar[8] = {
B00000,
B01110,
B10111,
B11111,
B00011,
B11111,
B01110,
B00000
};
byte ghostChar[8] = {
B00000,
B01110,
B10101,
B11111,
B11111,
B11111,
B10101,
B00000
};
byte ghostfearChar[] = {
B00000,
B01110,
B10101,
B11111,
B10001,
B11111,
B10101,
B00000
};
byte eatsmallChar[8] = {
B00000,
B00000,
B00000,
B00110,
B00110,
B00000,
B00000,
B00000
};
byte eatbigChar[8] = {
B00000,
B00000,
B01111,
B01111,
B01111,
B01111,
B00000,
B00000
};
// percorso
byte horizontalChar[] = {
B11111,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B11111
};
byte verticalrightChar[] = {
B11111,
B00001,
B00001,
B00001,
B00001,
B00001,
B00001,
B11111
};
byte verticalleftChar[] = {
B11111,
B10000,
B10000,
B10000,
B10000,
B10000,
B10000,
B11111
};
void setup() {
lcd.begin(20, 4);
lcd.setCursor(5,0);
lcd.write("PACMAN demo");
lcd.setCursor(0,1);
lcd.write("www.michelepisani.it");
for (int i=0; i<20; i++) {
lcd.setCursor(i,2);
lcd.write(byte(5));
}
lcd.setCursor(7,3);
lcd.write(byte(0));
lcd.setCursor(8,3);
lcd.write(byte(3));
lcd.setCursor(9,3);
lcd.write(byte(3));
lcd.setCursor(10,3);
lcd.write(byte(3));
lcd.setCursor(11,3);
lcd.write(byte(3));
lcd.setCursor(12,3);
lcd.write(byte(2));
// creazione caratteri speciali
lcd.createChar(0, pacmanopenrightChar);
lcd.createChar(1, pacmanopenleftChar);
lcd.createChar(2, ghostChar);
lcd.createChar(3, eatsmallChar);
lcd.createChar(4, eatbigChar);
lcd.createChar(5, horizontalChar);
lcd.createChar(6, verticalrightChar);
lcd.createChar(7, verticalleftChar);
}
void loop() {
delay(4000);
// scenario
for (int i=0; i<20; i++) {
lcd.setCursor(i,0);
lcd.write(byte(3));
lcd.setCursor(i,1);
lcd.write(byte(5));
lcd.setCursor(i,2);
lcd.write(byte(3));
lcd.setCursor(i,3);
lcd.write(byte(5));
}
lcd.setCursor(12,3);
lcd.write(byte(6));
lcd.setCursor(13,3);
lcd.write(3);
lcd.setCursor(14,3);
lcd.write(byte(7));
lcd.setCursor(10,1);
lcd.write(byte(6));
lcd.setCursor(11,1);
lcd.write(3);
lcd.setCursor(12,1);
lcd.write(byte(7));
lcd.setCursor(6,1);
lcd.write(byte(6));
lcd.setCursor(7,1);
lcd.write(3);
lcd.setCursor(8,1);
lcd.write(byte(7));
// cibo speciale
lcd.setCursor(0,0);
lcd.write(byte(4));
lcd.setCursor(19,2);
lcd.write(byte(4));
// pacman
lcd.setCursor(4,0);
lcd.write(byte(0));
// fantasma
lcd.setCursor(8,0);
lcd.write(byte(2));
lcd.setCursor(2,2);
lcd.write(byte(2));
lcd.setCursor(15,2);
lcd.write(byte(2));
delay(500);
// step 1
lcd.setCursor(5,0);
lcd.write(byte(0));
lcd.setCursor(4,0);
lcd.write(" ");
lcd.setCursor(7,0);
lcd.write(byte(2));
lcd.setCursor(8,0);
lcd.write(byte(3));
lcd.setCursor(3,2);
lcd.write(byte(2));
lcd.setCursor(2,2);
lcd.write(byte(3));
lcd.setCursor(14,2);
lcd.write(byte(2));
lcd.setCursor(15,2);
lcd.write(byte(3));
delay(500);
// step 2
lcd.setCursor(4,0);
lcd.write(byte(1));
lcd.setCursor(5,0);
lcd.write(" ");
lcd.setCursor(6,0);
lcd.write(byte(2));
lcd.setCursor(7,0);
lcd.write(byte(3));
lcd.setCursor(4,2);
lcd.write(byte(2));
lcd.setCursor(3,2);
lcd.write(byte(3));
lcd.setCursor(13,2);
lcd.write(byte(2));
lcd.setCursor(14,2);
lcd.write(byte(3));
delay(500);
// step 3
lcd.setCursor(3,0);
lcd.write(byte(1));
lcd.setCursor(4,0);
lcd.write(" ");
lcd.setCursor(5,0);
lcd.write(byte(2));
lcd.setCursor(6,0);
lcd.write(byte(3));
lcd.setCursor(5,2);
lcd.write(byte(2));
lcd.setCursor(4,2);
lcd.write(byte(3));
lcd.setCursor(12,2);
lcd.write(byte(2));
lcd.setCursor(13,2);
lcd.write(byte(3));
delay(500);
// step 4
lcd.setCursor(2,0);
lcd.write(byte(1));
lcd.setCursor(3,0);
lcd.write(" ");
lcd.setCursor(4,0);
lcd.write(byte(2));
lcd.setCursor(5,0);
lcd.write(" ");
lcd.setCursor(6,2);
lcd.write(byte(2));
lcd.setCursor(5,2);
lcd.write(byte(3));
lcd.setCursor(11,2);
lcd.write(byte(2));
lcd.setCursor(12,2);
lcd.write(byte(3));
delay(500);
// step 5
lcd.setCursor(1,0);
lcd.write(byte(1));
lcd.setCursor(2,0);
lcd.write(" ");
lcd.setCursor(3,0);
lcd.write(byte(2));
lcd.setCursor(4,0);
lcd.write(" ");
lcd.setCursor(7,2);
lcd.write(byte(2));
lcd.setCursor(6,2);
lcd.write(byte(3));
lcd.setCursor(11,1);
lcd.write(byte(2));
lcd.setCursor(11,2);
lcd.write(byte(3));
delay(500);
// step 6
lcd.setCursor(0,0);
lcd.write(byte(1));
lcd.setCursor(1,0);
lcd.write(" ");
lcd.setCursor(2,0);
lcd.write(byte(2));
lcd.setCursor(3,0);
lcd.write(" ");
lcd.setCursor(7,1);
lcd.write(byte(2));
lcd.setCursor(7,2);
lcd.write(byte(3));
lcd.setCursor(11,0);
lcd.write(byte(2));
lcd.setCursor(11,1);
lcd.write(byte(3));
delay(250);
lcd.noBacklight();
lcd.createChar(2, ghostfearChar);
delay(250);
lcd.backlight();
delay(250);
lcd.noBacklight();
delay(250);
lcd.backlight();
delay(250);
lcd.noBacklight();
delay(250);
lcd.backlight();
delay(500);
// step 7
lcd.setCursor(1,0);
lcd.write(byte(0));
lcd.setCursor(0,0);
lcd.write(" ");
delay(500);
// step 8
lcd.setCursor(2,0);
lcd.write(byte(0));
lcd.setCursor(1,0);
lcd.write(" ");
lcd.setCursor(3,0);
lcd.write(byte(2));
lcd.setCursor(7,2);
lcd.write(byte(2));
lcd.setCursor(7,1);
lcd.write(byte(3));
lcd.setCursor(12,0);
lcd.write(byte(2));
lcd.setCursor(11,0);
lcd.write(byte(3));
delay(500);
// step 9
lcd.setCursor(3,0);
lcd.write(byte(0));
lcd.setCursor(2,0);
lcd.write(" ");
delay(500);
// step 10
lcd.setCursor(4,0);
lcd.write(byte(0));
lcd.setCursor(3,0);
lcd.write(" ");
delay(500);
// step 11
lcd.setCursor(5,0);
lcd.write(byte(0));
lcd.setCursor(4,0);
lcd.write(" ");
delay(500);
// step 12
lcd.setCursor(6,0);
lcd.write(byte(0));
lcd.setCursor(5,0);
lcd.write(" ");
lcd.setCursor(6,2);
lcd.write(byte(2));
lcd.setCursor(7,2);
lcd.write(byte(3));
lcd.setCursor(13,0);
lcd.write(byte(2));
lcd.setCursor(12,0);
lcd.write(byte(3));
delay(500);
// step 13
lcd.setCursor(7,0);
lcd.write(byte(0));
lcd.setCursor(6,0);
lcd.write(" ");
delay(500);
// step 14
lcd.setCursor(8,0);
lcd.write(byte(0));
lcd.setCursor(7,0);
lcd.write(" ");
delay(500);
// step 15
lcd.setCursor(9,0);
lcd.write(byte(0));
lcd.setCursor(8,0);
lcd.write(" ");
delay(500);
// step 16
lcd.setCursor(10,0);
lcd.write(byte(0));
lcd.setCursor(9,0);
lcd.write(" ");
delay(250);
lcd.noBacklight();
lcd.createChar(2, ghostChar);
delay(250);
lcd.backlight();
delay(250);
lcd.noBacklight();
delay(250);
lcd.backlight();
delay(250);
lcd.noBacklight();
delay(250);
lcd.backlight();
delay(500);
// step 17
lcd.setCursor(9,0);
lcd.write(byte(1));
lcd.setCursor(10,0);
lcd.write(" ");
lcd.setCursor(7,2);
lcd.write(byte(2));
lcd.setCursor(6,2);
lcd.write(byte(3));
lcd.setCursor(12,0);
lcd.write(byte(2));
lcd.setCursor(13,0);
lcd.write(byte(3));
delay(500);
// step 18
lcd.backlight();
lcd.setCursor(8,0);
lcd.write(byte(1));
lcd.setCursor(9,0);
lcd.write(" ");
lcd.setCursor(7,1);
lcd.write(byte(2));
lcd.setCursor(7,2);
lcd.write(byte(3));
lcd.setCursor(11,0);
lcd.write(byte(2));
lcd.setCursor(12,0);
lcd.write(byte(3));
delay(500);
// step 19
lcd.setCursor(7,0);
lcd.write(byte(2));
lcd.setCursor(8,0);
lcd.write(" ");
lcd.setCursor(7,1);
lcd.write(byte(3));
lcd.setCursor(10,0);
lcd.write(byte(2));
lcd.setCursor(11,0);
lcd.write(byte(3));
lcd.setCursor(5,2);
lcd.write("GAME OVER");
delay(1000);
}
Sicuramente il codice è migliorabile ed integrabile ma credo che per coloro che sono finiti in questa pagina dopo una ricerca relativa alla realizzazione di un carattere personalizzato per display LCD possa in qualche modo tornare utile.
È molto utile per capire questi piccoli ma interessanti trucchi!
Complimenti
Grazie a te Roberto per il feedback :)
Salve, e se si vuole creare una figura animata nello stesso punto come si fa?
Grazie
Ciao Domenico 74,
direi che ti basta mantenere il cursore nello stessa posizione e modificare il contenuto dell'elemento dell'lcd (giocando con il delay tra il passaggio da un carattere all'altro nello stesso elemento).