LCD Displays am Arduino sind eine praktische Möglichkeit, damit man Daten nicht immer nur über die serielle Schnittstelle ausgeben muss und lassen auch kleine Projekte gleich professioneller aussehen.
Besonders das 1602 Display mit 2 Zeilen à 16 Zeichen ist in fast jedem Arduino Starter Kit zu finden. Diese Displays sind praktisch und kostengünstig, haben jedoch einen Nachteil: Sie werden standardmäßig parallel angesteuert und benötigen dadurch mindestens 6 Digitalpins am Arduino und eventuell auch noch eine Potentiometer am Breadboard.
Das kann problematisch werden, wenn weitere Sensoren oder Module angeschlossen werden sollen. Hinzu kommt der Kabelsalat auf dem Breadboard, der bei komplexeren Projekten schnell unübersichtlich wird.
Die Lösung: Die I²C-Schnittstelle für LCD Displays
Die I²C-Schnittstelle (Inter Integrated Circuit) bietet eine einfache Lösung für dieses Problem. I²C ist ein Protokoll zur seriellen Kommunikation zwischen elektronischen Bauteilen. Es benötigt nur zwei Leitungen und damit nur 2 Digitalpins am Arduino:
- SDA (Serial Data Line) für die Datenübertragung
- SCL (Serial Clock Line) für den Takt
Jedes I²C-Gerät hat eine eindeutige Adresse. Dadurch können über dieselben zwei Pins mehrere Module wie Sensoren oder Displays angesprochen werden.
Für Standard-LCDs gibt es kleine I²C-Adapterplatinen, die einfach an die 16 Anschlusspins des Displays gelötet werden. Diese verwenden meist den PCF8574, einen I/O Expander, der serielle Daten in parallele Signale für das LCD umwandelt. Diese Adapter stellen dann die 4 I²C-Anschlüsse (VCC, GND, SDA, SCL) zur Verfügung. Ein Potentiometer zur Helligkeitssteuerung der Anzeige ist oft noch zusätzlich auf der Platine integriert.
Diese Erweiterungen sind praktisch, machen das Modul jedoch etwas unhandlicher. Aus diesem Grund gibt es inzwischen auch LCD Displays mit integrierter I²C-Schnittstelle, die direkt auf dem Modul verbaut ist und so den gewohnten Formfaktor beibehält.
Anschluss eines I²C-LCD Displays an den Arduino
Der Anschluss eines I²C-LCD Displays an den Arduino UNO ist denkbar einfach:
- VCC an 5V
- GND an GND
- SDA an A4 (Arduino UNO)
- SCL an A5 (Arduino UNO)
Hinweis: Bei anderen Mikrocontrollern können die I²C-Pins variieren.
- Arduino UNO/Nano: SDA an A4, SCL an A5.
- Arduino Micro: SDA an D2, SCL an D3.
- Arduino Mega 2560: SDA an D20, SCL an D21.
- ESP8266 (NodeMCU): SDA an GPIO4 (D2), SCL an GPIO5 (D1).
- ESP32: SDA an GPIO21, SCL an GPIO22.
- Raspberry Pi (Model 3 & 4): SDA an GPIO2 (Pin 3), SCL an GPIO3 (Pin 5).
- STM32 (Blue Pill): SDA an PB7, SCL an PB6.
- Teensy 4.x: SDA an Pin 18, SCL an Pin 19.
LCD Display, Zeichensatz und Sonderzeichen
LCD-Displays wie das 1602 oder 2004 verwenden oft einen HD44780-kompatiblen Controller, der einen festgelegten Zeichensatz (Character Generator ROM, CGROM) integriert hat. Dieser Zeichensatz basiert auf dem erweiterten ASCII-Code und enthält neben den englischen Buchstaben, Zahlen und Symbolen auch einige Sonderzeichen wie Pfeile oder mathematische Symbole. Allerdings fehlen häufig länderspezifische Sonderzeichen, etwa deutsche Umlaute (ä, ö, ü) oder das „ß“.
Für individuelle Anpassungen bietet der Controller einen benutzerdefinierten Zeichensatz (CGRAM), in dem bis zu acht eigene Zeichen definiert werden können. Diese Zeichen werden als 8×5-Pixel-Matrix erstellt und können zur Darstellung von Sonderzeichen oder Symbolen genutzt werden. Wenn jedoch mehr als acht Sonderzeichen benötigt werden, müssen diese dynamisch neu definiert werden, was bei häufiger Nutzung den Code komplexer macht.
Eine sehr gute Library zur Anzeige deutscher Umlaute samt umfangreicher Erklärung:
https://werner.rothschopf.net/202003_arduino_liquid_crystal_umlaute.htm
Einfacher Sekundenzähler auf einem LCD Display
#include <LCDI2C_Multilingual_MCD.h>
LCDI2C_Latin lcd(0x27, 16, 2); // I2C Adresse: 0x27; Display: 16x2
void setup() {
lcd.init(); // Display initialisieren
lcd.backlight(); // Hintergrundbeleuchtung einschalten
lcd.setCursor(0, 0); // Cursor auf erstes Zeichen in erster Zeile
lcd.print("Sekunden:"); // Text in erster Zeile ausgeben
}
void loop() {
unsigned long seconds = millis() / 1000;
lcd.setCursor(0, 1); // Cursor auf erstes Zeichen in zweiter Zeile
lcd.print(seconds); // Zeit seit Start in Sekunden ausgeben
lcd.print("s "); // s für Sekunden und ein paar Leerzeichen zum Überschreiben alter Werte
delay(500); // Leichte Verzögerung, damit nicht dauernd geschrieben wird
}
Code-Sprache: PHP (php)
I²C-Adressen verstehen und ändern
Da alle über I²C angesteuerten Geräte über einen gemeinsamen Datenbus angesprochen werden, benötigt jedes Gerät eine eindeutige Adresse. Die Standardadresse für viele LCD-I²C-Module ist 0x27 (hexadezimal).
Wenn mehrere Displays verwendet werden sollen oder es einen Adresskonflikt mit anderen Geräten gibt, dann müssen unterschiedliche Adressen vergeben werden. Auf der Rückseite des I²C-Moduls befinden sich dazu drei Pins: A0, A1 und A2. Diese Pins steuern die letzten 3 Bits der I²C-Adresse.
- Offen (HIGH): Das Bit ist auf 1 gesetzt.
- Kurzgeschlossen (LOW): Das Bit wird auf 0 gesetzt.
Beispiel (I²C Adressen in 7 Bit)
- A0 geschlossen: Ändert die Adresse von 0x27 (0b0100111) auf 0x26 (0b0100110).
- A1 geschlossen: Ändert die Adresse von 0x27 (0b0100111) auf 0x25 (0b0100101).
- Alle geschlossen: Adresse ändert sich von 0x27 (0b0100111) auf 0x20 (0b0100000).
So lassen sich mehrere Displays mit unterschiedlichen Adressen am selben I²C-Bus betreiben.
Drei Displays gleichzeitig ansteuern
In diesem Beispiel werden drei Displays über den I²C-Bus gleichzeitig angesteuert. Die Displays zeigen unterschiedliche Informationen an.
#include <LCDI2C_Multilingual_MCD.h>
LCDI2C_Latin lcd1(0x25, 16, 2); // I2C Adresse: 0x25; Display: 16x2
LCDI2C_Latin lcd2(0x26, 16, 2); // I2C Adresse: 0x26; Display: 16x2
LCDI2C_Latin lcd3(0x27, 20, 4); // I2C Adresse: 0x27; Display: 20x4
unsigned long lastSecond = 0; // Zwischenspeicher für Fortschrittsbalken
int progressBar = 0; // Aktuelle Länge des Fortschrittbalkens
void setup() {
lcd1.init(); // Display1 initialisieren
lcd1.backlight(); // Hintergrundbeleuchtung einschalten
lcd1.setCursor(0, 0); // Cursor auf erstes Zeichen in erster Zeile
lcd1.print("Sekunden:"); // Text in erster Zeile ausgeben
lcd2.init(); // Display 2 initialisieren
lcd2.backlight();
lcd2.setCursor(0, 0);
lcd2.print("Millisekunden:");
lcd3.init(); // Display 3 initialisieren
lcd3.backlight();
lcd3.setCursor(0, 0);
lcd3.print("Fortschrittsbalken:");
}
void loop() {
unsigned long currentMillis = millis();
unsigned long currentSeconds = currentMillis / 1000;
// Display 1 - aktuelle Zeit in Sekunden
lcd1.setCursor(0, 1); // Cursor auf erstes Zeichen in zweiter Zeile
lcd1.print(currentSeconds); // Zeit seit Start in Sekunden ausgeben
lcd1.print("s"); // sec für Sekunden
// Display 2 - Zeit in Millisekunden
lcd2.setCursor(0, 1);
lcd2.print(currentMillis); // Zeit seit Start in Millisekunden ausgeben
lcd2.print("ms");
// Display 3: Fortschrittsbalken aktualisieren (60 Zeichen pro Minute)
if (lastSecond != currentSeconds) { // Nur aktualisieren, wenn sich der Inhalt geändert hat
progressBar = currentSeconds % 60; // Fortschritt von 0 bis 59
lastSecond = currentSeconds;
// Fortschrittsbalken leeren bei Neuanfang
if (progressBar == 0) {
for (int row = 1; row <= 3; row++) {
lcd3.setCursor(0, row);
lcd3.print(" ");
}
}
// Fortschrittsbalken zeichnen
int row = 1;
int col = 0;
for (int i = 0; i < progressBar; i++) {
lcd3.setCursor(col, row);
lcd3.print("#");
col++;
if (col >= 20) { // Nächste Zeile nach 20 Zeichen
col = 0;
row++;
}
}
}
}
Code-Sprache: PHP (php)
Mit einer I²C-Schnittstelle kann man LCD Displays einfach ansteuern und in jedes Projekt integrieren. Anstatt für jedes Display mehrere Pins zu belegen, genügen zwei Pins (SDA und SCL), um mehrere Displays oder Sensoren zu betreiben. Das ermöglicht komplexe Projekte, ohne sich Gedanken über fehlende GPIO-Pins machen zu müssen.
Dank der verfügbaren Libraries und einfacher Adressänderungen ist die Einrichtung von I²C-LCDs schnell erledigt. Ob ein einzelnes Display oder mehrere Module gleichzeitig – mit I²C sind die Möglichkeiten nahezu unbegrenzt.