I²C tekst display met twee regels van 16, 20 of 24 tekens

De programma's beneden zijn gemaakt voor LCD tekst displays van twee regels van 16, 20 of 24 tekens. Dit soort displays kunnen het adres 0x27 of 0x3F gebruiken. Als je display met het ene adres niet werkt probeer dan het andere. Op foto's van deze displays kun je niet zien hoe mooi deze displays zijn. Displays met witte letters op een blauwe achtergrond zijn vaak veel helderder dan zwarte letters op een groene achtergrond. Je kunt de helderheid tussen bepaalde grenzen regelen met de stelknop achter op de interface van het display.
Er zijn diverse bibliotheken die je kunt gebruiken met dit display. Ik gebruikte tot voor kort altijd de bibliotheek LiquidCrystal_I2C van Francisco Malpartida. Deze komt in de plaats van de standaard LiquidCrystal_I2C. Tegenwoordig wordt de bibliotheek van Frank de Brabander geadviseerd. Omdat deze ook LiquidCrystal_I2C heet kan er verwarring ontstaan. Je kunt deze bibliotheek heel gemakkelijk vinden in Bibliotheekbeheer en dan meteen installeren.
De bibliotheek LiquidCrystal_I2C_Hangul.h werkt ook erg eenvoudig en kan eveneens via het menu bibliotheek beheren geïnstalleerd worden. Deze bibliotheek ondersteunt, zoals de naam al aangeeft het Hangul (Koreaans) schrift, maar de resultaten zijn niet overtuigend. In beide methode zet je de achtergrond aan met de instructie background(), maar setBacklight(HIGH) werkt ook.
De beste en meest stabiele bibliotheek is LiquidCrystal_PCF8574.h van Matthias Hertel. Deze werkt helaas nogal afwijkend. Toch kunnen animaties er beter uitzien als je de bibliotheek van Matthias Hertel gebruikt. Bij deze bibliotheek moet je de achtergrondverlichting aanzetten met setBacklight(1), waarbij 1 de intensiteit is (je mag een hoger getal invullen - maximum 255, maar ik zie bij mijn displays geen verschil).
Als je de oude, standaardbibliotheek LiquidCrystal_I2C gebruikt dan kan het zijn dat lcd.print maar één letter print.
Je moet dan in het bestand LiquidCrystal_I2C.cpp bij

inline size_t LiquidCrystal_I2C::write(uint8_t value) {
	send(value, Rs);
	return 0;
}

de 0 veranderen in een 1. Ik heb voor de duidelijkheid van alle bibliotheken voorbeelden opgenomen, zie onder "Een Valentijn surprise" of "Lichtkrant". Als je deze bibliotheek gebruikt met een ESP processor, dan krijg je in de Arduino IDE een waarschuwing dat de bibliotheek misschien niet werkt, maar ik ben geen enkel probleem tegengekomen. Ik hem dit getest met een ESP32 en een ESP8266, met verschillende displays.

Aansluiting op je Arduino

Dit is heel simpel: VCC op 5 volt en GND op GND; bij de meeste Arduino's SDA op A4 en SCL op A5.

Aansluiting op systemen met een ESP processor

Modules met een ESP processor hebben doorgaans alleen 3,3 volt uitgangen. Een enkele zoals de WeMos D1 mini en de LoLin nodeMCU v2 hebben ook een pin die 5 volt levert, mits je de USB poort gebruikt voor de voeding.

Step-up (of boost-up) converter
Als jouw module alleen 3,3 volt uitgangen heeft, dan kun je een aparte 5 volt voeding voor het display gebruiken. Het is echter handiger om een zogenaamde step-up converter te gebruiken. Koop er een die 5 volt als uitgang heeft. Sluit VIN (of Vi of V1) van de step-up converter aan op een 3,3 volt pin en GND op GND. VOUT (of Vo) gaat dan naar VCC van het display.

Via step-up converter naar display.
Links: WeMos ESP8266 nodeMCU; rechts ESP32 WROOM

Bij bijna alle nodeMCU's die de ESP2866 gebruiken is pin D1 de klok en pin D2 de datalijn. Sluit D1 aan op SCL van het display en D2 op SDA van het display. Bij nodeMCU's, devKits e.d. die de ESP-32 gebruiken is pin 21 SDA en pin 22 SCL. Ik testte meer dan tien van deze displays met een aantal verschillende borden, waaronder met ESP32, allemaal zonder problemen.

Voorbeeldprogramma's

Eigen symbolen

24 × 2 display
16 × 2 display
Als eerste laat ik zien hoe je je eigen symbolen kunt maken. Op deze pagina kun je gemakkelijk je eigen ontwerp maken. Meer dan 8 verschillende tegelijkertijd gaat niet, maar na elkaar wel. Verderop staat enige uitleg bij de code. LET OP: bij sommige displays moet je in de tweede regel 0x3F vervangen door 0x27.

#include <LiquidCrystal_I2C.h> // Versie van Frank de Brabander

// Pas indien nodig het i2c adres (0x27) aan. Mogelijke waarden: 0x27 en 0x3F
LiquidCrystal_I2C lcd(0x27, 16, 2); // initialiseer op 16 karakters en 2 regels.
// Definieer enkele eigen symbolen
byte lach[8] = {0x0E, 0x11, 0x1B, 0x11, 0x1B, 0x15, 0x11, 0x0E};
byte pop[8]  = {0x04, 0x0E, 0x0E, 0x04, 0x0E, 0x1F, 0x1F, 0x0E};
byte pijl[8] = {0x04, 0x0E, 0x1F, 0x04, 0x04, 0x04, 0x04, 0x04};
byte raam[8] = {0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1F};

void setup() {
  lcd.init();
  lcd.setBacklight(HIGH);
  lcd.print("Eigen symbolen:");
  lcd.createChar(0, lach); lcd.createChar(1, pop);
  lcd.createChar(2, pijl); lcd.createChar(3, raam);
  lcd.setCursor(3, 1); lcd.write(0); lcd.print(" ");
  lcd.write(1); lcd.print(" "); lcd.write(2);
  lcd.print(" "); lcd.write(3);
}

void loop() {
}


#include <LiquidCrystal_I2C.h> // Installeer de versie van Frank de Brabander

// Pas indien nodig het i2c adres (0x27) aan. Mogelijke waarden: 0x27 en 0x3F
LiquidCrystal_I2C lcd(0x27, 24, 2); // initialiseer op 24 karakters en 2 regels.
// Definieer enkele eigen symbolen
byte lach[8] = {0x0E, 0x11, 0x1B, 0x11, 0x1B, 0x15, 0x11, 0x0E}; // Met hex getallen...
byte pop[8]  = {0x04, 0x0E, 0x0E, 0x04, 0x0E, 0x1F, 0x1F, 0x0E};
byte pijl[8] = {0x04, 0x0E, 0x1F, 0x04, 0x04, 0x04, 0x04, 0x04};
byte raam[8] = {0x1F, 0x15, 0x15, 0x1F, 0x15, 0x15, 0x15, 0x1F};
byte hart[8] = {0, 10, 31, 31, 14, 4, 0, 0}; // of met decimale getallen...
byte euro[8] = {0, B111, B1000, B11110, B1000, B11110, B1000, B111}; // of met binaire getallen

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0); lcd.print("Dit display is 24 breed!");
  // Ken een nummer (0 t/m 5) toe aan de eigen karakters:
  lcd.createChar(0, lach); lcd.createChar(1, pop);
  lcd.createChar(2, pijl); lcd.createChar(3, raam);
  lcd.createChar(4, hart); lcd.createChar(5, euro);
  lcd.setCursor(6, 1);
  lcd.write((byte)0); lcd.print(" ");
  lcd.write(1); lcd.print(" ");
  lcd.write(2); lcd.print(" ");
  lcd.write(3); lcd.print(" ");
  lcd.write(4); lcd.print(" ");
  lcd.write(5);
}

void loop() {
}

Uitleg bij de code

Voor meer informatie over LiquidCrystal_I2C kijk bij het vierregelig display.
In de vier regels die met byte beginnen worden de speciale symbolen gedefinieerd. Per pixel-regel wordt één byte gebruikt. Omdat dit display symbolen gebruikt van 5 pixels breed zijn alleen de laatste 5 bits van de byte van belang. Ik heb om de getallen te krijgen een programma gebruikt. Het is niet moeilijk om dat zonder programma te doen: stel dat je het raam wil maken (x = pixel AAN, spatie = pixel UIT)
xxxxx	Vertaal dit in B11111  (B staat voor binair, dit moet een hoofdletter zijn!)
x x x   Vertaal dit in B10101  (gebruik een 1 voor AAN en een 0 voor UIT)
x x x   Vertaal dit in B10101
xxxxx   Vertaal dit in B11111
x x x   Vertaal dit in B10101
x x x   Vertaal dit in B10101
x x x   Vertaal dit in B10101
xxxxx   Vertaal dit in B11111
De instructie wordt dan dus byte raam[8] = {B11111, B10101, B10101, B11111, B10101, B10101, B10101, B11111};
Je kunt de nieuwe symbolen op twee manieren schrijven: met write of met print. Als je write gebruikt dan kun je gewoon het nummer van het symbool invoeren (maar als dat nummer 0 is moet je in sommige bibliotheken char(0) zetten. Als je print gebruikt dan moet je aangeven dat je een char bedoelt. Voor de pijl (nummer 2): lcd.write(2); of lcd.print(char(2));. Theoretisch is het beter om de byte-waarde door te geven in plaats van een char en dat kan belangrijk zijn als je andere bibliotheken gebruikt dan hier: gebruik dan bijvoorbeeld lcd.print( (byte) 0). Het kan nog compacter door direct de hexadecimale waarde waarde door te geven. Dit geeft de mogelijkheid om in een print statement een aantal symbolen tegelijk te zetten: lcd.print("\x00\x01\x02");. Als je weet dat de hex waarde van de spatie 20 is dan kun je de regels
lcd.write(1); lcd.print(" "); lcd.write(2);   
lcd.print(" "); lcd.write(3);

uit het 16 × 2 programma vervangen door: lcd.print("\x01\x20\x02\x20\x03");. Dit is efficiënter, maar gaat wel te koste van de leesbaarheid. Bovendien mag je \x00 niet in de string hebben staan, omdat dit het einde van de string aanduidt.

Een Valentijn surprise

Hier hoef je alleen de naam van je geliefde in te zetten (in plaats van Lisa). Als de naam van je geliefde langer dan 7 letters is dan past dat niet op het standaard 1602 display en zul je de eerste regel moeten aanpassen of een 24 × 2 display gebruiken.

// Lisa.ino voor 16 x 2 display Copyright (2016/2026) Th.M. Hupkens
#include <LiquidCrystal_I2C.h> // Versie van Frank de Brabander
// Vervang hieronder 0x3F door 0x27 als het display niet werkt
LiquidCrystal_I2C lcd(0x27, 16, 2); // initialiseer op 20 karakters en 2 regels
byte hartje[]  = {10, 31, 31, 14, 4, 0, 0, 0};
byte l_hart[]  = {0, 4, 14, 31, 15, 7, 3, 1};
byte r_hart[]  = {0, 8, 28, 30, 28, 24, 16, 0};
byte l_hrt[]   = {0, 4, 14, 0, 0, 7, 3, 1};
byte r_hrt[]   = {0, 8, 28, 0, 0, 24, 16, 0};
byte pijl[]    = {0, 4, 6, 31, 6, 4, 0, 0};
byte schacht[] = {0, 0, 0, 31, 0, 0, 0, 0, 0};

void setup() {
  lcd.init(); 
  lcd.backlight();
  lcd.createChar(2, schacht); lcd.createChar(3, pijl);
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart);
  lcd.createChar(7, hartje); lcd.setCursor(0, 0);
  lcd.print("Voor "); lcd.write(7); lcd.print(" Lisa "); lcd.write(7);
  lcd.setCursor(12, 1); lcd.write(0); lcd.write(1);
}

void Pijl(byte x) {
  if (x > 0) {
    lcd.setCursor(x - 1, 1);
    if (x < 13 || x > 14) lcd.print(" ");
  }
  lcd.setCursor(x, 1);
  if (x < 12 || x > 13) lcd.write(2);
  lcd.setCursor(x + 1, 1);
  if (x < 11 || x > 12) lcd.write(2);
  lcd.setCursor(x + 2, 1);
  if (x < 10 || x > 11) lcd.write(3);
  if (x == 13) {
    lcd.createChar(0, l_hrt); // Gebroken hart
    lcd.createChar(1, r_hrt);
  }
  delay(200); // Stel hier de gewenste snelheid in
}

void loop() {
  delay(2500);
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart); // Herstel hart
  for (byte Teller = 0; Teller < 18; Teller++) Pijl(Teller);
}
void loop() {
  delay(2500);
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart); // Herstel hart
  for (byte Teller = 0; Teller < 18; Teller++) Pijl(Teller);
}

// Angelina.ino voor 20 x 2 display Copyright (2016/2020) Th.M. Hupkens
#include  // Versie van Frank de Brabander
// Vervang hieronder 0x3F door 0x27 als het display niet werkt
LiquidCrystal_I2C lcd(0x27, 20, 2); // initialiseer op 20 karakters en 2 regels
byte hartje[]  = {10, 31, 31, 14, 4, 0, 0, 0};
byte l_hart[]  = {0, 4, 14, 31, 15, 7, 3, 1};
byte r_hart[]  = {0, 8, 28, 30, 28, 24, 16, 0};
byte l_hrt[]   = {0, 4, 14, 0, 0, 7, 3, 1};
byte r_hrt[]   = {0, 8, 28, 0, 0, 24, 16, 0};
byte pijl[]    = {0, 4, 6, 31, 6, 4, 0, 0};
byte schacht[] = {0, 0, 0, 31, 0, 0, 0, 0};

void setup() {
  lcd.init(); 
  lcd.setBacklight(HIGH);
  lcd.createChar(2, schacht); lcd.createChar(3, pijl);
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart);
  lcd.createChar(7, hartje); lcd.setCursor(0, 0);
  lcd.print("Voor "); lcd.print(char(7)); lcd.print(" Angelina "); lcd.print(char(7));
  lcd.setCursor(12, 1); lcd.print(char(0)); lcd.print(char(1));
}

void Pijl(byte x) {
  if (x > 0) {
    lcd.setCursor(x - 1, 1);
    if (x < 13 || x > 14) lcd.print(" ");
  }
  lcd.setCursor(x, 1);
  if (x < 12 || x > 13) lcd.write(2);
  lcd.setCursor(x + 1, 1);
  if (x < 11 || x > 12) lcd.write(2);
  lcd.setCursor(x + 2, 1);
  if (x < 10 || x > 11) lcd.write(3);
  if (x == 13) {
    lcd.createChar(0, l_hrt); // Gebroken hart
    lcd.createChar(1, r_hrt);
  }
  delay(200); // Stel hier de gewenste snelheid in
}

void loop() {
  delay(2500);
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart); // Herstel hart
  for (byte Teller = 0; Teller < 21; Teller++) Pijl(Teller);
}

// Angelique.ino voor 24 x 2 display Copyright (2016/2026) Th.M. Hupkens
#include >LiquidCrystal_I2C.h< // Zorg dat je de goede versie hebt!

// Vervang hieronder 0x3F door 0x27 als het display niet werkt
LiquidCrystal_I2C lcd(0x27, 24, 2); // initialiseer op 24 karakters en 2 regels
byte hartje[]  = {10, 31, 31, 14, 4, 0, 0, 0};
byte l_hart[]  = {0, 4, 14, 31, 15, 7, 3, 1};
byte r_hart[]  = {0, 8, 28, 30, 28, 24, 16, 0};
byte l_hrt[]   = {0, 4, 14, 0, 0, 7, 3, 1};
byte r_hrt[]   = {0, 8, 28, 0, 0, 24, 16, 0};
byte pijl[]    = {0, 4, 6, 31, 6, 4, 0, 0};
byte schacht[] = {0, 0, 0, 31, 0, 0, 0, 0, 0};

void setup() {
  // Wire.begin(); // Haal de eerste commentaarstrepen weg als het display niet stabiel is 
  // Wire.setClock(50000); // hier ook en probeer een lagere frequentie (hier 50 kHz)
  lcd.init(); 
  lcd.setBacklight(HIGH);
  lcd.createChar(2, schacht); lcd.createChar(3, pijl);
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart);
  lcd.createChar(7, hartje); lcd.setCursor(0, 0);
  lcd.print("I love "); lcd.write(7); lcd.print(" "); lcd.write(7); 
  lcd.print(" Angelique "); lcd.write(7); lcd.print(" "); lcd.write(7);
  lcd.setCursor(12, 1); lcd.print(char(0)); lcd.print(char(1)) ;
}

void Pijl(byte x) { 
  if (x > 0) {
    lcd.setCursor(x - 1, 1);
    if (x < 13 || x > 14) lcd.print(" ");
  }
  lcd.setCursor(x, 1);
  if (x < 12 || x > 13) lcd.write(2);
  lcd.setCursor(x + 1, 1);
  if (x < 11 || x > 12) lcd.write(2);
  lcd.setCursor(x + 2, 1);
  if (x < 10 || x > 11) lcd.write(3);
  if (x == 13) {
    lcd.createChar(0, l_hrt); // Gebroken hart
    lcd.createChar(1, r_hrt);
  }
  delay(200); // Stel hier de gewenste snelheid in
}

void loop() {
  delay(2500);
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart); // Herstel hart
  for (byte Teller = 0; Teller < 25; Teller++) Pijl(Teller);
}

Angelina's gebroken hart op een mooi 20 × 2 display


// Valentijn.ino met Hangul bibliotheek. Copyright 2020, Th.M. Hupkens
#include <LiquidCrystal_I2C_Hangul.h>
// Gebruik hieronder 0x3F of 0x27; dit hangt van je display af.
LiquidCrystal_I2C_Hangul lcd(0x27, 16, 2); // I2C-adres, letters per regel, aantal regels
byte hartje[]  = {10, 31, 31, 14, 4, 0, 0, 0};
byte l_hart[]  = {0, 4, 14, 31, 15, 7, 3, 1};
byte r_hart[]  = {0, 8, 28, 30, 28, 24, 16, 0};
byte l_hrt[]   = {0, 4, 14, 0, 0, 7, 3, 1};
byte r_hrt[]   = {0, 8, 28, 0, 0, 24, 16, 0};
byte pijl[]    = {0, 4, 6, 31, 6, 4, 0, 0};
byte schacht[] = {0, 0, 0, 31, 0, 0, 0, 0};

void setup() {
  lcd.init(); 
  lcd.backlight();
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart);
  lcd.createChar(2, schacht); lcd.createChar(3, pijl);
  lcd.createChar(7, hartje); lcd.setCursor(0, 0);
  lcd.print("Voor "); lcd.print(char(7)); lcd.print(" Lisa "); lcd.print(char(7));
  lcd.setCursor(8, 1); lcd.write(byte(0)); lcd.write(1);
}

void Pijl(byte x) {
  if (x > 0) {
    lcd.setCursor(x - 1, 1);
    if (x < 9 || x > 10) lcd.print(" ");
  }
  lcd.setCursor(x, 1);
  if (x < 8 || x > 9) lcd.write(2);
  lcd.setCursor(x + 1, 1);
  if (x < 7 || x > 8) lcd.write(2);
  lcd.setCursor(x + 2, 1);
  if (x < 6 || x > 7) lcd.write(3);
  if (x == 9) {
    lcd.createChar(0, l_hrt); // Gebroken hart
    lcd.createChar(1, r_hrt);
  }
  delay(200); // Stel hier de gewenste snelheid in
}

void loop() {
  lcd.createChar(0, l_hart); lcd.createChar(1, r_hart); // Herstel hart
  delay(2500);
  for (byte Teller = 0; Teller < 21; Teller++) Pijl(Teller);
}

Probemen met 2002 en 2402 displays

Deze lange displays hebben soms een "backpack" van buydisplay.com. Deze zijn bekend slecht. Na enige tijd functioneren krijg je ze wellicht niet meer aan de praat of alleen na resetten. Dit kun je oplossen door een weerstand van rond de 5 kΩ te schakelen lussen SDA en VCC en ook een tussen SCL en VCC. Als het display rare tekens gaat tonen verlaag dan de I2C frequentie. In de sketch voor het 24 × 2 display zie je hoe je dat kunt doen (haal de voorste commentaarstrepen weg).

Lichtkrant

In het voorbeeld neem ik van de lange string "Krant" steeds de juiste letters die op de 16 plaatsen passen. Door te beginnen met spaties wordt de regel automatisch schoongemaakt als de krant begint te lopen. Het voordeel is hiervan is dat dit met andere displays, met minimale aanpassingen, ook zal werken. De laatste tekst blijft even staan wegens delay(1000). Kies voor de delays de waardes die jij prettig vindt. Als je de spaties in Krant achter de tekst zet dan krijg je meteen de hele tekst te zien die vervolgens naar links loopt. Kies zelf wat je beter vindt. Voor een 24 × 2 display kun de overal 16 vervangen door 24 en bij een 20 × 2 display door 20.

#include <LiquidCrystal_I2C_Hangul.
#include <LiquidCrystal_I2C.h> // Versie van Frank de Brabander   

// Pas indien nodig het I2C adres (0x27) aan! Kan ook 0x3F zijn
LiquidCrystal_I2C display(0x27, 16, 2);

void setup() {
  display.init(); 
  display.backlight();
  display.print("Hallo allemaal !"); 
}

String Krant = "                Dit is 'n mooie lichtkrant!";
void loop() {
  for (int Teller = 0; Teller <= Krant.length() - 16; Teller++) {
    display.setCursor(0, 1);
    display.print(Krant.substring(Teller, Teller + 16));
    delay(250);
  }
  delay(1000);
}
Hieronder staat een versie met de LiquidCrystal_I2C_Hangul bibliotheek. Ik heb deze getest met verschillende displays, waaronder een 20 × 2 en een 16 × 4 display.

#include <LiquidCrystal_I2C_Hangul.h>
#define L 16 // Dit is het aantal letters per regel dat het display kan weergeven

// Kies hieronder het I2C adres. Dit is 0x27 of 0x3F
LiquidCrystal_I2C_Hangul display(0x27, L, 2);

void setup() {
  display.init();
  display.backlight();
  display.setCursor((L > 16) ? 2 : 0, 0);
  display.print("Hallo allemaal !"); 
}

String Krant = "                Dit is 'n mooie lichtkrant!";
void loop() {
  for (int Teller = 0; Teller <= Krant.length() - L; Teller++) {
    display.setCursor(0, 1);
    display.print(Krant.substring(Teller, Teller + L));
    delay(250);
  }
  delay(1000);
}

Het cryptische (L > 16) ? 2 : 0 betekent: als L groter is dan 16 vul dan 2 in en anders 0, met andere woorden als je een display gebruikt met 16 of minder karakters per regel start dan op positie 0 en anders op positie 2.

Nog een leuke animatie

Deze animatie is oorspronkelijk gemaakt voor de Raspberry Pi Pico door Hylke Blijker. Ik heb de animatie iets veranderd en geschikt gemaakt voor de Arduino.

// Skull.ino
#include <LiquidCrystal_I2C.h> // Versie van Frank de Brabander
LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C-adres (0x27 of 0x3F); 16 tekens per regel; twee regels
byte TT1[8] = {8, 24, 28, 22, 3, 1, 0, 0};
byte TT2[8] = {0, 7, 15, 31, 25, 25, 31, 15};
byte TT3[8] = {0, 28, 30, 31, 19, 19, 31, 30};
byte TT4[8] = {2, 3, 7, 13, 24, 16, 0, 0};
byte BB1[8] = {0, 1, 3, 22, 28, 24, 8, 0};
byte KaakL[8] = {15, 5, 0, 0, 10, 15, 7, 0}; // Linker kaakdeel
byte KaakLDicht[8] = {15, 5, 0, 10, 15, 7, 0, 0};
byte KaakR[8] = {30, 10, 0, 0, 20, 30, 28, 0}; // Rechter kaakdeel
byte KaakRDicht[8] = {30, 10, 0, 20, 30, 28, 0, 0};
byte BB4[8] = {0, 16, 24, 13, 7, 3, 2, 0};

void setup() {
  lcd.init(); 
  lcd.backlight();
  lcd.createChar(0, TT1); lcd.createChar(1, TT2);
  lcd.createChar(2, TT3); lcd.createChar(3, TT4);
  lcd.createChar(4, BB1); lcd.createChar(5, KaakL);
  lcd.createChar(6, KaakR); lcd.createChar(7, BB4);
  lcd.setCursor(0,0); lcd.write( (byte) 0 );
  lcd.print("\x01\x02\x03");
  lcd.setCursor(0, 1); lcd.print("\x04\x05\x06\x07");
  lcd.setCursor(7, 0); lcd.print("\"skull\"");
  lcd.setCursor(6, 1); lcd.print("Uw tekst");
}

void loop() {
  delay(750);
  lcd.createChar(5, KaakLDicht);
  lcd.createChar(6, KaakRDicht);
  delay(750);
  lcd.createChar(5, KaakL);
  lcd.createChar(6, KaakR);
}

// Skull_PCF8574.ino voor 16 x 2 display
#include <LiquidCrystal_PCF8574.h>
LiquidCrystal_PCF8574 lcd(0x27);  // Het adres kan ook 0x3F zijn 

byte TT1[] = {8, 24, 28, 22, 3, 1, 0, 0};
byte TT2[] = {0, 7, 15, 31, 25, 25, 31, 15};
byte TT3[] = {0, 28, 30, 31, 19, 19, 31, 30};
byte TT4[] = {2, 3, 7, 13, 24, 16, 0, 0};
byte BB1[] = {0, 1, 3, 22, 28, 24, 8, 0};
byte KaakL[] = {15, 5, 0, 0, 10, 15, 7, 0}; // Linker kaakdeel
byte KaakLDicht[] = {15, 5, 0, 10, 15, 7, 0, 0};
byte KaakR[] = {30, 10, 0, 0, 20, 30, 28, 0}; // Rechter kaakdeel
byte KaakRDicht[] = {30, 10, 0, 20, 30, 28, 0, 0};
byte BB4[] = {0, 16, 24, 13, 7, 3, 2, 0};

void setup() {
  lcd.begin(16, 2); // Aantal tekens per regel; aantal regels
  lcd.setBacklight(1);
  lcd.createChar(0, TT1); lcd.createChar(1, TT2);
  lcd.createChar(2, TT3); lcd.createChar(3, TT4);
  lcd.createChar(4, BB1); lcd.createChar(5, KaakL);
  lcd.createChar(6, KaakR); lcd.createChar(7, BB4);
  lcd.setCursor(0, 0); lcd.write(0); lcd.print("\x01\x02\x03");
  lcd.setCursor(0, 1); lcd.print("\x04\x05\x06\x07");
  lcd.setCursor(7, 0); lcd.print("\"skull\"");
  lcd.setCursor(6, 1); lcd.print("Uw tekst");
}

void loop() {
  delay(750);
  lcd.createChar(5, KaakLDicht);
  lcd.createChar(6, KaakRDicht);
  delay(750);
  lcd.createChar(5, KaakL);
  lcd.createChar(6, KaakR);
}

In theorie geeft de bibliotheek van Matthias Hertel de rustigste animatie en die kan nog op verschillende manieren verbeterd worden. Het is echter mijn ervaring dat hoe mooi de animatie is vooral afhangt van de kwaliteit van het display. Optimalisaties hebben hooguit een marginaal effect.