Motivation

Der parallele Anschluss eines handelsüblichen Zwei- oder Vierzeilen-LCD mit einem HD44780-Controller an einen Microcontroller benötigt eine große Anzahl I/O-Pins, mindesten 3 Steuer- und 4 Datenleitungen, also mindestens 7 Pins.

Bei der Verwendung von nur vier Datenleitungen muss der µC jedes zu übertragende Byte in zwei Nibbles zerlegen. Will man dies vermeiden, kann man alle 8 Datenleitungen nutzen. Hierzu werden dann insgesamt 11 Pins benötigt.

Die Idee ist, die Daten seriell zu übertragen. Hierzu gibt es bereits eine Reihe von Varianten. Elektor hat eine Bauanleitung veröffentlicht, bei der die LCD-Ansteuerung per I²C über ein 4094-Schieberegister vermittelt wird. Andere Anleitungen benutzen andere Schieberegister-Typen. Hier eine Schaltung mit einem 74HC595.

Eine weitere häufig gefundene Methode benutzt einen I²C 8-Bit I/O-Expander (PCF8574). Hier erfolgt die Ansteuerung der einzelnen Pins über den I²C-Bus.

Beide Methoden haben den Nachteil, dass trotz des Einsatzes weiterer Elektronik immer noch die gesamte Steuerleistung beim µC liegt. Mehr noch, die Bits müssen zusätzlich serialisiert werden.

Eine komplett andere Variante benutzt Microcontroller zur Ansteuerung des LCD. Die Controller erhalten die Daten per RS232, I²C oder ähnlich. Es gibt auch separate Adapter zu kaufen, die die Ansteuerung eines LCD per I²C vermitteln.

Die Steuerlogik auf einen separaten µC auszulagern und diesen per einfacher serieller Schnittstelle anzusteuern, wie es in den beiden letzten Applikationen gemacht wird, ist eigentlich die beste Lösung. Der Preis eines kleinen µC unterscheidet sich nicht wesentlich von dem eines PCF8574 und ist nur etwa 1,- € teuer als ein Schieberegister. Jedoch sind die kommerziell erhältlichen seriellen LCDs mindesten 10,- € teuer als ein "nacktes". Hinzu kommt, dass die meisten dieser Systeme nicht meinen Vorstellungen entspricht: es müssen Verarbeitungszeiten berücksichtigt werden, die Kommando-Sequenzen sind kompliziert, die Abmessungen machen das System nicht "integrationsfähig".

Für mein MF70-Umbau-Projekt wollte ich sowieso alle Bedienelemente auf ein großes PCB bringen. Also wird es am sinnvollsten sein, Eigenentwicklung zu betreiben.

Spezifikation

Obligatorische Anforderungen:
  1. Die Ansteuerung erfolgt seriell über RS232 oder I²C.
  2. Alle möglichen Zeichen des Displays, einschließlich der frei definierbaren, sollen über den entsprechenden Code angezeigt werden.
  3. Die (möglichst einfach aufgebauten) Kommandos sollen –wann immer möglich– auf die Standard-C-Escape-Sequenzen gemappt sein (\a (alert), \e (escape), \b (backspace), \t (tabulator), \n (newline), \v (vertikal tabulator), \f (form feed), \r (carriage return)).
  4. Die Übertragung kann mit (nahezu) beliebiger Geschwindigkeit erfolgen. Wartezeiten müssen nicht eingehalten werden.

Optionale Anforderungen:

  1. Rückübertragung der aktuellen Cursorposition.
  2. Rückübertragung des aktuellen Bildschirminhalts.
  3. Sichern und Wiederherstellen des Display-Inhalts ("Zwischenfrage").
  4. Es gibt ein Ton-Signal (Beep, Summer).

Lösung

Prinzip

Prinzipdarstellung
Die obige Abbildung erläutert den prinzipiellen Aufbau des seriellen LCD: Die Daten kommen über die entsprechenden Empfangseinheiten herein. Sowohl bei RS232 als auch bei I²C erfolgt die Übertragung der Daten zu beliebigen Zeiten, also asynchron zum Programmablauf der Steuerlogik. Zur Entkopplung der Empfangslogik von der Steuerlogik dient ein (Ring–) Puffer. So werden Wartezeiten beim Sender wegen langsamer Verarbeitung im Empfänger vermieden.

Die Zeichen werden dem Empfangspuffer entnommen und auf Steuercodes überprüft. Steuercodes werden vom Kommando-Interpreter abgearbeitet.

Der Block "Status-Daten" ist optional. Er dient zur Realisierung der optionalen Anforderungen. Für die Anforderung 5 muss die Cursorpostion mitgeführt werden. Diese kann zwar über den Display-Controller ermittelt werden, allerdings nicht ohne Wartezeiten, was der Anforderung 4 widerspricht. Für die Anforderungen 6 und 7 muss der LCD-Inhalt mitgeführt werden.

Der Zwischenspeicher dient zur Speicherung des LCD-Inhalts bei Realisierung der optionalen Anforderung 4.

Die Rückübertagung der Daten kann auf verschieden Arten gelöst werden. Bei einer I²C-Lösung werden die Daten (Cursorposition und LCD-Inhalt) einfach nacheinander übertragen. Hierbei muss bedacht werden, dass die Rückübertragungsanforderung zu beliebigen Zeiten erfolgen kann und evtl. noch nicht alle gesendeten Daten verarbeitet wurden. Der Sender erwartet aber den Status nach Verarbeitung aller bisher gesendeten Daten. Beinhaltet also der Empfangspuffer noch nicht verarbeite Daten, muss eine "Daten-Ungültig-Kennung" übertragen werden.

Bei der RS232-Variante kann, sofern nur die Rückübertragung der Cursorposition verlangt wird, diese asynchron immer dann gesandt werden, wenn der Empfangspuffer leer ist, ggf. mit beliebiger Wiederholung. Sinnvoller erscheint jedoch die Übertragung auf konkrete Anforderung des Senders durch Übertagung eines entsprechenden Steuercodes. Auf diese Art und Weise kann auch der LCD-Inhalt abgerufen werden.

Steuercodes

Mit darstellbaren Zeichen sind die Codes 1..8 (0x01..0x08), 32..127 (0x20..0x7F), 160…255 (0xA0..0xFF) belegt. Die restlichen Codes stehen als Steuerzeichen zur Verfügung. Die frei programmierbaren Zeichen werden auf die Adressen 0x10..0x17 gemappt. Damit steht 0x07 (0x07, '\a') für einen evtl. Summer zur Verfügung.

Code Bedeutung Anmerkung
07 | 0x07 ('\a') Summer. Optional.
08 | 0x08 ('\b') Löscht Zeile. Das nachfolgende Byte enthält die Zeile (0-basiert). Z.B. "\b0x02" löscht die 3. Zeile. Ist die übergebene Zeile ungültig, wird diese nicht verarbeitet.
09 | 0x09 ('\t') Der Cursor wird absolut positioniert. Das nachfolgende Byte enthält die Position. Höherwertiges Nibble = Zeile (0-basiert), niederwertiges Nibble = Spalte (0-basiert). Z.B. "\t0x25" positioniert in 3. Zeile, 6. Spalte. Ist die übergebene Position ungültig, wird die Cursorposition nicht verändert.
10 | 0x0A ('\n') Der Cursor wird an den Anfang der nächsten Zeile positioniert. Der Rest der aktuellen Zeile wird gelöscht. Befindet sich in der letzten Zeile, verbleibt er dort. Alternative: Der Display-Inhalt wandert eine Zeile aufwärts.
11 | 0x0B ('\v') Der Cursor wird auf den Anfang der ersten Zeile positioniert. Der Display-Inhalt bleibt erhalten.
12 | 0x0C ('\f') Das Display wird gelöscht. Der Cursor wird auf den Anfang der ersten Zeile positioniert.  
13 | 0x0D ('\r') Der Cursor wird an den Anfang der aktuellen Zeile positioniert. Der Rest der aktuellen Zeile wird gelöscht.  
15 | 0x0F Definition der frei belegbaren Zeichen. Es folgen: Nummer des frei definierbaren Zeichens (0..7) und 5 Bytes mit der Pixel-Matrix, am weitesten rechte Spalte zuerst. Ist die übergebene Zeichennummer ungültig, erfolgt keine Speicherung dieser Sequenz. Die 5 Matrix-Bytes werden auf jeden Fall ausgefiltert, kommen also nicht zur Anzeige.
16 | 0x10 Übertrage aktuelle Cursorposition Optional. Höherwertiges Nibble = Zeile (0-basiert), niederwertiges Nibble = Spalte(0-basiert). Wird ebenfalls die Rückübertragung des LCD-Inhalts implementiert, beginnt die Übertragung mit 0x10, dann die Position, dann 0x1F
17 | 0x11 Übertrage aktuellen LCD-Inhalt. Optional. Die Übertragung beginnt mit dem Code 0x11. Danach werden (Zeilen x Spalten) Bytes übertragen. Zum Schluss wird der Code 0x1F übertragen.
18 | 0x12 Der aktuelle LCD-Inhalt wird gesichert. Optional.
19 | 0x13 Der aktuelle LCD-Inhalt wird wiederhergestellt. Optional.
0x10..0x17 Frei programmierbare Zeichen. Gemappt von 0x01..0x07.
 
Für die Darstellung deutscher Umlaute und weiterer auf der Tastatur verfügbarer Sonderzeichen muss eine Code-Konvertierung erfolgen, da die Umlaut-Symbole des Displays einen anderen Code besitzen als im Zeichensatz des AVR-Studios. Leider besitzt der Display-Zeichensatz für die Umlaute keine separaten Symbole für Groß- und für Kleinschreibung. Deshalb werden groß und klein geschriebene Umlaute auf das gleiche Symbol gemappt. Altenativ könnte man die fehlenden Symbole bei den frei belegbaren Symbolen definieren. Einen echten Unterscheid würde man aber nur beim 'Ä' sehen.

Umlaut Mapping
'Ä' (0xC4), 'ä' (0xE4) 0xE1
'Ö' (0xD6), 'ö' (0xF6) 0xEF
'Ü' (0xDC), 'ü' (0xFC) 0xF5
'ß' (0xDF) 0xE2
'µ' (0xB5) 0xE4
'°' (0xB0) 0xDF

Die Zeichen, die durch das Umlaut-Mapping nicht mehr direkt erreichbar sind, können über nicht benutzte Codes erreicht werden:

Zielcode Ersatzcode
0xC4 0x88
0xE4 ('µ') 0x89 (B5)
0xD6 0x8A
0xF6 ('Σ') 0x8B
0xDC 0x8C
0xDF 0x8D
0xB5 0x8E
0xB0 0x8F
 

Implementierungshinweise

Auswahl des Microcontrollers

ATtiny44:

ATtiny2313:

ATmegaX:
Bereits mit den kleinsten ATmega's kann alles gemeinsam realisiert werden. Das RAM ist so groß, dass man auch einen Zwischenspeicher-Stack aufbauen und so den LCD-Inhalt mehrfach sichern könnte.

Software: