LCDs verwenden

Programmieren

Manchmal möchte man Messwerte direkt anzeigen, ohne den seriellen Monitor zu nutzen. In diesem Tutorial lernst du, ein LCD dafür zu nutzen.

Verwenden von LCDs

In diesem Tutorial werden wir ein LCD verwenden, um Spannungswerte anzuzeigen, die wir mit dem Analog-Digital-Wandler von Arduino gemessen haben. Zu diesem Zweck habe ich ein LCD mit 16x2 Zeichen genutzt, das häufig zusammen mit dem Arduino verwendet wird. Aber was ist ein LCD und wie funktioniert es?

LCD steht für Flüssigkristallanzeige (engl. liquid crystal display). Was sind also diese magischen Flüssigkristalle? Ich werde nicht im Detail darauf eingehen, wie sie funktionieren, da dies auch mein Wissen übersteigen würde, aber lass uns einen Blick auf die Grundlagen werfen. LCDs haben eine Hintergrundbeleuchtung, in unserem Fall eine LED-Hintergrundbeleuchtung. Vor dieser LED befinden sich verschiedene Materialien, die das Licht streuen, um eine gleichmäßige Hintergrundbeleuchtung zu erhalten. Der wichtigere Teil ist ein Polarisationsfilter, der die elektromagnetischen Wellen des ausgestrahlten Lichts nur dann durchlässt, wenn sie die richtige Ausrichtung haben. Die Flüssigkristalle wirken ebenfalls als Polarisationsfilter, aber sie sind etwas Besonderes. Durch Anlegen einer Spannung ist es möglich, die Polarisationsrichtung zu ändern. Wenn die Polarisationsrichtung mit der des Polarisationsfilters im Hintergrund übereinstimmt, wird das Licht durchgelassen. Der Pixel leuchtet auf. Ist dies nicht der Fall, bleibt der Pixel dunkel. Als Ergebnis sind wir in der Lage, Bilder zu zeichnen oder Text anzuzeigen, indem wir die Pixel einschalten, die wir benötigen.

Natürlich gibt es auch auf einem monochromen LCD eine Menge Pixel. Auf unserem LCD haben wir 5x8 Pixel pro Zeichen. Insgesamt sind also 1280 Pixel zu steuern. Mit unserem Arduino ist das nicht möglich. Es ist auch zu kompliziert, jedes einzelne Pixel anzusteuern, weil dafür eine riesige Menge an Kabeln erforderlich wäre. Um dies zu lösen, werden die Pixel in Zeilen und Spalten gruppiert. Sie werden nicht alle auf einmal angesteuert, sondern Zeile für Zeile aktualisiert. Das ist immer noch nichts, was wir selbst tun wollen. Aus diesem Grund haben fast alle LCDs einen integrierten Controller, der sich um die Aktualisierung des Bildschirminhalts kümmert. Im Falle des LCDs, das wir in diesem Tutorial verwenden, ist es der HD44780 Controller, der häufig für alphanumerische Punktmatrix-Displays wie unsere genutzt wird. Der Controller nutzt eine parallele Schnittstelle, und wir benötigen immer noch eine Menge Kabel, um Daten an ihn zu senden. Wie das geht, wird in vielen anderen LCD-Tutorials beschrieben. Für dieses Tutorial verwenden wir einen zweiten Controller, um die Anzeige über I2C anzuschließen. Sie finden diese Adapterboards als I2C-Backpack bei vielen verschiedenen Händlern. Mein Modell ist auf dem Bild unten abgebildet. Es verwendet einen PFC8754T-Chip, um die 16 Pins des LCD auf nur 4 Pins zu konvertieren. I2C-Backpack zum Anschluss des LCDs I2C ist ein einfacher serieller Bus für die Kommunikation zwischen verschiedenen Schaltkreiskomponenten. Die Abkürzung steht für Inter IC Communication (IIC). Er wird manchmal auch als Two-Wire-Interface (TWI) bezeichnet, da nur zwei Datenleitungen benötigt werden, eine für das Taktsignal (SCL) und eine für die eigentlichen Daten (SDA). Mit I2C können mehrere Geräte an den Bus angeschlossen werden. In unserem Fall ist das Arduino der Master, der die Taktleitung steuert und für die Initiierung der Kommunikation mit den Slave-Geräten verantwortlich ist. Um mit einem bestimmten Gerät am Bus zu kommunizieren, wird eine Adresse verwendet. Für unser LCD ist diese Adresse 0x27. Wenn du dir unsicher bist, welche Adresse dein I2C-Adapter besitzt, kannst du das Beispielprogramm unter Datei > Beispiele > Draht > i2c_scanner verwenden, um nach Geräten am Bus zu suchen und deren Adressen im seriellen Monitor auszugeben.

Aufbau des Schaltkreises

Schaltkreis mit LCD und Potentiometer

Da wir für unsere Anzeige einen I2C-Adapter verwenden, ist die Verkabelung nicht so komplex. Das LCD benötigt 5V Versorgungsspannung und eine Verbindung zur Masse. Da wir diese auch für unser Potentiometer benötigen, habe ich sie mit dem Breadboard verbunden. Dort habe ich dann die VCC- und GND-Leitungen des LCDs angeschlossen. Die Datenleitungen des I2C-Busses sind mit den SDA und SCL Pins des Arduino Uno verbunden. Der Schleifer des Potentiometers ist mit A0 verbunden, mit dem wir die Spannung messen werden.

Wenn du woanders nachsiehst, wie LCDs oder andere Komponenten über I2C mit dem Arduino Uno verbunden werden, findest du vermutlich auch Pläne, in denen die Datenleitungen mit den Pins A4 und A5 verbunden sind. Das liegt daran, dass die SDA und SCL Anschlüsse auf dem Arduino Uno Board keine separaten Pins auf dem ATMega328P Mikrocontroller sind. Der Anschluss SDA ist direkt mit A4 verbunden. Dasselbe gilt für den SCL, das direkt mit A5 verbunden ist.
Es spielt keine Rolle, ob du die I2C-Komponente mit A4 und A5 oder SDA und SCL verbindest. Aber sei vorsichtig, du kannst nicht die SDA und SCL Pins für I2C Bausteine und gleichzeitig A4 und A5 als analoge Pins verwenden.

Verwendung der LCD-Bibliothek

Es ist nicht einfach, ein Display anzusteuern, und in unserem Fall haben wir zwei Controller dazwischen. Wir müssen Befehle über I2C an das Backpack senden. Die Daten werden dann an den Display-Controller weitergeleitet, der Befehle zur Änderung des Display-Inhalts akzeptiert. Den Code dafür willst du mit großer Wahrscheinlichkeit nicht selbst schreiben. Im letzten Tutorial habe ich gesagt, dass es auch vorprogrammierte Bibliotheken für den Empfang von Befehlen von IR-Fernbedienungen gibt. Dasselbe gilt für viele LCDs. Dieses Mal werden wir eine dieser Bibliotheken verwenden.

Installieren der Bibliothek

Um eine neue Bibliothek zu installieren, musst du den Bibliotheksverwalter der Arduino IDE öffnen. Öffnen des Bibliotheksverwalters

Der Bibliotheksverwalter zeigt eine Liste der bekannten Arduino-Bibliotheken an. Du kannst nach einer bestimmten Bibliothek suchen. In unserem Fall suchen wir nach "LCD I2C". Ich habe für dieses Tutorial die "LiquidCrystal I2C"-Bibliothek ausgewählt. Installieren der "LiquidCrystal_I2C"-Bibliothek

Verwendung der Bibliothek

Wie nutzen wir diese Bibliothek? Um sie zu nutzen, sollten wir uns zunächst einmal ansehen, was eine Bibliothek eigentlich ist. Eine Bibliothek enthält Funktionen und Klassen, die von jemand anderem geschrieben wurden und die wir importieren und in unserem Programm verwenden können. Die Bibliothek enthält eine sogenannte Header-Datei, die alle exportierten Funktionen deklariert, die wir verwenden können. Diese Datei wird vom Compiler benötigt, um zu überprüfen, ob die von uns verwendete Funktion oder Klasse existiert. Du erinnerst dich vielleicht noch daran, dass eine Funktion oberhalb des Codes, der sie verwendet, deklariert werden muss. Das ist der Grund, warum wir die von der Bibliothek deklarierten Funktionen am Anfang unseres Programms einfügen. Um dies zu tun, kannst du die Bibliothek über das Menü Sketch > Bibliothek einbinden auswählen. Die erforderliche #include-Direktive wird dann für dich generiert.

Einbinden der von der Bibliothek definierten Funktionen und Klassen

Um herauszufinden, welche Funktionen eine Bibliothek bietet, musst du in die Dokumentation der Bibliothek schauen. Diese findest du in der Regel auf der Webseite, auf die der im Bibliotheksverwalter angezeigte Bibliothekseintrag verweist. Eine andere Möglichkeit ist, sich die Beispiele anzusehen, die die Bibliothek zur Verfügung stellt, diese werden im Menü Datei > Beispiele hinzugefügt. Bitte beachte, dass nicht alle Bibliotheken Beispiele enthalten. Wenn du bereits Erfahrung mit C++ hast, kannst du auch nach der Header-Datei auf der Festplatte suchen und dir die Funktionen ansehen, die sie exportiert. Für dieses Tutorial gebe ich dir alle relevanten Funktionen und Klassen, die wir benötigen, vor.

Übrigens, was ist eine Klasse? Klassen wurden mit C++ eingeführt und erlauben eine objektorientierte Programmierung. Eine Klasse bündelt Funktionen mit Daten. Die Funktionen, die in diesem Kontext auch als Methoden bezeichnet werden, können die mit der Klasseninstanz verbundenen Daten verwenden. Eine Klasseninstanz wird auch als Objekt bezeichnet. In unserem Fall werden die Adresse und die Größe des Displays im Objekt gespeichert und intern verwendet, um die Befehle an das eigentliche Display und das I2C-Backpack anzupassen. Mach dir keine Sorgen, wenn du das Konzept der Klassen nicht kennst. Es gäbe noch viel mehr über sie zu sagen, aber wir wollen zum jetzigen Zeitpunkt keine eigenen Klassen und Bibliotheken schreiben, und die meisten Arduino-Programmierer werden das auch nie tun. Für uns reicht es aus, zu wissen, wie man sie benutzt.

Unsere LCD-Bibliothek definiert die Klasse LiquidCrystal_I2C. Um eine Instanz zu erzeugen, notieren wir nacheinander die Klasse, den Namen des zu erzeugenden Objekts und die zur Instanziierung der Klasse erforderlichen Parameter.
So sieht das Ganze aus:

LiquidCrystal_I2C lcd(0x27, 16, 2);

Diese Zeile erzeugt das Objekt lcd durch Instantiierung der Klasse LiquidCrystal_I2C mit der Adresse unseres I2C-Backpacks und der Größe unseres LCDs als Parameter. Sobald wir dieses Objekt erzeugt haben, können wir seine Methoden aufrufen. Ich habe eine Liste mit denen erstellt, die wir für dieses Tutorial benötigen. Die Syntax kommt dir vielleicht bekannt vor. Das liegt daran, dass Serial ebenfalls nur ein Objekt ist, es ist jedoch standardmäßig definiert.
Hier sind die Methoden, die wir benutzen werden:

  • lcd.init()
    • Setzt das LCD-Modul zurück und initialisiert es
  • lcd.clear()
    • Löscht den Bildschirminhalt und setzt den Cursor zurück in die Ecke oben-links
  • lcd.backlight()
    • Aktiviert die Hintergrundbeleuchtung
  • lcd.print(value)
    • Zeigt einen Text oder eine Zahl an der Stelle des Cursors auf dem LCD an
    • value: Text oder Zahl die angezeigt werden soll
  • lcd.setCursor(x, y)
    • Ändert die Cursorposition
    • x: Neue Spalte
    • y: Neue Reihe

Schreiben des Codes

Das war eine lange Einführung, jetzt ist es an der Zeit, den Code zu schreiben. Der Code ist ziemlich einfach. In der setup-Prozedur müssen wir das LCD initialisieren und die Hintergrundbeleuchtung einschalten. In der loop-Prozedur messen wir die Spannung an Pin A0, löschen alle alten Werte auf dem LCD und geben den neuen Wert aus.
Hier ist der Code:

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  lcd.init();
  lcd.backlight();
}

void loop() {
  int adc = analogRead(A0);
  
  lcd.clear();
  lcd.print("Voltage A0: ");
  lcd.print(adc);

  delay(500);
}

Ich habe der Messung den Text "Voltage A0" vorangestellt, dies ist nicht ganz korrekt, da ich den Messwert nicht in Volt umgerechnet habe. Wenn du möchtest, kannst du in deinem Code den Messwert in Millivolt umrechnen. Erinnerst du dich, wie das möglich ist? Die Antwort finden Sie im Tutorial zu analogen Eingängen.

Vielleicht hast du bemerkt, dass ich am Ende der " loop"-Prozedur ein delay eingebaut habe. Diese Verzögerung ist notwendig, um zu verhindern, dass der LCD-Inhalt direkt nach der Anzeige wieder gelöscht wird. Die Verzögerung gibt dir die Möglichkeit, den Messwert abzulesen. Wenn du sie weglässt, wird der Text nur schwer lesbar sein.

Ein erstes Ergebnis

Das erste Ergebnis kannst du im untenstehenden Video sehen. Der Text und die Messung werden auf dem Display angezeigt. Es gibt jedoch ein kleines Problem. Beim Aktualisieren des Bildschirminhalts tritt ein geringfügiges Flackern auf. Das liegt daran, dass der LCD-Controller nicht besonders schnell ist und insbesondere das Löschen des LCDs viel Zeit in Anspruch nimmt und auch das Neuzeichnen des LCD-Inhalts nicht sehr schnell ist. Die kurze Zeit bis zum vollständigen Neuzeichnen des LCD-Inhalts merkt man als Flackern. Dagegen können wir nicht viel tun. Der Bildschirm wird immer einige Zeit für einen vollständige Aktualisierung benötigen. Die einzige Lösung besteht darin, nur die Teile des LCD-Inhalts zu aktualisieren, die sich ändern, anstatt alles neu zu zeichnen.

Das Flackern unterbinden

Wie können wir ein partielles Neuzeichnen des Bildschirminhalts umsetzen? Zuerst müssen wir darüber nachdenken, welche Teile des LCD-Inhalts aktualisiert werden müssen. Es ist leicht zu erkennen, dass sich nur der Messwert ändert. Aus diesem Grund können wir den vorangestellten Text direkt in der setup-Prozedur ausgeben.
Um den Messwert zu aktualisieren, können wir den Cursor neu positionieren und den alten Wert überschreiben, ohne den gesamten Bildschirm zu löschen. Dabei gibt es eine Falle: Wenn sich die Anzahl der angezeigten Ziffern ändert, bleiben die alten Ziffern auf dem Bildschirm und verwirren den Benutzer. Um dieses Problem zu beheben, kann der unbenutzte Platz mit Leerzeichen überschrieben werden, die für den Benutzer nicht sichtbar sind. Die Zahl wird mithilfe von Leerzeichen immer auf vier Zeichen aufgefüllt. Der untenstehende Code zeigt, wie dies umgesetzt werden kann. Wenn du möchtest, dass die Zahl rechtsbündig angezeigt wird, kannst du die Leerzeichen auch vor der Zahl ausgeben lassen.

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.print("Voltage A0:");
  lcd.setCursor(12, 0);
}

void loop() {
  int adc = analogRead(A0);
  lcd.print(adc);

  if(adc < 1000) lcd.print(" ");
  if(adc < 100) lcd.print(" ");
  if(adc < 10) lcd.print(" ");

  lcd.setCursor(12, 0);
  delay(500);
}

Endergebnis

Mit dem neuen Code erhalten wir einen flackerfreien Text auf dem LCD-Display. Wenn du möchtest, kannst du auch eine kleinere Verzögerung wählen und öfter messen. Da die LCD-Anzeige nicht mehr vollständig gelöscht wird, haben wir nicht mehr das Problem mit unlesbarem Text, bei zu kleine Verzögerungen.

Vorheriger Beitrag Nächster Beitrag