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:
- Die Ansteuerung erfolgt seriell über RS232 oder I²C.
- Alle möglichen Zeichen des Displays, einschließlich der frei definierbaren,
sollen über den entsprechenden Code angezeigt werden.
- 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)).
- Die Übertragung kann mit (nahezu) beliebiger Geschwindigkeit erfolgen.
Wartezeiten müssen nicht eingehalten werden.
Optionale Anforderungen:
- Rückübertragung der aktuellen Cursorposition.
- Rückübertragung des aktuellen Bildschirminhalts.
- Sichern und Wiederherstellen des Display-Inhalts ("Zwischenfrage").
- Es gibt ein Ton-Signal (Beep, Summer).
Lösung
Prinzip
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:
- 11 verfügbare I/O-Pins (wenn man auf Umprogrammierung des RESET-Pins
verzichten will). Damit lässt sich das Display im 4-Bit-Modus betreiben.
- USI mit der entweder eine I²C-Bus oder eine RS232-Schnittstelle betreiben
kann. Das zweite Interface müsste über Software implementiert werden.
- Wird nur ein Interface implementiert, stehen 2 Pins für Zusatzaufgaben
zur Verfügung, z.B. für einen Summer und eine LED.
- Das interne RAM hat eine Größe von 256 Byte. Das reicht für einen Ringpuffer
(64 Byte), Status-Daten (65 Byte (Cursor-Position und LCD-Inhalts-Kopie))
und einen Zwischenspeicher von 65 Byte. Es verbleiben noch 63 Byte für lokale
Daten und den Stack. Bei Bedarf kann der Ringpuffer verkleinert werden.
ATtiny2313:
- 17 verfügbare I/O-Pins (wenn man auf Umprogrammierung des RESET-Pins
verzichten will). Damit lässt sich das Display im 8-Bit-Modus betreiben.
- USI mit der entweder eine I²C-Bus-Schnittstelle betreiben kann.
- USART zur gleichzeitigen Implementierung einer RS232-Schnittstelle.
- Es stehen 2 weitere Pins für Zusatzaufgaben zur Verfügung, z.B. für
einen Summer und eine LED.
- Das interne RAM hat nur eine Größe von 128 Byte. Das reicht nur für
einen Ringpuffer. Auf die Implementierung eines Zwischenspeichers oder einer
LCD-Inhalts-Rückmeldung muss verzichtet werden.
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: