MICHELEPISANI.IT
 

Una demo del gioco anni '80 PAC-MAN su LCD

Caratteri Personalizzati su Display LCD 16x2 e LCD 20x4 con Arduino

Creare Caratteri Personalizzati su Display LCD 16x2 e LCD 20x4 con Arduino
March 29
07:222017

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.

Tags

Autore

Michele Pisani

Michele Pisani

Ho uno spiccato orientamento al problem-solving, se è troppo facile non mi diverto :)
Credo nella volontà e nel cambiamento perchè hanno fatto della mia passione il mio pane quotidiano.
Se devo descrivermi con una sola parola direi... "Concretezza", la mia stretta di mano è una garanzia.

4 Commenti

  1. Roberto Sunday, December 24, 2017 alle ore 17:35

    È molto utile per capire questi piccoli ma interessanti trucchi!
    Complimenti

    Rispondi a questo commento
    • Michele PisaniAutore Sunday, December 24, 2017 alle ore 17:47

      Grazie a te Roberto per il feedback :)

      Rispondi a questo commento
      • Domenico 74 Wednesday, August 29, 2018 alle ore 20:43

        Salve, e se si vuole creare una figura animata nello stesso punto come si fa?
        Grazie

      • Michele PisaniAutore Monday, September 3, 2018 alle ore 12:17

        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).

Scrivi un Commento

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

Articoli e Argomenti correlati

Il Canale YouTube in ITALIANO

1 VIDEO GRATIS ogni 2 settimane! ISCRIVITI!

Entra a far parte della community su Facebook

Categorie popolari

Iscriviti alla mia newsletter

La tua e-mail con me sarà al sicuro.
Non fornirò mai le tue informazioni a nessuno!

Ultimi commenti

Michele Pisani

Hai verificato se succede per tutte le pagine da un certo periodo di tempo in poi o solo su alcune?

Tecnowiz

Sono amministratore di un blog pure io... Intanto ti faccio i complimenti per questo articolo. …

Michele Pisani

Ciao Antonio, grazie per la fiducia :) In caso di più fogli è necessario inserire il codice del …

Antonio

Ciao Michele, ho acquistato il tuo libro che unitamente ai tuoi video sta diventando il mio corso …