Alphanumerische Dotmatrix Flüssigkeitskristallanzeigen (LCD)
Wissenswertes - Ansteuerung - Initialisierung - Vierbitmodus
kommentierter Programmcode in Assembler für ATMEL AVRs
Inhalt:
[00]...zu Verdrahtungshinweisen...
[01]...hier zur Bedeutung der Kontrastspannung...
[02]...zur Beschaltung der LED-Hintergrundbeleuchtung...
[03]...hier zur Erklärung der Steuersignale...
[04]...und hier zur Erläuterung der LCD-Initialisierung...
[05]...hier zur Bedeutung der Initialisierungsschritte...
[06]...hier zu Timingproblemen bei den Controllern - "Busy Flag"...
[07]...zur Zeichenpositionierung...
[08]...zur Erklärung der Verzögerungsschleifen...
[09]...zu signierten Binärzahlen...
[10]...hier nun endlich geht's zum Vierbitmodus...
[11]...und zum vollständigen Initialisierungsprogramm in AVR-ASM...
[12]...und zum Initialisierungsprogramm mit anderer Portbelegung...
[13]...zum zugehörigen Intel-Hex-File nach Assemblierung...
[14]...zu möglichen häufigen, weiteren Störungsursachen...
[15]...und noch zu userdefinierten Charakters...
Einleitung:
Man findet im Internet und auch in einschlägigen Fachforen unzählige Beiträge
mit dem Titel:
"...Bekomme mein LCD nicht zum Laufen....", oder "...Hilfe, die Initialisierung
klappt nicht....", oder "...mein Display zeigt nur schwarze Balken...".
Möchte deswegen ohne Anspruch auf Vollständigkeit hier nochmals auf die
Funktionsweise und Ansteuerung eines LCD und die "tiefere" Bedeutung der
einzelnen LCD-Initialisierungsstufen eingehen.
Es steht zwar (fast) alles in den Datenblättern, die freundlicherweise im Internet zum
Download bereitgestellt werden, aber oft halt nur in Englisch und meistens irgendwie
so unglücklich und mißverständlich formuliert, daß selbst der geübteste Servicetechniker
und erst recht wohl der frischgebackene Elektronikbastler sehr schnell wieder die Lust
am neu erstandenen, leider partout nicht funktionieren wollenden LCDisplay
verlieren könnte. Der Autor dieser Zeilen kennt aus eigener Erfahrung diese Probleme
nur zu gut und möchte hier ein wenig dazu beitragen, den Spaß am Elektronikhobby
wiederherzustellen.
Zunächst einmal etwas Grundsätzliches zur Erklärung der relativ groß erscheinenden
Preisunterschiede bei den handelsüblichen Flüssigkeitskristallanzeigen.
Die Preisvorstellungen variieren nämlich zwischen etwa 1,00 und 35,00 Euro und mehr.
Sehr preiswert sind Displays, die wie eine Siebensegmentanzeige funktionieren.
Ihr Anwendungsbereich ist hauptsächlich auf Uhren oder Digitalvoltmeter beschränkt.
Die einzelnen Segmente können wesentlich einfacher, als dies bei den im folgenden
beschriebenen alphanumerischen LCDs der Fall ist, quasi direkt über spezielle
externe Siebensegmenttreiber-ICs angesteuert werden. Solche billigen LCDs
besitzen aber meistens keine weitere Elektronik und sind für die hier später noch
geplanten Anwendungsbeispiele leider nicht geeignet.
Ganz anders verhält es sich bei den etwas teureren, besagten "echten" alphanumerischen
LCDs. Diese Displays besitzen eine oder mehrere Zeilen, mindestens 8 Stellen mit jeweils
einer aus 5 mal 7 Punkten bestehenden Matrix pro Stelle ( - 5 x 8, wenn das Cursorfeld
mit einbezogen wird - ).
Diese benötigen einen speziellen Controllerchip, der aber meistens auf der Displayplatine
schon fest eingebaut ist, und korrespondieren zwingend mit Mikrocontrollern,
die für diesen Anwendungszweck mit entsprechender Software gefüttert werden müssen.
Bei den LCD-Dotmatrix-Controllern hat sich der Hitachi-HD44780-Standard
durchgesetzt, der einerseits die komplizierte Spannungs-Ansteuerung der einzelnen Pixel
auf dem eigentlichen Display ermöglicht, andererseits bereits ein maskenprogrammiertes
ROM mit dem jeweiligen Zeichenvorrat bietet, der sich nach dem amerikanischen
ASCII-Standard richtet aber auch meistens japanische und koreanische Schrifttypen
nach den Kano-Schriftsatz besitzt, ebenso eine Steuerlogik über verschiedenartige
Register aufweist, womit eine Kommunikation mit den ansteuernden Mikrocontrollern
erst ermöglicht wird.
Gängige andere LCD-Dotmatrix-Controller, wie zum Beispiel Samsung KS0066U
oder Sitronix ST7066 sind vom Prinzip her mit dem Hitachi-HD44780-Standard
kompatibel.
Ein direkter Austausch eines Displays der einen Marke gegen ein solches einer
anderen ist allerdings oft nur nach - wenn auch relativ einfachen - Abänderungen
im Standard-Ansteuerprogramm möglich. - Dazu später mehr.
Kurzum:
Ohne Grundkenntnisse in Hardware und Programmierung hat man demnach kaum
eine Chance, diese LCD-Module auf Anhieb zur Mitarbeit zu bewegen,
da zum Beispiel ein mit einem BASCOM-Compiler
erstelltes Programm oft deswegen nicht funktioniert, weil zur Zeit nicht für alle
LCD-Controllervarianten eine entsprechende Library verfügbar ist.
(Allein deswegen schon wird hier weiter unten zunächst nur in
AVR-Assembler programmiert, was dann in BASCOM unter Umständen als
ASM-Ergänzung eingefügt werden kann.)
Aber "keine Panik auf der Titanik", hat man erst ein bißchen "herumgeschmökert",
stellt sich die gute Laune schnell wieder ein, und man schmunzelt vielleicht sogar
hinterher über die typischen Fallstricke, in die man gerade als "Anfänger" auf diesem
Gebiet nur zu leicht hineingetappt sein könnte.
Alles, was im Umgang mit den Mikrocontrollern verlangt wird, sind nicht etwa
großartige Hilfsmittel, ein aufwendiger Messgerätepark, nein, nur eine gute
Programmiersoftware (zum Beispiel ATMEL Studio4), ein (Windows-)Rechner
- möglichst mit "echter" serieller Schnittstelle -, ein Programmierevaluationsboard
(neuerdings auch mit USB- und Windows-Vista-Unterstützung) und nur ein
wenig Aufmerksamkeit wären absolut angesagt.
Aber nun genug der vielen allgemeinen Worte, jetzt sollte es endlich konkret werden.
Bei der eigentlich sich selbst erklärenden Verdrahtung ist noch
zu beachten, daß sich die Anschlußreihenfolge so ergibt, wie sie zumindest
mit dem Aufdruck der Ziffer "1" auf der Platine beginnend - mit dem Datenblatt
übereinstimmend - eindeutig markiert wurde.
Oft sind hierbei die Anschlüsse für die Hintergrundbeleuchtung versetzt angebracht,
so daß man beim Abzählen der Anschluss-Lötpunkte durcheinander kommen könnte,
bzw. es gibt unter Umständen "unschöne" Kreuzungen am Stecker beziehungsweise
Flachbandkabel hinterher.
Bei einigen Displays, die wahlweise mit oder ohne Hintergrundbeleuchtung
ausgeliefert werden, müssen dann noch eventuell Lötbrücken oder Jumper
korrekt gesetzt werden.
Beispiel für am häufigsten anzutreffende Anschluss-Belegung:
Lötpunktbelegung am Display:
=================================================================
|16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 |
|-LED +LED D7 D6 D5 D4 D3 D2 D1 D0 Ena R/W RS Vee 5V Masse|
=================================================================
( Vee = Kontrastspannung
Vdd bzw. Vcc = + 5 Volt; Vss = 0 Volt, Masse, Ground, GND )
Flachbandkabel-Steckverbinder-Pinbelegung:
=========================
|15 13 11 9 7 5 3 1|
|16 14 12 10 8 6 4 2|
=========================
rot markierte Ader an Pin 1 oder sinngemäß
Stimmt die Hardwareverbindung, ist das Ergebnis der Lötkunst "endlich" erfolgreich mit
Durchgangsprüfer durchgeklingelt, können wir uns jetzt ersten Tests widmen.
Zum Seitenanfang
Die Kontrastspannungseinstellung:
Für die einwandfreie Darstellung der Zeichen auf dem Display ist es
sehr wichtig, daß die Spannung am Eingang Vee mit Hilfe eines Potentiometers
richtig einjustiert wurde.
( Poti 10 Kiloohm mit Serienwiderstand ca. 4,7 Kiloohm nach Vcc,
anderes Poti-Ende direkt an Vss, Schleifer an Vee.)
Nach dem Anlegen der Versorgungsspannung (5 Volt) können bei noch
nicht initialisiertem Display je nach Blickwinkel und Beleuchtung
bei Verstellen des Kontrastes schon schwache Balken sichtbar gemacht
werden. Bleibt diese allererste Test-Einstellung nun so bestehen,
werden bei nachfolgender vollständiger Initialisierung die
darzustellenden Zeichen meist völlig überdeckt.
Häufigster Anlaß für den Hilferuf "...ich sehe nur schwarze Balken..."
ist eben ein zu hoch gedrehter Kontrast. Die ganz einfache Lösung
dieses Problems besteht also im Wiederherunterdrehen des Kontrastes,
bis nur noch ganz schwach Pixel zu erkennen bleiben.
Diese Vee-Spannung liegt meistens nahe bei null Volt, kann aber
- bedingt durch Fertigungstoleranzen - von vorne herein nicht so exakt
bestimmt werden, daß ein Festwiderstand oder eine einfache Verbindung
mit Vss (Masse) im Test genügte.
LCDs unterliegen darüberhinaus stärkerer Umgebungstemperaturabhängigkeit
im laufenden Betrieb, die auch mit NTC-Widerstandsschaltungen in Kombination
mit einem Regeltransistor oder anderweitig über Vee ausgeglichen werden kann.
Einige Displays mit erweitertem Arbeitstemperaturbereich benötigen deswegen
sogar eine negative Kontrast-Spannung, die dann mit separatem IC-Spannungswandler
generiert werden sollte (ICL 7660).
Zum Seitenanfang
Die Hintergrundbeleuchtungs-Spannungsversorgung
hingegen ist nicht so kritisch und kann auch von einer separaten Spannungsquelle aus
erfolgen, da sie in den meisten Fällen keine direkte Verbindung mit der LCD-
Spannungsversorgung hat. So kann man zum Beispiel die Beleuchtung beim
Batteriebetrieb des Gerätes ganz ausgeschaltet lassen und so die Batterien schonen,
ohne das gesamte LCDisplay außer Betrieb nehmen zu müssen.
Wichtig:
Dabei ist zu beachten, daß diese Leuchtdiodenanschlüsse unter keinen Umständen
direkt an die Versorgungsspannung angeschlossen werden sollten,
es muß zwingend eine Maßnahme zur Strombegrenzung getroffen werden.
Dazu wird entweder eine Konstantstromquelle benutzt, oder ein Widerstand von
etwa 68 bis 100 Ohm in Reihe geschaltet, um den Leuchtdiodenstrom zu begrenzen,
der im Bereiche um 100 bis 180 Milliampere liegt. (Genaueres dazu in den Datenblättern.)
Dabei ist es meistens ohne Belang, ob dieser Widerstand in der gemeinsamen Kathoden-
oder Anodenleitung liegt.
Auch läßt sich die Hintergrundbeleuchtung mit Hilfe eines Transistors steuern.
Erfolgt die Steuerung der Helligkeit (Dimmung) über ein Mikrocontrollerportbit,
sollte die Hintergrundbeleuchtung auch über die LCD-Versorgungsspannung versorgt werden.
Dann ist die Entkopplung über Abblockkondensatoren,
wie hier beschrieben, zwingend notwendig.
Zum Seitenanfang
Die einzelnen Bits am LCD-Bus müssen natürlich
richtig beschaltet werden.
Neben den Portbits D0 bis D7 für die eigentliche Datenübertragung
unterscheidet man beim LCD noch eine Reihe von Steuerbits:
RS => Register Select:
Umschaltung des Zugriffs auf Steuerregister oder Zeichendarstellungs-
Puffer-Register
Steht dies Bit auf "low", wird in den Befehlsmodus gewechselt,
das heißt, jetzt werden alle an Portbits D0 bis D7 des LCDs angelegten
Datenbits als Steuerbefehle interpretiert und entweder ins
Befehls-RAM hineingeschrieben,
oder im Lesemodus kann dann die Abfrage der im Display anliegenden
Adress-Counter-Bits, insbesondere des Statusbits, des "Busy-Flags",
erfolgen, was dann wiederum der Mikrocontroller im Programm
weiterverwerten kann.
(Hierbei, beim "Auslesemodus", muß dann, wie bereits angedeutet,
der Eingabeport zum Ausgabeport umfunktioniert werden. -
Aus der Sicht des Mikrocontrollers ist's dann genau umgekehrt -.)
Steht das RS-Bit auf "high", werden anstehende Datenbits (D0 bis D7) als
ASCII-Code aufgefaßt und ins Display-RAM geschrieben.
Aus dem Charakter-RAM bzw. -ROM wird intern nun das dort abgelegte Zeichen
geholt und als Buchstabe, Ziffer, Satzzeichen, Sonderzeichen etc. zur
Darstellung gebracht.
Dieses RS-Bit verlangt nach keiner streng ausgeprägten Timing- und
Impulsform-Bedingung, ist also ein "Current state"-Bit, bei dem es nur
darauf ankommt, ob es im übrigen Zusammenspiel mit den anderen
Steuerbefehlen gesetzt ist oder nicht.
R/WQuer => Read/WriteQuer
Es bestimmt die Datenflussrichtung des Datenports D0 bis D7.
Steht dieses Bit auf "low", fungiert der LCD-Port als Eingang,
es können also Daten und Befehle in das Display hineingeschrieben werden.
Da dies in der Mehrzahl der Anwendungen der Fall ist,
wird dieser Anschluß oft schon standardmäßig auf Masse (Vss) fest verdrahtet.
Sollte aber das "Busy-Flag" abgefragt werden,
muß dieser Anschluß auch an den Mikrocontroller-Steuerport geführt werden.
Steht dieses Bit auf "high" ( und das RS-Bit auf "high" ), kann der momentan
im DDRAM gespeicherte Display-Inhalt oder ( bei RS-Bit auf "low" ) der
Zustand des "Busy-Flags" und die Position des Cursors bzw. Stand des
Adresscounters ausgelesen werden, (allerdings nicht das Programm selbst,
welches sich als Firmware auf dem LCD-Controller befindet).
Dieses R/WQuer-Bit hat keine ausgeprägte Timing-Bedingung,
ist also auch ein "current state"-Bit.
Enable => Es ist das "Daten-gültig"-Bit, das "Übernahme"-Bit.
Dieses Bit ist das kritischste von den Steuerbits, da es nur im eng begrenzten
Rahmen mit fallender Flanke von ca. 25 Nanosekunden seine Funktion erfüllt,
und somit ein "Transition state"-Bit darstellt.
(Bei einem "Transition state"-Bit ist dessen Funktion durch den Wechsel
der Information also hier von "high" auf "low" bestimmt. )
Vor Übernahme der Daten bzw. Steuerbefehle soll es auf "high" gesetzt werden.
Die Daten am Bus (D0 bis D7) müssen vorher für eine gewisse Zeit bereits anliegen.
Der Enable-Impuls soll eine Mindestdauer von 500 Nanosekunden aufweisen,
dann mit den besagten ca. 25 Nanosekunden in der fallenden Flanke
wieder auf "low" gesetzt werden, wobei die Daten am Bus für eine kurze Zeit
danach noch anstehen sollten.
Ebenso müssen RS-Bit und R/W-Quer-Bit auch stabil gesetzt sein.
Dieses Steuerbit ist deswegen so unerläßlich und wichtig, weil (fast) jede
Schreib- oder Leseoperation scheitern muß, wird dieses Bit im Programm
nicht korrekt eingesetzt.
Wie es am besten programmiert wird, folgt weiter unten im Assemblerprogramm-
Beispiel: Enableimpulsgenerierung
Aus dem Datenblatt des HD44780-Controllers kann man auf Seiten 52 / 58
entnehmen, wie die Timingbedingungen für die Steuersignale nun genauer
aussehen:
Zum Seitenanfang
Weitere Problemstellungen ergeben sich aus dem Verständnis des
Initialisierungsprogrammes, der Tatsache, daß (bis auf spezielle Ausnahmen)
alle Schreib- und Lesebefehle unmittelbar von einem Enableimpuls
gefolgt sein müssen, ferner aus dem Verständnis des Vierbitmodus.
Hier zunächst die Initialisierungsbefehle, die vom Mikrocontroller am Anfang an das
Display gesendet werden müssen, damit es überhaupt etwas anzeigt.
Nach dem Einschalten vollführt das Display zwar selbständig einen Power-On-Self-Init,
dieser endet aber mit dem Ausschalten des Displays.
Warum die Hersteller das so gemacht haben, bleibt mir nach wie vor ein Rätsel.
Jedenfalls ist man deswegen nun gezwungen, das Display
nochmals per Software - sprich, mit von außen angelegten Steuerbefehlen -
zu initialisieren.
Die zeitliche Abfolge der Befehle spielt eine Bedeutung, die man oft unterschätzt.
Leider sind die "Control-and-Display-Command"-Listen anders
aufgebaut, als es der Programmverlauf bei der Initialisierung zwingend
verlangt. Diese Listen sind hierarchisch so aufgebaut, daß das die Funktion bestimmende
"High"-Bit der Reihenfolge nach immer weiter in Richtung höherwertiges Bit
aufgelistet wird.
So ergibt sich die Befehlsaufstellung unten:
(Wohlgemerkt - keineswegs die Initialisierungsbefehls-Reihenfolge im Programm.)
Immer RS und RWQuer auf "low", und alle anderen höherwertigen Bits "low", versteht sich.
"X" bedeutet entweder "Don't care", sowohl "low" als
auch "high", oder wird als Funktions-Parameter - siehe weiter unten - noch gesetzt.
=================================================================
Bit-Reihenfolge - Zählrichtung: Funktion - Verarbeitungszeit:
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L L L L L L L H = "Display clear"
DB0 = high => Display clear (Verarbeitungszeit 1,7 Millisekunden)
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L L L L L L H X = "Cursor Home"
DB1 = high => Coursor an Anfangsposition (ca. 1,7 Millisekunden)
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L L L L L H X X = "Entry Mode Set"
DB2 = high => Entry Mode Set (ca. 50 Mikrosekunden)
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L L L L H X X X = "on/off Control"
DB3 = high => Display on/off Control (ca. 50 Mikrosekunden)
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L L L H X X X X = "Shift Set"
DB4 = high => Shift Set (ca. 50 Mikrosekunden)
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L L H X X X X X = "Function Set"
DB5 = high => Function Set (ca. 50 Mikrosekunden)
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L H X X X X X X = "Set CGRAM Add."
DB6 = high => Set CGRAM address (ca. 50 Mikrosekunden)
-----------------------------------------------------------------
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
H X X X X X X X = "Set DDRAM Add."
DB7 = high => Set DDRAM address (ca. 50 Mikrosekunden)
=================================================================
Zum Seitenanfang
Die verschiedenen Parameter bei den Initialisierungsbefehlssätzen:
Entry Mode Set:
DB2 = high
DB1 low = decrement, high = increment
DB0 low = no shift, high = Display is shifted
Wahlmöglichkeit 1:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L L L L H L L = "Entry Mode Set"
Cursor wandert von rechts nach links "decrement/decrease"
Displayinhalt wird nicht geschoben "no shift"
0b00000100 binär
0x04 bzw. $04 hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
Wahlmöglichkeit 2: (wird am häufigsten verwendet)
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L L L L H H L = "Entry Mode Set"
Cursor wandert von links nach rechts "increment/increase"
Displayinhalt wird nicht geschoben "no shift"
0b00000110 binär
0x06 bzw. $06 hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
Wahlmöglichkeit 3:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L L L L H H H = "Entry Mode Set"
Cursor wandert von links nach rechts "increment/increase"
Displayinhalt wird von links nach rechts geschoben "right shift"
0b00000111 binär
0x07 bzw. $07 hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
oder Wahlmöglichkeit 4:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L L L L H L H = "Entry Mode Set"
Cursor wandert von rechts nach links "decrement/decrease"
Displayinhalt wird von rechts nach links geschoben "left shift"
0b00000101 binär
0x05 bzw. $05 hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
Display on/off control:
DB3 = high
DB2 low = display off, high = display on
DB1 low = cursor off, high = cursor on
DB0 low = blinking off, high = blinking on
Am häufigsten angewendete Wahlmöglichkeit:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L L L H H L L = "on/off Control"
Display eingeschaltet, kein Cursor, kein Blinken
0b00001100 binär
0x0C bzw. $0C hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
Shift Set:
DB4 = high
DB3 low = cursor move, high = display shift
DB2 low = left shift, high = right shift
DB1 don't care
DB0 don't care
Am häufigsten angewendete Wahlmöglichkeit:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L L H L L L L = "Shift Set"
Cursor wandert laut Einstellung im "Entry Mode Set"
Displayinhalt wird nicht geschoben "no shift"
0b00010000 binär
0x10 bzw. $10 hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
Function Set:
DB5 = high
DB4 low = 4 bits interface, high = 8 bits interface
DB3 low = 1 line display, high = 2 lines display
DB2 low = 5x7 dots, high = 5x10 dots
DB1 don't care
DB0 don't care
Wahlmöglichkeit zweizeilig, Acht-Bit-Modus:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L H H H L L L = "Function Set"
Acht-Bit-Modus, zwei Zeilen, Standardschrift
0b00111000 binär
0x38 bzw. $38 hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
Wahlmöglichkeit zweizeilig, Vier-Bit-Modus:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
-----------------------------------------------------------------
L L H L H L L L = "Function Set"
Vier-Bit-Modus, zwei Zeilen, Standardschrift
0b00101000 binär
0x28 bzw. $28 hexadezimal (für Programmierung in Assembler)
-----------------------------------------------------------------
Zum Seitenanfang
Bevor ein Initialisierungs-Befehlssatz nun gesendet wird, erfolgt das
Setzen der größten Pause, um den Power-on-Reset des Displays selbst
erst abzuwarten (Einschaltverzögerung).
Die zeitliche Reihenfolge der Initialisierungsbefehle nach Power-on-Reset
und deren "tiefere" Bedeutung:
Numero 1:
Die dreimal hintereinanderzusendenden Hex 30
und Function Set:
Dreimal Hex 30 ist der Reset, der auch als Teil des Function Sets
(Datenlänge-8/4Bit-Zeilenanzahl-Zusatzfeatures (Zeichensatz-Dot-Anzahl)
angesehen werden kann, wobei sich einige Hersteller diese Sequenz
einsparen und direkt schon die Bits für den vollständigen Function Set
setzen. Hier also etwa direkt Hex 38.
Das ist die niedrigste Stufe, die bei der Initialisierung durchlaufen werden muß.
Sollte hinterher im Programm an Zeilenzahl und Datenlänge etwas geändert werden,
geht das nur über den Function Set, da muß genau damit dann wieder von vorne
begonnen werden. Da braucht man auf einmal wieder die dreimal
hintereinanderzusendenden Hex 30 als Reset, weil dann ja kein
Power-On-Self-Reset erfolgt. Also besser, wir lassen diese Sequenz in der
Initialisierungsroutine von vorne herein bestehen, auch, wenn die Hersteller
vielleicht etwas anderes in ihren Produktbeschreibungen angegeben haben sollten.
Numero 2:
Display On/Off Control:
Hier sage ich dem Display, ob es ein- oder ausgeschaltet werden soll, das heißt,
ob das Display den Textpufferinhalt anzeigen soll, oder ob keine Charakters auf dem
Anzeigefeld erscheinen sollen, wobei der Inhalt des Textpuffers natürlich
gespeichert, das Display selbst auch eingeschaltet bleibt.
"On-Off-Control" bezieht sich also nicht auf das An- und Abschalten der gesamten
LCD-Betriebsspannung, sondern nur auf das Anzeigen des Textpufferinhaltes auf
dem Display.
Ferner wird hier festgelegt, ob ich den Cursor als Unterstrich oder als den abwechselnd
hell/dunkel komplementär blinkenden Block dargestellt haben möchte.
(Der Witz ist, nach dem Power-On-Self-Reset initialisiert sich das LCD von selbst auf 8-Bit-,
Cursor nach rechts, eine Zeile [bzw. zwei Zeilen] aber schaltet die Textanzeige "sinnigerweise"
wieder ab.
Also ich brauche diesen Befehlssatz auf jeden Fall.)
Jetzt könnte ich direkt auch, da ich mich entschieden habe,
ob ich einen Cursor dargestellt haben möchte oder nicht, in den Entry Mode Set wechseln.
(Da wird dann noch differenziert, wie herum der Cursor laufen soll, nach rechts oder links
oder ob nach jeder Charaktereingabe der gesamte Display-Puffer-Inhalt geschoben
werden soll.)
Das sollte ich aber nicht tun, bevor nicht der Cursor an den Anfang
gesetzt wurde, und die zufälligen Dots auf dem Display mit dem
Befehlssatz Display Clear in dem DDRAM geputzt wurden.
Numero 3:
Display Clear:
Also vor Entry Mode Set steht der Display-Clear-Befehlssatz,
wobei der Controller beim Display Clear meistens anfängt,
wesentlich langsamer zu werden, da das gesamte Display-RAM
mit Leerzeichen vollgeschrieben wird (Space-Code).
Daher sollte man ihm eine größere Pause vergönnen,
bevor man in den Entry Mode Set wechselt.
Bei einigen Displays wird zugleich mit dem Ausführen dieses Lösch-Befehls
auch das Auto-Increment wieder gesetzt. Sollte das nicht gewünscht werden,
kann man das wiederum im nun folgenden "Entry Mode Set" leicht
korrigieren.
Numero 4:
Entry Mode Set:
Cursor Laufrichtung und Schieben/Scrollen des gesamten Displayinhaltes
festlegen.
Mit dem Increment/Decrement-Bit wird festgelegt, ob der Cursor,
nachdem ein Zeichen in den Textpuffer geschrieben worden ist, rechts
oder links davon zur nächsten Position springen soll.
Mit dem Shift-Bit wird dann festgelegt, ob der Cursor nach dem
Schreibbefehl auf dem Textpuffer und dem Display wandern soll oder nicht.
Wird hierbei "Shift" gesetzt, wandert der Cursor zwar immer noch im
Textpuffer, gleichzeitig verschiebt sich dann aber auch dieser Bereich um eine
Stelle, so daß der Cursor stillzustehen scheint, währenddessen der Text
im Display wandert.
-----
Die weiteren Befehle Hex02 für "Cursor Home", also Cursor an Anfang
Zeile 1, links oben, ohne Display-Inhalt zu löschen
oder der Shift-Set sind vorerst nicht unbedingt nötig, das sind Befehlssätze,
die für die Programmierung hinterher unter Umständen wichtig sind,
also auch später aus dem laufenden Programm heraus an beliebiger Stelle
zur Erzielung bestimmter Effekte aufgerufen werden können, aber zum jetzigen
Zeitpunkt für die Initialisierungsroutine nicht unbedingt gebraucht werden.
"Cursor Home" (Hex 02) bewirkt auch, daß der vom Display angezeigte
Anteil des Textpuffers, falls dieser einmal verschoben worden sein sollte,
wieder auf den Anfang des Textpuffers springt.
Zum Seitenanfang
- Bemerkung: Ganz schön verwirrend - aber kein Problem-
Probieren geht über Studieren. Es folgen noch Bildchen hier in Kürze für
verschiedene Initialisierungsmöglichkeiten, was da so im Einzelnen
passieren kann, auch scheinbar oder tatsächlich "Widersprüchliches". -
Man sieht, es kann durchaus zu widersprüchlichen Einstellungen kommen,
wenn zum Beispiel im "Entry Mode Set" Laufrichtungen entgegengesetzt
zum "Shift Set" eingerichtet werden. Das Display verhält sich dann
unter Umständen ganz eigenartig, flimmert etc.
Auch wird nur einmal nach Aufruf des "Shift"-Befehlssatzes der gesamte
Displayinhalt um eine Stelle (Adresse) nach rechts oder links verschoben.
Es entsteht so noch nicht automatisch eine Laufschrift, wie es die Bezeichnung
"Shift" vielleicht vermuten lassen könnte, dazu muß dieser "Shift Set"
wiederholt im weiteren Programmverlauf - nach erfolgreicher Initialisierung -
vorgesehen werden, wenn sich schon ein Text im DDRAM befindet.
Das Laufschriftphänomen bewirkt die "Shift"-Einstellung im "Entry Mode Set"
nämlich besser, aber dauerhaft nach entsprechender Initialisierung.
Fallstrick:
Ist ein Zeichen standardmäßig oder durch Positionierung an den Anfang
gesetzt worden und wird nun nach links geschoben, sieht man eventuell
nur noch den blinkenden Cursor oder garnichts, falls die entsprechende
Einstellung den Cursor abgewählt hatte. Man läßt den "Shift Set" also
am besten zunächst ganz fort bei der "Standardinitialisierungs-Routine".
*1) Dazu ein Zusatzprogramm unten.
Und ein kleines Video dazu.
Zum Seitenanfang
Timingproblematik:
Der eigentliche Initialisierungscode scheint mir dagegen überhaupt
nicht unterschiedlich zu sein bei den Controllern HD44780 und
KS0066 bzw. ST7066 etc..
Dennoch gibt es hie und da wesentliche Abweichnungen in den
Verarbeitungszeiten, folglich in den Pausen, die zwischen
den einzelnen Befehlen des Ansteuerprogrammes eingeschoben
werden müssen - gerade sehr wichtig bei der Initialisierung -.
Die Verarbeitungsgeschwindigkeit beim Display ist fast
ausnahmslos geringer als die Befehlsfolge des angeschlossenen
Mikrocontrollers.
Zum Vergleich: Ein typisches LCD arbeitet mit einer Taktfrequenz
von ca. 125 bis 350 Kilohertz, die meisten Mikrocontroller in diesem
Zusammenhang zwischen 1 bis 16 Megahertz.
Man sieht sofort, daß eine Kommunikation so nicht ohne gewisse
Probleme vonstatten gehen kann.
Bei den LCDs hat man auf eine "hardwaremäßige" Lösung dieses
Kommunikationsproblems - wie von der parallelen
Centronics-Druckerschnittstelle her bekannt - verzichtet.
Bei der Druckerschnittstelle wird ja das "Daten-Gültig-Bit"
mit "Acknowledge-Bit" bezeichnet; beim LCD entspricht das dem
"Enable-Bit". Standardmäßig ist bei dieser Druckerschnittstelle aber
darüberhinaus auch ein "Busy"-Anschluß-Pin vorgesehen,
worüber dem PC signalisiert wird, wann der Drucker wieder bereit
ist, ein neues Zeichen zu verarbeiten.
Ausnahmslos alle LCDisplays bieten nun zwar auch eine vergleichbare
Datenfluß-Kontrollmöglichkeit an, allerdings nur als zeitweise spezielle
Deklaration eines der Datenport-Bits D0 bis D7. Das "ominöse" Bit D7
zeigt durch "High"-Level bei jeder Schreib- oder Leseoperation an,
ob das Display noch mit der Befehlsausführung beschäftigt ist.
"Busy" ist folglich nicht als Extra-Anschluß-Pin herausgeführt,
blockiert auch nicht automatisch immer das Bus-Bit D7, sondern
kann nur als sogenanntes "Busy-Flag" durch ein entsprechend
eingerichtetes Ansteuerprogramm sinnvoll verwertet werden.
Dabei soll der weitere Programmverlauf so lange gestoppt werden, bis
sich das "Busy-Flag" nach der "internen Operation" wieder von selbst
auf "low" zurückgesetzt hat.
Es wird zwar in den Datenblättern immer behauptet,
daß sich ohne Abfrage dieses "Busy-Flags" die dort angegebenen
mittleren Befehlsausführungszeiten drastisch verlängerten,
dennoch würde ich zunächst ohne programmseitige Abfrage
des "Busy-Flags" arbeiten, da dies sonst mit weiteren
Schwierigkeiten verbunden ist, insofern, weil dann die Eingänge
des Displays zu Ausgängen umprogrammiert werden müssen
(- und entsprechend umgekehrt aus der Sicht des
Mikrocontrollers -), was bei Fehlern im Test-Programm zu
Datenkollision und unter Umständen Zerstörung des Displays
und des verwendeten Mikrocontrollers führt.
Ferner kann dieses Status-Bit ganz am Anfang der Initialisierungs-
routine gar nicht zur Kontrolle der Verarbeitungsgeschwindigkeit
herangezogen werden, da es dann ständig auf "high" steht.
Diesem Umstand müsste das Initialisierungs-Programm dann
auch noch gerecht werden.
Es wird darüber hinaus davon berichtet, daß trotz vorgesehener
Busy-Flag-Abfrage die Displayansteuerungsprogramme gelegentlich
Probleme bereiten können.
Das liegt zum einen daran, daß trotzalledem oft noch eine Latenzzeit
nach dem Rücksetzen des "Busy-Flags" von einigen Nanosekunden
verbleibt, was man nur durch Einfügen von No-Operation-Befehlen
(NOP) an geeigneter Stelle im Programm kompensieren kann.
Zum andern kommt es oft - wie beim unten angegebenen
Programmabschnitt für eine Busy-Flag-Abfrage -
wegen des bei jedem Enableimpuls weiterzählenden
Adresscounters zur unschönen Erscheinung, daß der Cursor wandert,
ohne daß ein Zeichen angezeigt wird, beziehungsweise einzelne Zeichen
manchmal wie durch Zufall nicht an der Stelle angezeigt werden,
wo sie laut Programm eigentlich hingehören.
Dem Datenblatt des HD44780 zufolge ist das Senden von
Enable-Impulsen bei der Busy-Flag-Abfrage aber vorgesehen.
Siehe auch hier: (Datenblatt HD44780, Seite 33)
(Busy-Flag-Abfrage im Vierbitmodus)
Ob dies ein Fehler in der Dokumentation ist, kann nicht beurteilt
werden, jedenfalls konnte hier bei einem Test diese unerwünschte
Cursorwanderung bzw. falsche Zeichenpositionierung durch Weglassen
der Enableimpulse bei der Busy-Flag-Abfrage abgestellt werden.
Es reichte also völlig aus, das RS-Bit auf "low" und das R/WQuer-Bit
auf "high" zu setzen, um dann das Bit D7 auf dem Bus per Polling
auslesen zu können.
Als einfache Lösung dieser in zahllosen Beiträgen immer wieder
geschilderten Timingprobleme sollten extra Verzögerungsschleifen
im Programm vorgesehen werden, deren Werte einmal vom tatsächlich
verwendeten Displaycontroller - sei er nun voll mit dem
Hitachi-Standard kompatibel oder nicht - zum andern
hauptsächlich von der verwendeten Mikrocontroller-Taktfrequenz
abhängen.
Man tut also - wie bereits angedeutet - gut daran, zunächst einmal
ohne Busyflagabfrage mit relativ groß erscheinenden, geschätzten
Pausen zwischen den einzelnen Befehlen zu arbeiten, die dann
bei erfolgreicher Initialisierung in weiteren Versuchen sukzessive
immer kürzer gewählt werden sollten, bis man Werte erreicht,
die einerseits zur Einsparung von Programmlaufzeit so klein
wie möglich sind, andererseits das Display noch hinreichend sicher
funktionieren lassen.
Eine andere Möglichkeit, die Pausen im Programm lang genug zu gestalten,
besteht darin, den Mikrocontroller im Testlauf zunächst mit einem Quarz
niedrigerer Megahertzzahl zu takten, beziehungsweise die Fuses so zu setzen,
daß ein Prescaler den Prozessortakt entsprechend reduziert. Später kann
man dies ja wieder rückgängig machen, sollte sich zeigen, daß es nicht notwendig
ist. Ein wenig Experimentierfreude ist hier durchaus angesagt.
Empfehlenswert ist die Programmierung von mindestens drei unterschiedlich langen
Pausen, von etwa 5 Millisekunden, 30 bis 50 Millisekunden und in etwa 500 bis 1000
Millisekunden Dauer. Einmal zu groß gewählte Pausen schaden dem Display
nicht, so zeitkritisch ist die Initialisierungsroutine glücklicherweise nun auch
wieder nicht.
Zum Seitenanfang
Eine weitere, mögliche Störungsquelle ist in der Impulsverschleifung,
auch gerade in derjenigen des Enable-Bits zu sehen,
daher sollte die Leitungslänge zwischen Mikrocontroller-Port
und Display-Bus nicht länger als ca. 10 Zentimeter betragen.
Ist dies aus Layout-Gründen nicht realisierbar,
müssen Maßnahmen zur Impulsregenerierung getroffen,
bzw. Pufferstufen unmittelbar vor das Display gesetzt werden.
Die Spannungsversorgung (Vcc) darf nicht (über Transistor ferngesteuert)
abgeschaltet werden, wenn noch Verbindungen zum ansteuernden
Mikrocontroller, der eventuell sogar eine separate Spannungsquelle aufweist,
bestehen, da sonst die Maximalbedingungen für Pegel an den Ports
über- bzw. unterschritten werden könnten, was zur Zerstörung der MOS-FETs
am Bus führen kann.
Ein sogenanntes "Back-Powering" über die diversen Port-Anschlüsse ist auf
jeden Fall zu vermeiden, vor allem, wenn im Vierbitmodus schon bestimmte
Portbits eindeutig auf Vcc oder Vss definiert verdrahtet wurden.
Da das Display im Betrieb einen doch sehr niedrigen Strombedarf hat,
können unter Umständen diese "parasitären" Ströme über die Ports bei sonst
abgeklemmten Anschlüssen Vcc bzw. Vss schon ausreichen, um das Display
funktionieren zu lassen. Das nehmen einem diese Porttransistoren auf Dauer
aber sehr übel. Schließlich führt dies zur drastischen Verkürzung der
Lebensdauer des Controllers. ( Sogar eine kurzzeitige Verpolung der Spannungen
Vcc und Vss kann für ein paar Sekunden gerade noch gut gehen,
das sollte aber auf jeden Fall vermieden werden.)
Eine Entkopplung der Speisespannung durch geeignete
Abblockkondensatoren unmittelbar an den LCD-Versorgungsspannungs-
anschlüssen und dem Kontrastspannungsanschluss gegen Vss ist ratsam.
Durch diese Maßnahme wird eine eventuelle Verkopplung von Steuerteil
und Display-Anzeigen-Teil wirksam verhindert.
Im Versuchsaufbau wurden Kondensatoren von 0,1 Mikrofarad
bis 10 Mikrofarad getestet. Größere Kondensatoren könnten unter Umständen
die Power-On-Self-Reset-Bedingung empfindlich stören.
zurück zu Hintergrundbeleuchtung
Eine Initialisierung kann dann als zumindest teilweise erfolgreich angesehen
werden, wenn nach dem Einschalten auf wenigstens einer Zeilenlänge kräftige,
kontrastreiche Balken erscheinen, die auf jeden Fall nach kurzer Zeit wieder
verschwinden sollten. ( - MCU und Display an gleicher 5 Volt-
Versorgungsspannung, bzw. nach Reset der MCUnit, wenn beide Komponenten
bereits an der Versorgungsspannung liegen - )
Video Initialisierung (1) hier
Ist die Intensität der Balken ständig gleich stark, verschwinden diese nicht,
ist eventuell - wie schon gesagt - der Displaykontrast zu hoch eingestellt,
dieser müßte dann erst zurückgedreht werden, bis nur noch ganz schwache
Balken zu sehen sind.
Kommen hier, also kurz nach dem Anlegen der Betriebsspannung,
oder kurz darauf "Hieroglyphen" oder undefinierbare, "vermurkste"
Zufallscharakters, ist etwas Fehlerhaftes ins Display-RAM geschrieben worden,
bzw. im Programm der MCU wurde nur der Enableimpuls gesendet ohne
vorherigen definierten Port-Inhalt oder Daten vom "falschen" "Temporär"-
Ausgangsregister. Ein typisches Pixelmuster wäre dann ein "negativer"
Bindestrich oder irgendein Zeichen des Kano-Schriftsatzes, also aus dem
ASCII-Code über dem "normalen" Zeichensatzmuster des Charakter-ROMs.
Bei falscher Portbelegung oder bei fehlerhafter "swap-"Befehlsfolge und
verdreht eingegebenen Argumenten in den "andi-" und "ori-"Strings
kommt es gerade beim Vierbit-Modus zu solchen Effekten.
So erscheint oft systematisch statt der erwarteten Zeichenfolge "Text",
dann vielleicht "EVçG", wenn die Argumente fehlerhaft "geswappt" wurden.
Also: ASCII-Code hex 54, hex 65, hex 78, hex 74
dann: hex 45, hex 56, hex 87, hex 47
Beim Initialisierungsprogramm mit anderer Portbelegung
ist nun eine solche "abweichende" Portbelegung bewußt gewählt worden.
Dabei ist zu beachten, daß die allerersten Steuerzeichen in
"geswappter" Form direkt eingegeben werden: also anstelle von
hex 30 dann hex 03., bzw. statt hex 20 dann hex 02.
Würde anders verfahren, bliebe die Initialisierung dort schon hängen.
Des weiteren gehört nun dort das "Swappen" bereits an den Anfang der
Ausgaberoutinen und wird nicht als zweiter Schritt ausgeführt.
Das ist deswegen wichtig, weil der Controller stets das "höherwertige"
Daten-Nibble zuerst verlangt. Kommen die Nibble vertauscht an, wird
das Byte hinterher verdreht zusammengesetzt und auch so verarbeitet
mit dem keineswegs erwünschten Zufalls-Effekt.
Wieder ein kleines Video dazu.
Sollten Zeichen bereits direkt nach Anlegen der Betriebsspannung bzw.
Reset der MCU auf dem Display erscheinen, wurde der
Initialisierungsbefehlssatz "Display Clear" nicht korrekt ausgeführt,
oder die Pause dahinter war leider immer noch nicht ausreichend lang
genug bemessen.
Das DDRAM besitzt nämlich die eigentlich durchaus begrüßenswerte
Eigenschaft, auch nach Abschalten der Betriebsspannung die zuletzt
eingegebenen Zeichen so lange zu speichern, bis es nach Wiedereinschalten
und mit "Display Clear" in der nun hoffentlich fehlerfreien
Initialisierungsroutine mit Leerzeichen überschrieben wird.
Der vom Display ausgeführte Power-On-Self-Init löscht "kurioserweise"
nicht das DDRAM, wenn schon einmal etwas hineingeschrieben worden ist.
[ Übrigens, da die "Display Clear"-Befehlsausführung im Programmverlauf
später oftmals noch benötigt wird, aber manchmal doch zu lange dauert
und damit zu Problemen mit Interruptroutinen führt, können auch durch
gezieltes Setzen von ein paar Leerzeichen (Hex 20) diese Zeichen nach
vorausgegangenem Positionierungskommando gelöscht werden. ]
- Das nur zu den mit am häufigsten auftretenden Initialisierungsfehlern,
die sich durch kleine Korrekturen im Programm leicht beheben lassen. -
Manchmal eilt bei bestimmten Displays der Cursor einige Stellen vor,
bis der Text erscheint, oder der Text wird nur dargestellt, wenn er
in einer Endlosschleife zum wiederholten Male vom Programm übergeben
wird. Hier ist leider aller Wahrscheinlichkeit nach das Display defekt.
Das kann an der Firmware liegen, wenn das Zusammenspiel der internen
Register mit dem Addresscounter etc. nicht einwandfrei funktioniert.
Oder ein Widerstand zur Takt-Oszillatorkalibrierung wurde schlicht
und ergreifend beim Herstellungsprozeß vergessen einzulöten.
Leider ist es bei bereits eingebaut gewesenen und daran gelöteten Teilen
dann nicht mehr so einfach, das defekte Display beim Händler
umtauschen zu können.
Ohne nun alle empfehlenswerten Marken hier auflisten zu müssen, sollen
nur einige Erwähnung finden:
Die Elektronic Assembly DOG-M-Serie ist IMHO empfehlenswert.
Schon allein deswegen, weil diese Displays in eine Fassung gesteckt
werden können - ohne Löterei.
Eine Hintergrundbeleuchtung kann so jederzeit nachgerüstet und
ausgetauscht werden, falls sie mal ihren Geist aufgegeben haben sollte
nach ca. 100000 Stunden.
Auch bieten einige Displays dieser Serie neben dem
Acht- und Vierbitmodus auch noch einen 2-Draht-Modus an, worüber ich
mir allerdings (noch) nicht klar bin, wie hier die Initialisierung dann hernach
aussehen sollte. (Datenblatt !)
Das hier verwendete 16x2-Display mit Beleuchtung zeigt die oben
beschriebenen Fehler auch nicht und hat als Härtetestkandidat klaglos
alle "Misshandlungen" hier geduldig über sich ergehen lassen,
ist somit auch ohne mir bekannt gewordene Einschränkungen voll
zu empfehlen: Displaytech 162F.
Zum Seitenanfang
Die Positionierung (Locating) des anzuzeigenden Zeichens
auf eine ganz bestimmte Spalte und Zeile kann über den Befehlsmodus direkt
programmiert werden.
Den in den Datenblättern dazu angegebenen Adressenzuweisungen ist dann noch
das Bit 7 "high" hinzuzuaddieren. Das ist auch eine potenzielle Fehlerquelle.
Wir erinnern uns:
DB7 = high => Set DDRAM address mit Verarbeitungszeit 50 Mikrosekunden
Der Adressbereich eines 16-stelligen, zweizeiligen Displays wird in Datenblättern
meistens so angegeben:
Stelle: 1 bis 16
--------------------------
Zeile 1: hex 00 bis hex 0F
Zeile 2: hex 40 bis hex 4F
Also muß der Steuercode dafür wie folgt aussehen:
Stelle: 1 bis 16
--------------------------
Zeile 1: hex 80 bis hex 8F
Zeile 2: hex C0 bis hex CF
(Dazu muß noch in den Befehlsmodus gewechselt werden, RS-Bit auf "low",
Steuercode für Positionierung zuerst gesendet, dann der Charakter im
Datenübernahmemodus - RS-Bit auf "high" - hineingeschrieben werden.)
Alle auf ein "positioniertes" Zeichen folgende werden auch dann noch rechts bzw.
links daneben geschrieben, wurde vorher der Cursor abgewählt.
(Die Adressenincrement- und -decrement-Einstellung erfolgt ja - wie gesagt -
unabhängig von der Erscheinungsform des Cursors selbst im "Entry-Mode-Set".)
So wird vermieden, daß für zusammenhängende Textbestandteile jedesmal
für jedes einzelne Zeichen separat eine Positionierungsangabe programmiert
werden muss.
Eine ausgesprochene Erleichterung für das Programmieren und Entlastung
des ansteuernden Mikrocontrollers.
Allerdings können die Positionierungen an beliebiger Stelle im Adressbereich
gesetzt werden, dann beginnt der Text genau dort, wie es die Adressenangabe
vorsieht.
So kann man beispielsweise ständig sich ändernde Messwertergebnisse an einer
ganz bestimmten Stelle positionieren und den dazugehörigen "feststehenden"
Legendentext mit nur einer Positionierungs-Angabe drei Stellen davor setzen.
"Fließtexte" werden in der Regel dann durch "Festtexte" mit definierter
Positionsangabe überschrieben, bzw. verschwinden dann, wenn ein Charakter
mit vorausgegangener Positionierungsangabe auf dieselbe Stelle geschrieben
wird, wo vorher schon etwas stand.
Man kann, wenn man so will, ein "unerwünschtes" Adressenincrement bzw.
-decrement mit einer Positionierung immer auf dieselbe Stelle ganz gezielt
austricksen. Das Adressenincrement/-decrement läßt sich nämlich keineswegs
abstellen, es gibt nur die Wahlmöglichkeit zwischen "increment" oder
"decrement", kein "no step", oder wie man das nennen wollte.
Video: Zeit-/Messwert-Programm
Zur Sicherheit sollte aber hierbei ein Leerzeichen (hex 20) vor jedem neu
darzustellenden Zeichen gesendet werden. Dann ist der DDRAM-Inhalt
an dieser Stelle ganz unzweifelhaft vorher gelöscht.
Eine AVR-Assembler-Befehlsfoge dafür könnte dann in etwa so aussehen:
ldi temp, 0xC0 ; Positionierung auf Anfang Zeile 2
rcall kommando
ldi temp, 0x20 ; Senden eines Leerzeichens
rcall datenuebernahme
ldi temp, 0xC0 ; Positionierung auf Anfang Zeile 2
rcall kommando
ldi temp, 0x30 ; Senden der Ziffer "0"
rcall datenuebernahme
rcall verzoegerung3 ; Pause zwischen den Zeichen
ldi temp, 0xC0 ; Positionierung auf Anfang Zeile 2
rcall kommando
ldi temp, 0x20 ; Senden eines Leerzeichens
rcall datenuebernahme
ldi temp, 0xC0 ; Positionierung auf Anfang Zeile 2
rcall kommando
ldi temp, 0x31 ; Senden der Ziffer "1" auf dieselbe Stelle
rcall datenuebernahme
rcall verzoegerung3 ; Pause zwischen den Zeichen
; etc. Ziffern 2 bis 9 sinngemäß
Es erscheint dann eine rasche Ziffernfolge, vergleichbar einer
Zehntelsekundendarstellung bei einer Stoppuhr immer auf einer Stelle im Display.
Würden keine Positionierungsangaben gesetzt, erschiene die Ziffernfolge
fortlaufend hintereinander auf dem Display.
Bei Adressenincrement also:
0 1 2 3 4 5 6 7 8 9
und bei Adressendecrement:
0
?
Ja, richtig, denn hierbei wird erst der "nicht darstellbare" Adressbereich
von "hinten" vollgeschrieben.
Wollte man nun die Ziffernfolge 9 8 7 6 5 4 3 2 1 0 darstellen im
Adressendecrement, müßte nämlich ganz am Anfang eine
Positionierungsangabe gesetzt werden (hex C9).
(Bei Adressendecrement erscheinen also Texte "spiegelverkehrt".)
Gegebenenfalls muß nochmals ein "Display-Clear"-Befehl für Ordnung sorgen, da
auch nach Abschalten der Betriebsspannung o h n e Initialisierung mit "Display Clear"
bzw. fehlerhafter Initialisierung die vorher eingegebenen und angezeigten Zeichen
gespeichert bleiben. (Das kann manchmal auch zu einiger Verwirrung führen, vor allem,
wenn man mit "Bigfoot" oder selbst erstellten Charakters arbeitet, die sich über mehrere
Spalten und Zeilen erstrecken.)
Auf die Programmierung von selbst erstellbaren Characters möchte ich hier
aber nicht eingehen, sondern weiter unten. hier klicken
Da auch LCDs mit "nur" 16 Spalten Controller verwenden, die ursprünglich für
40 Spalten ausgelegt sind, gibt es für die Darstellung von "Fließtexten" die
weitere Schwierigkeit, daß der Text nicht automatisch nach Terminalart am Ende der
jeweiligen Zeile umgebrochen wird. Es wird vielmehr auch der nicht darstellbare
Adressbereich zunächst vollgeschrieben, erst dann erscheint der weitere Text auf
Anfang Zeile 2. Ebenso wird im Schiebe-Modus, bei "Display Shift enabled",
immer der gesamte Adressbereich, sowohl in Zeile 1 als auch in der zum
Controllerbereich gehörenden Folgezeile verschoben.
Datenblätter geben hier genauere Auskunft, ebenso darüber, wie die diesbezüglichen
Verhältnisse bei vier- und mehrzeiligen Displays nun genau sind. Derartige Displays
besitzen unter Umständen mehrere Controller, die jeweils für sich nach einer
Initialisierungsroutine verlangen.
Ein vierzeiliges LCDisplay besitzt oftmals zwei Controller.
Nun wird häufig nicht etwa die Zeilenzuordnung fortlaufend, sondern
überlappend gewählt, das heißt, Controller 1 bedient Zeile 1 und Zeile 3,
Controller 2 die Zeile 2 und Zeile 4. Da nicht selten für beide Controller derselbe
Adresscode verwendet wird, erfolgt dann die Übernahme der Positionierungsbefehle
für die entsprechende Zeile über den dem jeweiligen Controller zugeordneten
Enable-Impuls.
Also, zum Beispiel:
hex C0, Enable1, setzt das Zeichen auf Anfang Zeile 3,
hex C0, Enable2, auf Anfang Zeile 4.
Zum Seitenanfang
Userdefinierte Charakters:
Bei den meisten LCDs können die im ASCII-Fernschreibcode als
"Verbindungsstatus"- und "Steuerzeichen" eigentlich hierbei unbenutzt
gebliebenen Codes hex 00 bis hex 07 als freie Adressen zur
Zwischenspeicherung von selbst definierbaren Zeichen verwendet werden.
Das CharakterRAM besitzt also auf den ASCII-Codes hex 00 bis hex 07
acht freie Speicherpositionen, denen man jeweils Charakters fest zuordnen
kann, die auf der 5x7-Dotmatrix pixelweise selbst erstellt wurden.
(Wenn man den Cursorbereich dazurechnet, sind es sogar acht Zeilen.)
Dazu sind pro Zeichen (nach Setzen des Steuerbefehls) noch insgesamt acht
Schreiboperationen nötig.
Zunächst sendet man im Kommandomodus (RS auf "low") den
Steuerbefehlssatz:
DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
L H X X X X X X = "Set CGRAM Add."
DB6 = high => Set CGRAM address (ca. 50 Mikrosekunden)
ldi temp, 0x40
rcall kommando
und legt so erst einmal das erste neu zu erstellende Zeichen
auf den ASCII-Code hex 00 fest.
Nun werden die zugehörigen Pixelwerte Zeile für Zeile im
Datenübernahmemodus (RS auf "high") insgesamt achtmal
hintereinander gesendet, wobei der Controller automatisch
mit jedem Ladebefehl und Enableimpuls jeweils eine Zeile
weiterschaltet, bis alle acht Zeilen eines "neuen" Zeichens
vervollständigt sind.
(Adressen-Increment ist natürlich im "Entry Mode Set" vorher
gesetzt worden, oder bei einigen Displays durch das
"Display Clear" alleine schon - wie oben bereits erwähnt -.)
Bei der Pixelung einer 5x7-(5x8-)Dotmatrix ist die Bitanzahl
demnach auf 5 Bits pro Zeile beschränkt,
die noch verbliebenen höherwertgen Bits werden beim
Programmieren bis zu acht Bit Breite dann mit Nullen aufgefüllt.
Das Pixel am äußersten rechten Rand besitzt die niedrigstwertige
Bitposition.
("Low" für Pixel ausgeschaltet, nicht dargestellt,
"high" für Pixel eingeschaltet, dargestellt.)
Ein waagerechter Strich, bestehend aus 5 Pixeln, weist also den
Code hex 1F auf.
Ein Eurozeichen könnte - frei definiert - beispielsweise so aussehen:
Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0; Binärschreibweise:
L L L L H H H L 0b00001110
L L L H L L L H 0b00010001
L L L H H H L L 0b00011100
L L L H L L L L 0b00010000
L L L H H H L L 0b00011100
L L L H L L L H 0b00010001
L L L L H H H L 0b00001110
---------------------------------------
L L L L L L L L 0b00000000 (sonst für
Cursordarstellung reserviert)
und in Hexadezimalschreibweise:
hex 0E, hex 11, hex 1C, hex 10, hex 1C, hex 11, hex 0E,
Cursorposition leer: hex 00
Sollen die Zeichen nun direkt dargestellt werden, muß zunächst eine
Positionierungsangabe vorausgehen, damit aus dem
"Programmiermodus" herausgesprungen wird.
Erst dann kann im Datenübernahmemodus - wie auch bei einem
"normalen" Text - der entsprechende ASCII-Code hex 00 bis hex 07
gesendet werden. Auch später im Programm empfiehlt sich, vor jedem
userdefinierten Charakter eine präzise Positionierungsangabe zu setzen.
Bei Anwendung des sogenannten "Bigfoot"-Charaktersets ist dies sogar
zwingend erforderlich, sonst verrutschen womöglich einzelne Segmente
des ganz groß erscheinenden "Siebensegment"-Ziffern-Satzes, vor allem
bei vier- und mehrzeiligen Displays mit mehreren Controllern.
Für die Programmierung weiterer Charakters ist nun nicht einfach die
Addition einer "Eins" pro ASCII-Adresse zum Steuercode hex 40 möglich,
es muß immer eine "Acht" hinzuaddiert werden.
Also:
ASCII Code hex 00 = > Set Character generating ROM Address hex 40
ASCII Code hex 01 = > Set Character generating ROM Address hex 48
ASCII Code hex 02 = > Set Character generating ROM Address hex 50
ASCII Code hex 03 = > Set Character generating ROM Address hex 58
ASCII Code hex 04 = > Set Character generating ROM Address hex 60
ASCII Code hex 05 = > Set Character generating ROM Address hex 68
ASCII Code hex 06 = > Set Character generating ROM Address hex 70
ASCII Code hex 07 = > Set Character generating ROM Address hex 78
Mehr geht so nicht, da wir uns sonst schon im Steuerbefehlssatz
DB7 = high => Set DDRAM address (ca. 50 Mikrosekunden)
befinden würden.
Diese userdefinierten Charakters müssen bei Programmbeginn jedesmal
neu geladen werden, da sie nach Abschalten der Betriebsspannung zunächst
zwar gespeichert bleiben, aber durch den "Display Clear"-Befehlssatz in der
Standard-Initialisierungsroutine dann wieder gelöscht werden.
(Bei bestimmten Display-Arrangements kann mit weiteren externen
Baugruppen ein dauerhaftes Speichern und Wieder-Löschen userdefinierter
Charakters erzielt werden. Beispielsweise bei Displays, die sich über
eine RS232-Schnittstelle mit für das Terminalprogramm schreib- und
lesbaren "Escape-Sequenzen" ansprechen lassen. Die Display-Controller
selbst besitzen diesen Steuercode nicht.)
Ein vollständiges Testprogramm
hierfür ist unten angegeben.
Und noch ein Bildchen dazu:
Zum Seitenanfang
Vierbitmodus:
Wer eigentlich die Idee dazu hatte, kann ich gar nicht sagen,
jedenfalls ist es eine gute Möglichkeit, am Mikrocontroller Portbits einzusparen,
ohne wesentliche Nachteile in Kauf nehmen zu müssen in puncto Verarbeitungsgeschwindigkeit.
Da die LCD-Ausgabe als solche immerhin schlechterdings die langsamste Schleife
im Programmablauf darstellt, spielen ein paar Mikrosekunden mehr oder weniger kaum eine Rolle.
Fast ausnahmslos alle LCDs bieten den Vierbitmodus an.
Dabei wird die standardmäßige Datenbusbreite von acht Bit halbiert.
Besser gesagt, es werden nach wie vor acht Bits gesendet und empfangen,
jedoch sequentiell in zwei Blöcken zu je vier Bits hintereinander.
Definiert wurde, daß die niederwertigsten Bits,
also Bit0 bis Bit3 dann als "don't care"-Bits ignoriert werden,
gemeinsam über Entkoppelwiderstände also fest mit Vdd oder Vss verbunden werden können,
nur die oberen vier Anschlüsse für Bits Bit4 bis Bit7 zum Datentransfer genutzt werden.
Die meisten Controller sind so eingerichtet,
daß sie zunächst die vier oberen Datenbits erwarten,
diese sollten also durch das Programm zuerst gesendet werden,
dann wird ein Enableimpuls erwartet, jetzt in Folge die Vierergruppe
mit den niederwertigen Bits, wiederum ein Enableimpuls,
dann wird im Display selbst das vollständige Byte in der richtigen Zusammensetzung
weiterverarbeitet.
Dieser Modus gilt sowohl für das Senden von Daten und Befehlen an das Display,
als auch genauso für das Auslesen und das Abfragen des "Busy-Flags" und des Adressenzählers.
Verständnisschwierigkeiten gibt es hier schon in der Initialisierungsroutine
im Einrichten des Vierbitmodus, beziehungsweise im Wechsel vom Achtbit- in den Vierbitmodus.
Standardmäßig befindet sich das Display nach dem Power-on-Reset im Achtbitmodus.
Also, wie können dann ganz am Anfang der Initialisierungsroutine
acht-Bit-breite Befehle gesendet werden, wenn nur noch vier Anschlüsse vorgesehen sind ?
Ganz einfach:
Der Code ist schon intelligent genug, bei dem Function Set, wie oben beschrieben,
nur "High"-Signale im Bereich der oberen Vierergruppe zu verwenden.
Die unteren vier Bit liegen
verdrahtungsmäßig sowieso meistens (über Widerstände) schon auf "low",
bzw. sie werden vom Controller eh wie "don't care"-Bits behandelt,
also ignoriert.
Am Anfang der Initialisierungsroutine steht das Senden von dreimal hintereinander Hex 30.
Das bedeutet, daß das Display in den Achtbitmodus gefahren wird.
Die 3 steht ja schon in der Gruppe der höherwertigen Bits.
Gibt also keine Probleme, diese zu senden mit nur vier Anschlüssen.
Nur, man muß jetzt jedesmal noch einen Enableimpuls senden.
Erst jetzt sollte in den Vierbitmodus gewechselt werden.
Dazu sendet man Hex 20, also auch die 2 steht schon in der Gruppe der höherwertigen Bits.
Jetzt wird noch ein Enableimpuls gesendet.
Und nun erst kommt der Wechsel in der Art und Weise, wie weitere Befehle
an das Display zur Weiterverarbeitung am sinnvollsten übergeben werden sollten.
Man kann natürlich für die Initialisierungsroutine die acht-bit-breiten Argumente
nach High- and Low-Nibble (Nibble = Gruppe von vier Bits) spiegeln,
übergeben und danach jeweils einen Enableimpuls programmieren. Da der Mikrocontroller nur
acht-Bit-breite Befehle laden kann, würde dann aus einem LCD-Befehlssatz einmal in
Achtbit-Modus-Sendepraxis:
ldi temp, 0b00101000 oder in Hexadezimalschreibweise $28
out datenport, temp
=> nur ein Enableimpuls
dann beispielsweise in Vierbit-Modus-Sendepraxis:
ldi temp, 0b00101000 oder hexadezimal $28 "high Nibble"
out datenport, temp => Enableimpuls
ldi temp, 0b10000010 oder hexadezimal $82 "low Nibble"
out datenport, temp => Enableimpuls
So ist es auch in einigen Datenblättern beschrieben.
Und das ist komplett falsch.
Richtig muß es lauten:
ldi temp, 0b00100000 oder hexadezimal $20 "high Nibble"
out datenport, temp => Enableimpuls
ldi temp, 0b10000000 oder hexadezimal $80 "low Nibble"
out datenport, temp => Enableimpuls,
denn die unteren 4 Bits müssen ausgeblendet, also im Programm zunächst auf null ("low")
gesetzt werden. Damit diese unteren vier Portbits nun auch anderweitig belegt werden,
zum Beispiel zur Übertragung für das RS- und Enable-Bit auch ausgenutzt werden können,
werden die unteren Nibble in der boolschen Algebra zunächst mit dem UND-Befehl
ausgeblendet (ANDI), anschließend der Port mit dem ODER-Befehl (ORI) maskiert.
Dies wird am besten mit Ausgaberoutinen bewerkstelligt, die ohnehin später noch
gebraucht werden, immer, wenn etwas ans Display gesendet bzw. aus ihm ausgelesen
werden soll.
Ab jetzt bietet es sich also in der Initialisierungsroutine geradezu von selbst an,
daß nach jedem geladenen Befehlsargument, das wie ein Achtbitmodusbefehl aussieht,
also nach wie vor acht Bits enthält, hier beim weiter unten angegebenen
Programmierbeispiel in das Label "Kommando" gesprungen wird,
damit die Enableimpulse nicht noch extra programmiert werden müssen.
Diese Impulse werden jetzt vom Unterprogramm "Kommando" wiederum direkt im
weiteren Unterprogrammaufruf des Labels "Enable" automatisch ausgeführt.
Die Enableimpulse müssen nämlich jetzt, nachdem das
Display auf den Vierbitmodus gesprungen ist, nach jedem Nibble gesendet werden,
das hieße doppelt so oft wie im Achtbitmodus.
Die Steuerbefehle zur Initialisierung des Displays sind also im Acht- wie Vierbitmodus
völlig identisch, werden im Vierbitmodus nur nacheinander übertragen.
Es gibt, wenn man so will, nur einen Achtbit-Steuerbefehlssatz.
Bei den ATMEL AVRs befinden wir uns in der glücklichen Lage,
daß es schon einen Befehl gibt, der Vierergruppen von Bits in der
korrekten Zusammensetzung verschiebt,
also ohne die Reihenfolge der Bits in sich nochmals zu verdrehen,
was in einigen Datenblättern zur LCD-Initialisierung zu weiterer,
nun kompletten Verwirrung führte.
Dieser Befehl heißt SWAP
Eine Vierergruppe von Bits wird nun, wie schon gesagt, mit Nibble bezeichnet.
Bei SWAP-Nibble-Anweisung wird ein Byte, also eine Achtergruppe,
eine volle Portbreite, so verdreht,
daß die Bits danach in folgender Reihenfolge stehen:
ldi r16, 0b11010001
Vorher: Bit7; Bit6; Bit5; Bit4; Bit3; Bit2; Bit1; Bit0
swap r16
Nachher: Bit3; Bit2; Bit1; Bit0; Bit7; Bit6; Bit5; Bit4
out portb, r16 ;(0b00011101)
Das Assemblercode-Beispiel für das Senden eines Kommandos im Vierbitmodus
sieht nun so aus:
Dabei muß das RS-Bit auf "low" gesetzt sein, also das Bit,
welches für das Senden dieses Registerselect-Bits vorgesehen wurde,
frei wählbar auf den noch unbenutzt verbliebenen
vier unteren Bits denselben Ports nun, hier willkürlich gewählt Bit0:
Dabei bietet die AVR-Familie durch sbi/cbi-Befehl
das direkte Portbit-Setzen/Löschen an.
kommando: ; Schreiben von Steuerbefehlen ins Display
push temp ; Sichern von temp auf Stapel
cbi daten, 0 ; Setzen des RS-Bits auf "low" fuer Befehlsmodus
mov temp1, temp ; Kopie des Wertes fuer weitere;
; ; Operation unten
andi temp, 0b11110000 ; unteres Nibble ausblenden
ori temp, 0b00000000 ; Port maskieren, RS auf "low"
out daten, temp ; Ausgabe oberes Nibble auf D4-D7
cbi daten, 0 ; Setzen des RS-Bits auf "low" fuer Befehlsmodus
rcall enable ; Aufruf des Unterprogramms fuer Enable-Impuls
nop
nop
swap temp1 ; Nibble-Vertauschen, Wert aus temp;
; ; kopiert, siehe oben
andi temp1, 0b11110000 ; unteres Nibble ausblenden
ori temp1, 0b00000000 ; Port maskieren, RS auf "low"
out daten, temp1 ; Ausgabe unteres Nibble auf D4-D7
rcall enable ; Aufruf des Unterprogramms fuer Enable-Impuls
rcall verzoegerung2
pop temp ; Zurückschreiben von temp vom Stapel
ret
Das Enable-Bit liegt nun willkürlich gewählt auf
einem noch unbenutzt gebliebenen Portbit, hier Bit1,
dabei bietet die AVR-Familie durch sbi/cbi-Befehl das direkte Portbit-Setzen/Löschen an.
Das zwingend notwendige Label/Unterprogramm für das Senden des
"Daten-Gültig"-Impulses sieht nun so aus:
enable: ; Unterprogramm fuer das Senden des "Daten-gueltig"-Impulses
nop ; keine Operation, kuerzeste Pause
sbi daten, 1 ; Enable-Bit auf "high" setzen
nop ; Zeitverzoegerung mindestens
nop ; 1000 (500) Nanosekunden laut Datenblatt
nop ; lieber ein paar "nops" mehr
nop ; spendieren
nop ; keine Operation, kuerzeste Pause
nop ; keine Operation, kuerzeste Pause
nop ; keine Operation, kuerzeste Pause
cbi daten, 1 ; Enable-Bit auf "low" zurücksetzen
nop ; keine Operation, kuerzeste Pause
ret ; Rueckkehr zum Befehl, der auf den Befehl des
; Unterprogrammaufrufes folgt
;-----------------
zurück zu "Steuersignalen"
Zum Seitenanfang
Verzögerungsschleifen:
Eine weitere Verständnisschwierigkeit tritt bei der Programmierung
der leider notwendigen Pausen mit Hilfe von Verzögerungsschleifen auf.
Eigens einen Timer dafür zu verwenden,
stellt eine unnötige Verschwendung von Resourcen dar.
Da sie nur selten und auch meistens nur im Zusammenhang mit der Initialisierung
und Ausgabe auf dem LCD verwendet werden,
sollte man sie getrost separat programmieren.
Eine einfache Verzögerungsschleife besteht aus einem Abzählvorgang eines Registers,
der Prozessortakte "verbrät".
rcall = Unterprogrammaufruf von Verzögerungsschleife
load = Laden eines Registers mit definiertem Wert (0 bis 255)
count = Aufwärts/Abwärts-Zählen bzw. Vergrößern/Verringern des Registerinhalts um eins
compare = Vergleichen und bedingter Sprung zurück nach "count", wenn Registerinhalt
gleich/ungleich Vergleichswert
ret = Rückkehr zum dem das Unterprogramm aufrufenden Befehl folgenden Befehl
Die Prozessortakte, die verbraucht werden, hängen von dem Ladewert der Register,
der Anzahl der Befehle, die nacheinander jeweils durchlaufen werden und von
den Befehlsausführungszeiten ab, wobei der Sprungbefehl einmal einen, einmal
zwei Takte benötigt, je nach Zustand der Bedingung. Auch wird die Schleife einmal nicht
voll durchlaufen, so daß vom Ladewert ein Takt abgezogen werden muß.
Die oben im Flussdiagramm angegebene Marke "compare" wird bei AVR-Assembler-
Sprache mit zwei Befehlen realisiert, einmal mit dem Befehl für das Vergleichen eines
Registerwertes mit einer Konstanten, dann mit dem unmittlbar darauffolgenden bedingten
Sprungbefehl.
Der hier verwendete Sprungbefehl BRLT testet das
Signierte-Binärzahlen-Flag.
Die Befehlsfolge unter Verwendung des BRLT-Befehls ist beim Codebeispiel unten
eng mit dem Dekrementieren, also Registerinhalt-Verringern um eins,
ebenso mit dem Comparewert 1 des CPI-Befehlsoperanden verknüpft.
[ Signierte Binärzahlen => Binärzahlen,
bei denen definitionsgemäß das höchstwertige Bit das Vorzeichen enthält,
da man keine Extra-Zeichen für "plus" und "minus" bei Binärzahlen kennt.
Eine "Null" an höchster Stelle bedeutet dabei eine positiv signierte,
eine "Eins" eine negativ signierte Binärzahl. Man erhält einen Vorzeichenwechsel,
indem man, ausgehend von einer positiven Binärzahl, alle Nullen zu Einsen umkehrt.
Das nennt man das "Einerkomplement".
Die "negative" Binärzahl ist - dann dezimal gesehen - um einen Wert größer.
Addiert man jetzt zur "negativen" Binärzahl noch eine "Eins", erhält man im
sogenannten Zweierkomplement eine Zahl, die zahlenmäßig der Dezimalzahl
entspricht, nur mit umgekehrten Vorzeichen.
Entsprechendes gilt für Umwandlung von negativ signierten Binärzahlen in positiv signierte.
Beispiel: 0000 = +0
Einerkomplement: 1111 = -1
Zweierkomplement: + 1 10000 = -0
Reichen die Stellen, bedingt durch Übertrag nun nicht mehr aus, fügt man noch
eine höchstwertige Stelle an. Die Schwierigkeit besteht darin, dass nur aus dem übrigen
Zusammenhang deutlich werden kann, wie nun die Binärzahlen zu interpretieren sind,
denn im obigen Beispiel könnte die Folge 1111 auch dezimal 255 , beziehungsweise die
Folge 10000 auch dezimal 256 bedeuten. Für "unseren" BRLT-Befehl heißt dies, dass
dieser automatisch das höchstwertige Bit als Signierbit auffasst und auch dann das
Statusregister-Signierbit-Flag verwerten kann. ]
Zum Seitenanfang
Es sind aber bei den Verzögerungsschleifen auch andere bedingte Sprungbefehle anwendbar.
Man kann nun mehrere Schleifen so ineinander verschachteln, dass sich die Werte multiplizieren.
;*******************************************
; (Dateiname: verzoegerung.asm
; kann als spezifische INCLUDE- *.asm-Datei benutzt werden.
; Erklärung siehe weiter unten.)
;
verzoegerung: ; bei 4 MHz dauert ein Taktimpuls 0,25 Mikrosekunden
; ; Anzahl Prozessortakte:
; ; 2+[4x(255-1)+3]x[5x(5-1)]+7=20389; x 0,25=5,09725 ms
;
wdr ; Watchdogtimer-Reset siehe unten
ldi zeit, 0xFF ; Laden von Wert 255 in "zeit"(8-Bit-Register)
ldi zeit1, 0x05 ; Laden von Wert 5 in "zeit1"
rjmp verzoegerungs_schleife ; Direktsprung nach Label "verzoegerungs_schleife"
verzoegerungs_schleife:
dec zeit ; Verringern "zeit" um eins
cpi zeit, 1 ; Vergleich "zeit" mit Wert eins
brlt verzoegerungs_schleife ; Fortfahren nur, wenn bei Registerwert
; ; eins von "zeit" CPI das Vorzeichen wechselt,
; ; sonst Sprung zu "verzoegerungs_schleife"
ldi zeit, 0xFF ; Nachladen von "zeit" auf 255, ohne
; ; Vorzeichenwechsel
dec zeit1 ; Verringern "zeit1" um eins
cpi zeit1, 1 ; Vergleich "zeit1" mit Wert eins
brlt verzoegerungs_schleife ; Fortfahren nur, wenn bei Registerwert
; ; eins von "zeit1" CPI das Vorzeichen wechselt,
; ; sonst Sprung zu "verzoegerungs_schleife"
ret ; Rueckkehr zur naechsten Adresse hinter
; Unterprogrammaufruf durch RCALL
Verzoegerung2: ;2+[4x(255-1)+3]x[5x(32-1)]+7= 39,49 ms bei 4 MHz
wdr
ldi zeit, 0xFF
ldi zeit1, 0x20
Verzoegerungs_Schleife2:
dec zeit
cpi zeit, 1
brlt Verzoegerungs_Schleife2
ldi zeit, 0xFF
dec zeit1
cpi zeit1, 1
brlt Verzoegerungs_Schleife2
ret
Verzoegerung3:
wdr
ldi zeit, 0xFF
ldi zeit1, 0xFF
ldi zeit2, 0xA0
Verzoegerungs_Schleife3:
dec zeit
cpi zeit, 1
brlt Verzoegerungs_Schleife3
ldi zeit, 0xFF
dec zeit1
cpi zeit1, 1
brlt Verzoegerungs_Schleife3
dec zeit2
cpi zeit2, 1
brlt Verzoegerungs_Schleife3
ret
; FIN
;---------------------------------------------------
Es bleibt noch zu bemerken, daß mit AVR-MCUs keine größeren
Verzögerungszeiten als etwa 1000 Millisekunden bei Verwendung
von derartigen Loops in der Regel erzielt werden können.
Es kommt dabei nicht selten zum "Hängenbleiben",
weil die CPU-Auslastung dann immer 100 % beträgt,
das "Prefetching" von Befehlen, welches den auf den momentan abgearbeiteten
Befehl folgenden Befehl bereits aufruft, während jener noch nicht vollständig
durchgeführt wurde, hierbei oftmals nicht mehr einwandfrei
funktionieren kann, da so immer wieder gleichartige Befehle in rascher Folge
über für den Controller "längeren" Zeitraum aufgerufen werden.
Das "Prefetching" läuft sozusagen nicht selten dann heiß.
Auch sollte eine WDR-Marke vor jeder Verzoegerungsschleife
den eventuell programmierten Watchdog-Timer resetten,
damit die Zeitschleife nicht zum Reset des Programms bei Erreichen des
Watchdogtimeouts führt. Mithin ist das größte Watchdogresetintervall
auch bei 1000 Millisekunden angesiedelt.
Besser ist es also, mehrere kleinere Zeitschleifen hintereinander zu
programmieren, um die MCUnit von vorne herein nicht in diese
Grenzbereiche zu fahren.
Zum Seitenanfang
Die vollständige LCD-Initialisierungsroutine im Vierbitmodus (16x2 LCD)
mit den Unterprogramm-Routinen zum Senden der Steuerkommandos im
Befehlsmodus und der darzustellenden Zeichen im Datenübertragungsmodus
(ohne Abfrage des "Busy-Flags) sieht in Assembler-Programmiersprache
bei Verwendung eines der beliebtesten Mikrocontroller aus der
Atmel-AVRisc-Mikrocontroller-Serie "ATTiny2313" nun folgendermaßen aus:
Kenntnisse über MC-typenspezifische Include-Files, Assembler-Direktiven,
Portzuweisungen und Stapel-Initialisierung, notwendig zum Funktionieren
der Unterprogramm-Aufrufe (Label-Sprünge), werden als bekannt vorausgesetzt.
Hinweise zur Programmierung:
Eventuell immer wiederkehrende, kürzere Routinen und
einzelne Programmabschnitte werden oft in separaten
Dateien vorprogrammiert. Diese sind für sich genommen
nicht immer isoliert lauffähig.
So sind auch viele Programmbibliotheken (Libraries) aufgebaut,
die sich aus einer Fülle von Einzelprogrammen mit kryptischen
Dateinamen zusammensetzen, die dann erst für den jeweiligen
Anwendungszweck miteinander zu einem lauffähigen
Gesamtprogramm kombiniert werden sollten.
Beim Schreiben des "Hauptprogrammes" werden Verweise auf
solche *.asm-Files mit Hilfe der .INCLUDE-Anweisung ans
Programmende gesetzt und somit hinzugefügt, ohne dass diese
Programmteile in Gänze - explizit ausformuliert - wieder
vollständig in den Programmeditor einkopiert werden müssen. -
Alle aktuell einzubindenden Include-Dateien sollten sich
beim Assembliervorgang auf dem gleichen PC-HDD-
Laufwerks-Pfad befinden (einschließlich der *def.inc-Datei
am Anfang. "2313def.inc")
Die Verweise auf diese *.asm-Dateien mit .INCLUDE
sollten aber an das "Hauptprogramm"-Ende geschrieben werden,
andernfalls quittiert das die Programmiersoftware mit einer Menge
von Fehlermeldungen, wie zum Beispiel:
"...error: Overlap in .cseg: addr=0x0 conflicts with 0x0:0x09a
giving up after 100 errors..."
Hier im Beispiel nun werden die Verzögerungsschleifen eingebunden über das
*.asm-INCLUDE-File
"verzoegerung.asm"
Will man im Simulator "debuggen", muß aber der Quelltext in
ASM-Mnemonik vollständig ausformuliert sein, da der "Simulator"
meistens diese .INCLUDE-Anweisungen hier mißversteht und stoppt,
bzw. nur das "sieht", was an Mnemonik alles im Klartext im
Editorfenster vorhanden ist ("Built & Run").
Die gebräuchlichsten prozessortypischen Definitionsfiles
befinden sich im Standard-Lieferumfang des ATMEL-Studio4.
;-----------------------------------------------------------
;
;*Deklarationen:
;
; RS=PortB,Bit0; Enable=PortB,Bit1;
; RW/-Steuerbit:
; auf "Low" (Masse, GND) fest verdrahtet
;
; *LCD-Datenbus-Beschaltung:
; D0 = GND (mit Widerstand)
; D1 = GND (mit Widerstand)
; D2 = GND (mit Widerstand)
; D3 = GND (mit Widerstand)
; D4 = PB4
; D5 = PB5
; D6 = PB6
; D7 = PB7
;
; *MCU-PORT:
; PORTB:
; PB0 = RS
; PB1 = ENABLE
; PB2 = nicht verwendet
; PB3 = nicht verwendet
; PB4 = D4 Displaybus
; PB5 = D5 Displaybus
; PB6 = D6 Displaybus
; PB7 = D7 Displaybus
;
; *MCU-Taktfrequenz: 4 MHz
; - - - - - - - - - - - - - - - -
; Hier startet der Code:
;
.nolist
.include "2313def.inc" ; Einbinden der prozessortypischen
.list ; Definitionsdatei
;
;* Konstanten, Synonyme:
;
.equ daten = portb ; Portzuweisung LCD-Ausgabe
.equ rs = 0 ; Portbit fuer RS festlegen
.equ ena = 1 ; Portbit fuer Enable festlegen
;
;* Registerdefinitionen, Synonyme:
;
.def temp = r16 ; Temporaerregister 1
.def temp1 = r17 ; Temporaerregister 2
.def temp2 = r18 ; Temporaerregister 3
.def zeit = r19 ; Register fuer Verzoegerungsschleifen
.def zeit1 = r20 ; Register fuer Verzoegerungsschleifen
.def zeit2 = r21 ; Register fuer Verzoegerungsschleifen
;
.cseg ; Sprung ins Codesegment
.org 0x0000 ; Einsprungadresse(n)festlegung
rjmp hauptprogramm ; Sprung ins Hauptprogramm
;
hauptprogramm:
;
ldi temp, low(RAMEND) ; Stack initialisieren
out SPL, temp ; Stackpointerregister auf Ende SRAM
;ldi temp, high(RAMEND) ; nur bei AVR-MCUs mit
;out SPH, temp ; hoeherem Speicher als ATTiny2313
ser temp ; Portzuweisung
out DDRB, temp ; Port B Ausgang fuer LCD
rcall einschaltverzoegerung ; der Power-On-Self-Reset des LCD
rcall lcd_routine ; muß erst abgewartet werden
rcall textausgabe ; ruft einmal Text auf
rjmp ende
;
ende: ; Programmende Endlosschleife
;
rjmp ende
;
einschaltverzoegerung: ; LCD-Power-on-self-Reset
;
rcall verzoegerung3
rcall verzoegerung3
rcall verzoegerung3
ret
;
lcd_routine: ; LCD-Initialisierung
;
push temp ; Sicherung Temporaer- und
push temp1 ; Statusregister auf Stack
push temp2
clr temp2
in temp2, SREG
push temp2
cbi daten, rs ; RS-Bit auf "low"
ldi temp, 0x00 ; Reset des Ports
out daten, temp
rcall enable ; Enable-Impuls toggeln
rcall verzoegerung3
;
; Acht-Bit Init: ; dreimal Hex 0x30
; ; immer notwendig,
cbi daten, rs ; wenn einmal 8/4-Bit-Modus
ldi temp, 0x30 ; gewechselt wird
out daten, temp ; dazu steht RegisterSelect-Bit
rcall enable ; stets auf "low"
rcall verzoegerung3 ; und jedesmal ein Enable-
cbi daten, rs ; Impuls-Toggeln
ldi temp, 0x30
out daten, temp
rcall enable
rcall verzoegerung3
cbi daten, rs
ldi temp, 0x30
out daten, temp
rcall enable
rcall verzoegerung3
;
; Acht-zu-Vier-Bit-Modus-Wechsel
;
cbi daten, rs
ldi temp, 0x20 ; zunaechst nur 4-Bit-Modus
out daten, temp ; untere vier Bit werden
rcall enable ; ignoriert, aber immer noch
rcall verzoegerung3 ; 8-Bit-Bus-Befehlsbreite
;
; Vier-Bit-Modus; zwei Zeilen
;
cbi daten, rs ; dann 4-Bit-Modus
ldi temp, 0x28 ; und zweizeilig
rcall kommando ; ab hier bereits Steuerbefehle
rcall verzoegerung3 ; im Vierbit-Kommando-Modus
; ; senden
; On Off Control; Display einschalten:
;
cbi daten, rs
ldi temp, 0x08 ; Displaypuffer ausschalten,
rcall kommando ; Cursor aus, Blinken aus
rcall verzoegerung3
;
; Anzeige loeschen:
;
cbi daten, rs ; Anzeige wird im "Space"-
ldi temp, 0x01 ; Modus geloescht
rcall kommando ; braucht mehr Zeit
rcall verzoegerung3
rcall verzoegerung3
;
; Entry Mode/Shift Set:
;
cbi daten, rs
ldi temp, 0x06 ; Cursor von links nach rechts
rcall kommando ; "increment", kein Schieben
rcall verzoegerung3
cbi daten, rs
ldi temp, 0x10 ; kein Schieben, Cursorbewegung
rcall kommando ; von links nach rechts
rcall verzoegerung3
cbi daten, rs
ldi temp, 0x0F ; Anzeige wieder durchschalten
rcall kommando ; Cursor ein, Blinken ein
rcall verzoegerung3
cbi daten, rs
ldi temp, 0x01 ; Anzeige nochmals loeschen
rcall kommando
rcall verzoegerung3
pop temp2 ; Temporaerregister und
out SREG, temp2 ; Statusregister wiederherstellen
pop temp2
pop temp1
pop temp
ret
;
datenuebernahme: ; Schreiben von ASCII-Zeichen ins Display
;
push temp ; Retten von "temp" auf Stapel
mov temp1, temp ; kopiert "temp" auf "temp1" fuer weitere;
; ; Operation unten
andi temp, 0b11110000 ; unteres Daten-Nibble ausblenden
ori temp, 0b00001111 ; RS, etc. auf "high" maskieren Port B
sbi daten, rs ; RS-Bit auf "high" , um ASCII-Charakter...
nop ; ins Display zu schreiben...Pause
out daten, temp ; Ausgabe oberes Nibble auf PB4-PB7
nop ; Stabilisierungspause
rcall enable ; ruft Enableimpulsgenerierung auf
nop
swap temp1 ; Nibble-Vertauschen, alter Wert aus "temp";
; ; ist nach "temp1" kopiert, s.o.
andi temp1, 0b11110000 ; unteres Daten-Nibble ausblenden
ori temp1, 0b00001111 ; RS, etc. auf "high" maskieren; Port B
sbi daten, rs ; RS-Bit nochmals setzen
nop ; Stabilisierungspause
out daten, temp1 ; Ausgabe unteres Nibble auf PB4-PB7
nop ; Stabilisierungspause
rcall enable ; zweiter Enableimpuls kommt jetzt
rcall verzoegerung2 ; kurze Pause zwischen Zeichen
pop temp ; Wiederherstellen von "temp"
ret
;
kommando: ; Schreiben von Steuerbefehlen ins Display
;
push temp ; Retten von "temp" auf Stapel
mov temp1, temp ; kopiert "temp" auf "temp1" fuer weitere;
; ; Operation unten
andi temp, 0b11110000 ; unteres Nibble sperren
ori temp, 0b00000000 ; Port maskieren, RS auf "low"
cbi daten, rs ; RS-Bit auf "low" fuer Steuerbefehle
nop ; Stabilisierungspause
out daten, temp ; Ausgabe oberes Daten-Nibble auf PB4-PB7
nop ; Stabilisierungspause
rcall enable ; ruft Enableimpulsgenerierung auf
nop
swap temp1 ; Nibble-Vertauschen, Wert aus "temp"
; ; ist nach "temp1" kopiert, siehe oben
andi temp1, 0b11110000 ; unteres Nibble sperren
ori temp1, 0b00000000 ; Port maskieren, RS etc auf "low"
cbi daten, rs ; nochmals RS auf "low"
nop ; Stabilisierungspause
out daten, temp1 ; Ausgabe unteres Daten-Nibble auf PB4-PB7
nop ; Stabilisierungspause
rcall enable ; zweiter Enableimpuls kommt jetzt
rcall verzoegerung2 ; kurze Pause
pop temp ; Wiederherstellen von "temp"
ret
;
enable: ; Enableimpulsgenerierung
;
nop
sbi daten, ena ; Enable-Bit auf "high" setzen
nop ; Zeitverzoegerung mindestens
nop ; 1000 (500)Nanosekunden laut Datenblatt
nop ; lieber ein paar "nops" mehr
nop ; spendieren
nop
nop
nop
nop
nop
nop
nop
nop
cbi daten, ena ; Enable-Bit wieder auf "low" zurücksetzen
nop
ret
;
textausgabe: ; Textausgabe/ Positionierungsbeispiel
; ; der Assembler errechnet automatisch
ldi temp, 'A' ; das korrekte ASCII-Zeichen, werden
rcall datenuebernahme ; Zeichen zwischen 'einfache'
rcall verzoegerung2 ; Anfuehrungsstriche gesetzt
ldi temp, 0xC0 ; Positionierung Anfang Zeile 2
rcall kommando
rcall verzoegerung2
ldi temp, 'H'
rcall datenuebernahme
rcall verzoegerung2
ldi temp, 'a'
rcall datenuebernahme
rcall verzoegerung2
ldi temp, 'l'
rcall datenuebernahme
rcall verzoegerung2
ldi temp, 'l'
rcall datenuebernahme
rcall verzoegerung2
ldi temp, 'o'
rcall datenuebernahme
rcall verzoegerung2
ret
; Einzubindender Pogrammabschnitt:
;
.include "verzoegerung.asm"
; FIN
;-------------------------------------
;
; und natürlich die oben angegebenen Verzögerungsschleifen
nicht vergessen...
Diese werden, wie gesagt, durch eine INCLUDE-Anweisung am Programmende
hinzugefügt.
Initialisierung
Vierbitmodus
Zum Seitenanfang
BRLT-Befehl:
Dieser bedingte Sprung mit relativer Adressierung
testet das "Signed Flag (S)" und verzweigt
in relativer Adressierung zur Adresse im Programmzähler,
wenn "S" gesetzt ist. Wird diese Anweisung unmittelbar
hinter einen der Befehle CP, CPI, SUB oder SUBI gesetzt,
wird der Sprung eineindeutig nur dann ausgeführt, wenn
eine "signierte" Binärzahl in Rd kleiner als eine "signierte"
Binärzahl in Rx ist. Die Sprungweite beträgt 63 Adressen
zurück und 64 Adressen vorwärts. Der Parameter "k"
stellt den Offset des Programmzählers in Zweierkomplement-
Form dar. (Ähnlicher Befehl: BRBS 4,k)
CPI-Befehl:
Diese Anweisung führt einen Vergleich aus zwischen Register Rd
und einer Konstanten. Der Registerinhalt wird dabei nicht verändert.
Es können alle bedingten Sprungbefehle hinter dieser Anweisung
folgen.
Status Register (SREG) and Boolsche Formel:
I T H S V N Z C
- - x x x x x x x=groesser/kleiner als
Zum Seitenanfang
Assemblieren - Flashen
Zum Erzeugen eines flashbaren Files wird zunächst
der Assembler-Mnemonik-Code in den Studio-4-Editor
geladen, dann mit Menü "Build" assembliert.
Die Build-Message am Ende sollte dann so aussehen:
AVRASM: AVR macro assembler 2.1.12 (build 87 Feb 28 2007 07:31:13)
Copyright (C) 1995-2006 ATMEL Corporation
Include file:
C:\Programme\Atmel\AVR Tools\AVRAssembler2\Appnotes\2313def.inc
No EEPROM data deleting...eep
AT90S2313 memory use summary [bytes]:
Segment: Begin: End: Code: Data: Used: Size: Use%:
[.cseg] 0x000000 0x000178 376 0 376 2048 18.4%
[.dseg] 0x000060 0x000060 0 0 0 128 0%
[.eseg] 0x000000 0x000000 0 0 0 128 0%
* Assembly complete, 0 errors, 0 warnings
Kein Fehler, keine Warnung, das heißt, die Syntax ist korrekt.
So sieht nun das fertig assemblierte Hex-File aus:
Aufbau: Adressen-Code-Prüfzahl
Hinweis:
Dieser unten angegebene Code kann direkt zum Flashen
benutzt werden. Dazu markiert man ihn, kopiert ihn
in einen Text-Editor, speichert ihn dann
in einem Text-File auf der Festplatte ab.
Diese Datei muß dann nur noch vor dem Flashvorgang
mit der Dateiendung "hex" versehen werden.
:020000020000FC
:1000000000C00FED0DBF0FEF07BB76D003D078D047
:1000100000C0FFCF0F931F932F932FB72F93C0983C
:1000200000E008BB5DD099D0C09800E308BB58D071
:1000300094D0C09800E308BB53D08FD0C09800E3A1
:1000400008BB4ED08AD0C09800E208BB49D085D00A
:10005000C09808E233D081D0C09808E02FD07DD07E
:10006000C09801E02BD079D078D0C09806E026D097
:1000700074D0C09800E122D070D0C0980CE01ED09F
:100080006CD0C09801E01AD068D02F912FBF2F916B
:100090001F910F9108950F93C09A102F007F0F604A
:1000A00008BBC09A1DD0000000001295107F1F6091
:1000B00018BB16D047D0C09A0F9108950F93C098DF
:1000C000102F007F006008BBC0980AD0000000001D
:1000D0001295107F106018BB03D034D00F91089593
:1000E0000000C19A000000000000000000000000B5
:1000F0000000C1980000089530D02FD02ED0089570
:1001000001E4C9DF1FD000ECD9DF1CD008E4C3DF55
:1001100019D001E6C0DF16D00CE6BDDF13D00CE627
:10012000BADF10D00FE6B7DF0DD00895A8953FEFE6
:1001300045E000C03A953130ECF33FEF4A9541304D
:10014000CCF30895A8953FEF40E23A953130ECF3B7
:100150003FEF4A954130CCF30895A8953FEF4FEF1C
:1001600050EA3A953130ECF33FEF4A954130CCF309
:080170005A955130B4F30895D3
:00000001FF
Initialisierung
Vierbitmodus
Zum Seitenanfang
*1) Zusatz:
"Paradoxe" Befehlsfolge im "Entry Mode Set" und "Shift Set":
Folgendes Programm läßt den Text "Hallo Hallo",
auf Zeile 2 beginnend, nach rechts wandern.
Gleichzeitig kreuzt der blinkende Cursor den Text
in entgegengesetzter Richtung laufend.
Dabei erscheint beim Blinken abwechselnd ein schwarzer
Balken und der entsprechende Buchstabe.
Man sieht hier auch sehr schön, wie plötzlich der Cursor
wieder auf Zeile 1 wechselt, wenn der wiederholt in einer
Endlosschleife gesendete Text "Hallo Hallo" am Ende
des gesamten (nicht darstellbaren) Adressbereichs angekommen ist,
und der Schiebebefehl nun wieder bei der entsprechenden Adresse
wirksam wird. Es wird ja - wie gesagt - der gesamte Adressbereich
vom Shift-Befehlssatz beeinflusst, nicht nur eine Zeile für sich isoliert.
Die hierbei noch benötigten INCLUDE-Files können nochmals
gesondert heruntergeladen werden:
lcdinit.asm
verzoegerung.asm
;*******************************************
;* LCD-Initialisierungstest mit
;* paradoxen Befehlen:
;* Entry Mode Set-Shift Set: Text und Cursor
;* gegensinnig bewegen
;* 428 Byte, 20,9 % Programmspeicherbedarf
;* MCU: ATTiny2313 bei 4 MHz Taktfrequenz
;*
;* Zur gefaelligen Beachtung:
;* Die Routinen fuer LCD-Initialisierung
;* und Verzoegerungsschleifen muessen
;* beim Assembliervorgang vorhanden sein.
;* Diese INCLUDE-Anweisungen am Programmende
;*******************************************
;
.nolist
.include "2313def.inc"
.list
;
.equ daten = portb
.equ beleuchtung = 0x08 ; Portbit Hintergrundbeleuchtung
.def temp = r16 ; Temporaerregister 1
.def temp1 = r17 ; Temporaerregister 2
.def temp2 = r18 ; Temporaerregister 3
.def zeit = r19 ; Register fuer Verzoegerungsschleifen
.def zeit1 = r20 ; Register fuer Verzoegerungsschleifen
.def zeit2 = r21 ; Register fuer Verzoegerungsschleifen
;
.cseg ; Sprung ins Codesegment
.org 0x0000 ; Programmbeginn ab Adresse Null
rjmp hauptprogramm
;
hauptprogramm:
;
ldi temp, low(RAMEND) ; Stack initialisieren
out SPL, temp ; Stackpointerregister auf Ende SRAM
;ldi temp, high(RAMEND) ; nur bei AVR-MCUs mit
;out SPH, temp ; hoeherem Speicher als ATTiny2313
ser temp ; Portzuweisung
out DDRB, temp ; Port B Ausgang fuer LCD
rcall einschaltverzoegerung ; der Power-On-Self-Reset des LCD
; ; muss abgewartet werden
rcall lcd_routine
rcall textausgabe
ldi temp, beleuchtung ; Hintergrundbeleuchtung einschalten
out daten, temp
rcall schieben
rjmp ende
;
schieben:
;
rcall verzoegerung3
rcall verzoegerung3
ldi temp, 0x1C ; Display right shift
rcall kommando
rcall verzoegerung3
rcall verzoegerung3
ldi temp, 0x10 ; Cursor left shift
rcall kommando
rcall verzoegerung3
rcall verzoegerung3
ldi temp, 0x10 ; Cursor left shift
rcall kommando
rcall verzoegerung3
rcall verzoegerung3
ret
;
ende: ; Programmende Endlosschleife
;
rcall verzoegerung3
rcall verzoegerung3
rcall schieben
rjmp ende
;
textausgabe: ; Textausgabe/ Positionierungsbeispiel
; ; mit Hilfe eines Pointers
ldi temp, 0xC0 ; Positionierung Anfang Zeile 2
rcall kommando
rcall verzoegerung2
rjmp festtext
;
festtext: ; Textabfrage
;
ldi ZL, LOW(text*2) ; Adresse des Strings in den
ldi ZH, HIGH(text*2) ; Z-Pointer laden
rcall print ; Funktion print aufrufen
rcall verzoegerung3 ; Pause zwischen Zeichen
ret ; Sprung zurueck
;
print:
;
lpm ; erstes Byte des Strings nach R0 lesen
tst R0 ; R0 auf 0 testen
breq print_end ; wenn 0, dann zu print_end
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe ; Ausgabefunktion aktivieren
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label text;zu holen
print_end: ; Beendigung der Ausgaberoutine
;
ret
;
text: ; Konstanten des Textes
;
.db "Hallo Hallo", 0
;
ausgabe: ; gibt Text von "temp" auf LCD aus
;
rcall datenuebernahme
rcall verzoegerung3
ret
; Einzubindende Programmabschnitte:
; LCD-Initialisierung, Eingabe-Ausgaberoutinen
; Verzoegerungsschleifen
.include "lcdinit.asm"
.include "verzoegerung.asm"
; FIN
;-----------------------------------
Initialisierung
Vierbitmodus
Zum Seitenanfang
Userdefinierte Charakters erzeugen:
Die hierbei noch benötigten INCLUDE-Files können,
falls noch nicht geschehen, hier nochmals
gesondert heruntergeladen werden:
lcdinit.asm
verzoegerung.asm
;*******************************************
;* LCD-Initialisierungstest mit
;* acht userdefinierten Charakters
;*
;* 826 Byte, 40,3 % Programmspeicherbedarf
;* MCU: ATTiny2313 bei 4 MHz Taktfrequenz
;* Zur gefaelligen Beachtung:
;* Die Routinen fuer LCD-Initialisierung
;* und Verzoegerungsschleifen muessen
;* beim Assembliervorgang vorhanden sein.
;* Diese INCLUDE-Anweisungen am Programmende
;*******************************************
;
.list
.include "2313def.inc"
.nolist
;
.equ daten = portb
.equ beleuchtung = 0x08 ; Portbit Hintergrundbeleuchtung
.def temp = r16 ; Temporaerregister 1
.def temp1 = r17 ; Temporaerregister 2
.def temp2 = r18 ; Temporaerregister 3
.def zeit = r19 ; Register fuer Verzoegerungsschleifen
.def zeit1 = r20 ; Register fuer Verzoegerungsschleifen
.def zeit2 = r21 ; Register fuer Verzoegerungsschleifen
;
.cseg ; Sprung ins Codesegment
.org 0x0000 ; Programmbeginn ab Adresse Null
rjmp hauptprogramm
;
hauptprogramm:
;
ldi temp, low(RAMEND) ; Stack initialisieren
out SPL, temp ; Stackpointerregister auf Ende SRAM
;ldi temp, high(RAMEND) ; nur bei AVR-MCUs mit
;out SPH, temp ; hoeherem Speicher als ATTiny2313
ser temp ; Portzuweisung
out DDRB, temp ; Port B Ausgang fuer LCD
rcall einschaltverzoegerung ; der power-on self reset des LCD muss
; ; abgewartet werden
rcall lcd_routine
ldi temp, beleuchtung ; Hintergrundbeleuchtung einschalten
out daten, temp
rcall zeichen_neu
rcall ende
rcall ende1
;
zeichen_neu:
;
rcall ascii_00
rcall ascii_01
rcall ascii_02
rcall ascii_03
rcall ascii_04
rcall ascii_05
rcall ascii_06
rcall ascii_07
ret
;
ende: ; Ausgabe
;
rcall text ; Festtext, "normaler" ASCII-Code
rcall verzoegerung2 ; neue Zeichen:
ldi temp, 0xC0 ; Position Anfang Zeile 2
rcall kommando
ldi temp, 0x00 ; Zeichen ASCII_00
rcall datenuebernahme
ldi temp, 0xC2 ; Position Spalte 3, Zeile 2
rcall kommando
ldi temp, 0x01 ; Zeichen ASCII_01
rcall datenuebernahme
ldi temp, 0xC4 ; Position Spalte 5, Zeile 2
rcall kommando
ldi temp, 0x02 ; Zeichen ASCII_02
rcall datenuebernahme
ldi temp, 0xC6 ; Position Spalte 7, Zeile 2
rcall kommando
ldi temp, 0x03 ; Zeichen ASCII_03
rcall datenuebernahme
ldi temp, 0xC8 ; Position Spalte 9, Zeile 2
rcall kommando
ldi temp, 0x04 ; Zeichen ASCII_04
rcall datenuebernahme
ldi temp, 0xCA ; Position Spalte 11,Zeile 2
rcall kommando
ldi temp, 0x05 ; Zeichen ASCII_05
rcall datenuebernahme
ldi temp, 0xCC ; Position Spalte 13,Zeile 2
rcall kommando
ldi temp, 0x06 ; Zeichen ASCII_06
rcall datenuebernahme
ldi temp, 0xCE ; Position Spalte 15,Zeile 2
rcall kommando
ldi temp, 0x07 ; Zeichen ASCII_07
rcall datenuebernahme
ret
;
ende1: ; Programmende
;
rjmp ende1
;
text: ; Festtext: "Neue Charakters"
;
ldi temp, 0x80 ; Positionierung Anfang
rcall kommando ; Zeile 1, Spalte 1
rcall verzoegerung2
ldi ZL, LOW(text1*2) ; Adresse des Strings in den
ldi ZH, HIGH(text1*2) ; Z-Pointer laden
rcall print ; Funktion print aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print:
;
lpm ; erstes Byte des Strings nach R0 lesen
tst R0 ; R0 auf 0 testen
breq print_end ; wenn 0, dann zu print_end
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label text1;zu holen
print_end:
;
ret
;
text1: ; Konstanten des Festtextes
;
.db "Neue Charakters", 0
;
ausgabe:
;
rcall datenuebernahme
ret
; ; Charakters zuordnen
ascii_00: ; und Pixel definieren
;
ldi temp, 0x40 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; fuer ASCII-Code hex 00
rcall verzoegerung2
ldi ZL, LOW(ascii_0*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_0*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_0 ; Funktion print_0 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_0:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end0 ; wenn 9, dann zu print_end0
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_0 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_0;zu holen
print_end0:
;
ret
;
ascii_01:
;
ldi temp, 0x48 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; fuer ASCII-Code hex 01
rcall verzoegerung2
ldi ZL, LOW(ascii_1*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_1*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_01 ; Funktion print_01 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_01:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end1 ; wenn 9, dann zu print_end
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_01 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_1;zu holen
print_end1:
;
ret
;
ascii_02:
;
ldi temp, 0x50 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; fuer ASCII-Code hex 02
rcall verzoegerung2
ldi ZL, LOW(ascii_2*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_2*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_02 ; Funktion print_02 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_02:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end2 ; wenn 9, dann zu print_end2
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_02 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_2;zu holen
print_end2:
;
ret
;
ascii_03:
;
ldi temp, 0x58 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; fuer ASCII-Code hex 03
rcall verzoegerung2
ldi ZL, LOW(ascii_3*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_3*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_03 ; Funktion print_03 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_03:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end3 ; wenn 9, dann zu print_end3
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_03 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_3;zu holen
print_end3:
;
ret
;
ascii_04:
;
ldi temp, 0x60 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; ASCII-Code hex 04
rcall verzoegerung2
ldi ZL, LOW(ascii_4*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_4*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_04 ; Funktion print_04 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_04:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end4 ; wenn 9, dann zu print_end4
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_04 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_4;zu holen
print_end4:
;
ret
;
ascii_05:
;
ldi temp, 0x68 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; fuer ASCII-Code hex 05
rcall verzoegerung2
ldi ZL, LOW(ascii_5*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_5*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_05 ; Funktion print_05 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_05:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end5 ; wenn 9, dann zu print_end5
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_05 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_5;zu holen
;
print_end5:
;
ret
;
ascii_06:
;
ldi temp, 0x70 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; fuer ASCII-Code hex 06
rcall verzoegerung2
ldi ZL, LOW(ascii_6*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_6*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_06 ; Funktion print_06 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_06:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end6 ; wenn 9, dann zu print_end6
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_06 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_6;zu holen
print_end6:
;
ret
;
ascii_07:
;
ldi temp, 0x78 ; "Set CGRAM address"-Steuerbefehl
rcall kommando ; fuer ASCII-Code hex 07
rcall verzoegerung2
ldi ZL, LOW(ascii_7*2) ; Adresse des Strings in den
ldi ZH, HIGH(ascii_7*2) ; Z-Pointer laden
ldi temp2, 0x00
rcall print_07 ; Funktion print_07 aufrufen
rcall verzoegerung2
ret ; Sprung zurueck
;
print_07:
;
lpm ; erstes Byte des Strings nach R0 lesen
inc temp2
cpi temp2, 0x09 ; insgesamt achtmal Pixelwerte holen
breq print_end7 ; wenn 9, dann zu print_end7
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall ausgabe
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print_07 ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label ascii_7;zu holen
print_end7:
;
ret
; ; Konstanten der Charakterpixel
; ; keine Nullterminierung
ascii_0: ; Pixel des Charakters ASCII_00
.db 0x1F, 0x15, 0x15, 0x1F, 0x15, 0x15, 0x1F, 0x00
;
ascii_1: ; Pixel des Charakters ASCII_01
.db 0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00, 0x00
;
ascii_2: ; Pixel des Charakters ASCII_02
.db 0x1F, 0x1B, 0x1B, 0x00, 0x1B, 0x1B, 0x1F, 0x00
;
ascii_3: ; Pixel des Charakters ASCII_03
.db 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x1F, 0x00
;
ascii_4: ; Pixel des Charakters ASCII_04
.db 0x1F, 0x0E, 0x04, 0x15, 0x0A, 0x05, 0x02, 0x01
;
ascii_5: ; Pixel des Charakters ASCII_05
.db 0x0E, 0x11, 0x1C, 0x10, 0x1C, 0x11, 0x0E, 0x00
;
ascii_6: ; Pixel des Charakters ASCII_06
.db 0x0E, 0x11, 0x1B, 0x15, 0x15, 0x1F, 0x0E, 0x04
;
ascii_7: ; Pixel des Charakters ASCII_07
.db 0x10, 0x08, 0x04, 0x02, 0x01, 0x11, 0x1F, 0x1F
;
; Einzubindende Programmabschnitte:
; LCD-Initialisierung, Eingabe-Ausgaberoutinen,
; Verzoegerungsschleifen
.include "lcdinit.asm"
.include "verzoegerung.asm"
; FIN
;-------------------------------
------------------------------------------------------
Programmabschnitt für Busy-Flag-Abfrage (Vierbit-Modus)
Die Unterprogramm-Aufrufe für die Verzögerungsschleifen
können dann bei Benutzung der Busy-Flag-Abfrage durch Aufrufe
des Labels "busy_read" ersetzt werden, allerdings erst hinter der
Einschaltverzögerung und dem "Function Set".
(Man sieht, ganz ohne Verzögerungsschleifen kommt man doch
nicht aus. )
Testweise bei den Unterprogrammen "datenuebernahme"
und "kommando" einmal "rcall verzoegerung2" heraus-rem-en und
"rcall busy_read" einfügen.
Zur evtl. Verhinderung unerwünschter Cursorwanderung bzw.
fehlerhafter Zeichenpositionierung sind hier die laut
Dokumentation vorgesehenen Enable-Impulse ausgeremt
worden.
;*******************************
; "busy_read.asm" INCLUDE-File
; Busy-Flag-Abfrage (Vierbit-Modus):
; ; Deklaration Steuerbits:
; ; PortB, Bit0 = RS
; ; PortB, Bit1 = Enable
; ; PortB, Bit2 = R/W
busy_read:
;
push temp ; retten von temp auf Stapel
ldi temp, 0x7F ; Ausgabeport zum Eingabeport
nop ; maskieren, nur Bit7 "High"-Nibble,
nop ; "Low"-Nibble fuer RS/RW/Enable-
nop ; Ausgabeport beibehalten
out ddrb, temp ; Portsynchronisation
nop ; abwarten
cbi portb, 0 ; RS-Bit "low" (Kommandomodus)
sbi portb, 2 ; R/W-Bit "high" = LCD-Auslesen
rjmp busy_read1 ; Sprung zur Abfrage-Schleife
;
busy_read1: ; Abfrage-Schleife
;
nop
;sbi portb, 1 ; 1. Enable-Impuls vorbereiten,
;nop ; PortB, Bit1 setzen
;nop ; Enable-Impuls evtl. aus-rem-en
;nop
;cbi portb, 1 ; 1. Enable-Bit-"Low"-Flanke
;nop ; Start Abfrage "High-Nibble"
in temp, pinb ; Inhalt Port B nach
nop ; temp lesen
andi temp, 0x80 ; nur "high" Nibble Bit7
nop
;sbi portb, 1 ; 2. Enable-Impuls vorbereiten
;nop ; PortB, Bit1 setzen
;nop ; Enable-Impuls evtl aus-rem-en
;nop
;cbi portb, 1 ; 2. Enable-Bit-"Low"-Flanke
;nop ; "Low"-Nibble ignorieren
cpi temp, 0x80 ; Zustand Bit7 abfragen
breq busy_read1 ; Bit7 = 1 => Sprung zurueck
nop ; nach busy_read1, sonst fortfahren
ldi temp, 0xFF ; Eingabeport zum vollen
out ddrb, temp ; Ausgabeport wiederherstellen
cbi portb, 2 ; R/W-Bit "low" = LCD-Schreiben
sbi portb, 0 ; RS-Bit wieder "high" setzen
pop temp ; Wiederherstellung von temp
ret
; ------FIN
-------------------------------------------------------
anderes Initialisierungsprogramm (Vierbit-Modus)
Beispiel für LCD-Initialisierung mit abweichender Portbelegung:
Hier mußte auch der "Swap-String" anders gesetzt werden.
;********************
; LCD-4-Bit-Initialisierung und
; Textdarstellungs-Tests (16x2-LCD)
; *
; MCU:
; ATMega16
; CPU Takt: 12 MHz
; Programmgroesse 570 Bytes
; 3,5 % used Memory
; *
; LCD-Datenbus-Beschaltung:
; D0 = GND (mit Widerstand)
; D1 = GND (mit Widerstand)
; D2 = GND (mit Widerstand)
; D3 = GND (mit Widerstand)
; D4 = PD0
; D5 = PD1
; D6 = PD2
; D7 = PD3
; *
; RW/-Steuerbit:
; auf "Low" (Masse, GND) fest verdrahtet
; *
; MCU-PORTS:
; PORTD:
; PD0 = LCD D4
; PD1 = LCD D5
; PD2 = LCD D6
; PD3 = LCD D7
; PD4 = RS
; PD5 = ENABLE
; PD6 = nicht verwendet
; PD7 = nicht verwendet
; *
; Rev. 230509
;********************
;
; Deklarationsteil:
.nolist
.include "m16def.inc"
.list
; synonyme Bezeichnungen:
;
.equ daten = portd ; Definition des LCD-Ausgabeports
.equ mctakt = 12000000 ; verwendete Quarzfrequenz
.equ rs = 4 ; Festlegung des RS-Bits am ;MCU-Port
.equ ena = 5 ; Festlegung des Enable-Bits ;am MCU-Port
.equ zeit1 = 65535 ; Registerladewert 1 fuer Zeitschleifen
.equ zeit2 = 65535 ; Registerladewert 2 fuer Zeitschleifen
.equ zeit3 = 65535 ; Registerladewert 3 fuer Zeitschleifen
;
; synonyme Register-Bezeichnungen:
;
.def temp = r16 ; Temporaerregister 1
.def temp1 = r17 ; Temporaerregister 2
.def temp2 = r18 ; Temporaerregister 3
.def xxl = r24 ; r24 als Doppelregister "Lowbyte"
.def xxh = r25 ; r25 als Doppelregister "Highbyte"
; ; fuer "16-Bit"-Zeitschleifen
;
; Speicher- und Einsprungadressenzuordnungen:
;
.cseg ; Codesegment
.org 0x0000 ; Anfangsadresse bei Null
rjmp hauptprogramm ; Sprung zum Hauptprogramm
;
hauptprogramm:
;
ldi temp, low(RAMEND) ; Stack initialisieren
out SPL, temp
ldi temp, high(RAMEND)
out SPH, temp
ldi temp, 0xFF ; Portinitialisierung
out DDRD, temp ; Port D Ausgang fuer LCD
rcall lcd_init_routinen ; Sprung zur LCD-Initialisierung
rjmp ende
;
ende: ; Endlosschleife:
;
rcall textprobe ; Text wird von links geschrieben...
rcall lcd_clear ; ...ohne Cursor...LCD loeschen
rcall verz4 ; Verzoegerung
rcall lcd_entry_mode2 ; damit Text von rechts kommt,...
rcall textprobe ; ...Cursor steht und blinkt
rcall verz4 ; Verzoegerung
rcall lcd_entry_mode ; und Text wieder von links...
rcall verz4 ; Verzoegerung
rjmp ende ; und wieder von vorne, endlos
;
textprobe: ; Textbaustein 1:
;
ldi ZL, LOW(2*text1) ; Adresse des Strings in den
ldi ZH, HIGH(2*text1) ; Z-Pointer laden
rcall print ; Funktion print aufrufen
rcall verz4 ; Pausen zwischen Zeichen
rcall verz4
rjmp textprobe2 ; danach Sprung zum 2. Textbaustein
;
print:
;
lpm ; erstes Byte des Strings nach R0 lesen
tst R0 ; R0 auf 0 testen
breq print_end1 ; wenn 0, dann zu print_end1
mov temp, r0 ; Inhalt von R0 nach temp kopieren
rcall lcd_ausgabe ; Ausgabefunktion aktivieren
adiw ZL:ZH, 1 ; Adresse des Z-Pointers um 1 erhoehen
rjmp print ; zum Anfang springen, um naechstes
; ; Byte aus dem cseg-Label text1/2 zu holen
print_end1: ; Beenden der Leseroutine
;
ret
;
text1: ; Konstanten Text1:
;
.db "Das ist ein ...", 0
;
textprobe2: ; Textbaustein 2:
;
ldi temp, 0xC0 ; LCD auf Position Anfang Zeile 2
rcall kommando
rcall verz3
ldi ZL, LOW(2*text2) ; Adresse des Strings in den
ldi ZH, HIGH(2*text2) ; Z-Pointer laden
rcall print ; Funktion print aufrufen
rcall verz4 ; Pausen zwischen Zeichen
rcall verz4
ret
;
text2: ; Konstanten Text2:
;
.db "***Probetext***", 0
;
lcd_ausgabe: ; uebergibt Inhalt von "temp" an LCD
;
rcall verz4
rcall datenuebernahme ; Aufruf LCD-Charakter schreiben
rcall verz4 ; mit Verzoegerungen
ret
;
lcd_init_routinen: ; LCD-Initialisierung:
;
rcall verz4 ; Einschaltverzoegerung LCD
rcall verz4 ; LCD-Power-on-self-Init
rcall verz4 ; sollte abgewartet werden
rcall verz4
push temp ; Sicherung Temporaerregister...
push temp1 ; und Statusregister auf Stapel
push temp2 ; ist hier eigentlich unnoetig...
in temp2, SREG ; ...bei Interrupts aber doch
push temp2
rcall lcd_reset ; LCD-Bus-Reset
rcall lcd_achtbitinit ; LCD-Init: acht Bit
rcall lcd_acht_vier ; LCD-Init: acht zu vier Bit
rcall lcd_vier_zwei_z ; LCD-Init: vier Bit, 2 Zeilen
rcall lcd_on_off ; LCD-Init: Displaypuffer aus
rcall lcd_clear ; LCD-Init: Anzeige loeschen
rcall lcd_entry_mode ; LCD-Init: Entry Mode Set
pop temp2 ; Wiederherstellung Temporaerregister
out SREG, temp2 ; und Statusregister
pop temp2
pop temp1
pop temp
ret
;
lcd_reset: ; LCD-Bus-Reset:
;
push temp ; Temporaeregister auf Stack retten
cbi daten, rs ; RS-Bit auf "low"
ldi temp, 0x00 ; Reset des LCD-Ausgabeports
out daten, temp ; Ausgabe auf Port D = "daten"
rcall enable ; Enable-Impuls toggeln
rcall verz3 ; kurze Verzoegerung
pop temp ; Wiederherstellen von "temp"
ret
;
lcd_achtbitinit: ; Acht-Bit Init:
; ; dreimal Hexerei 0x03;0x30
push temp ; ist immer notwendig,
cbi daten, rs ; wenn einmal 8/4-Bit-Modus
ldi temp, 0x03 ;0x30 ; gewechselt werden soll,
out daten, temp ; dazu steht RegisterSelect-Bit
rcall enable ; stets auf "low",
rcall verz3 ; und jedesmal einen Enable-
cbi daten, rs ; Impuls toggeln lassen
ldi temp, 0x03 ;0x30 ; ausgeremte Werte nehmen,
out daten, temp ; wenn PD4 bis PD7 statt
rcall enable ; PD0 bis PD3 am MCU-Port
rcall verz3 ; verwendet werden
cbi daten, rs ; Verzoegerungen notwendig,
ldi temp, 0x03 ;0x30 ; um Reaktionszeit LCD
out daten, temp ; abzuwarten
rcall enable
rcall verz3
pop temp
ret
;
lcd_acht_vier: ; Acht-zu-Vier-Bit-Modus-Wechsel:
;
push temp
cbi daten, rs
ldi temp, 0x02 ;0x20 ; zunaechst nur String "4-Bit-Modus",
out daten, temp ; untere vier LCD-Port-Bits werden
rcall enable ; ignoriert, aber immer noch
rcall verz3 ; 8-Bit-Befehlsbreite
pop temp ; ausgeremter Wert: siehe oben
ret
;
lcd_vier_zwei_z: ; Vier-Bit-Modus, zwei Zeilen:
;
push temp
cbi daten, rs ; dann 4-Bit-Modus
ldi temp, 0x28 ; und zweizeilig,
rcall kommando ; ab hier bereits Steuerbefehle
rcall verz3 ; im Vierbit-Kommando-Modus...
pop temp ; ...senden
ret ; ab hier keine Steuerwortaenderung
; ; gegenueber dem Originalcode...
; ; ...Nibble-Tauschen erledigt
; ; Subroutine "kommando"
;
lcd_on_off: ; On Off Control, Display ein/ausschalten:
;
push temp
cbi daten, rs
ldi temp, 0x08 ; Displaypuffer ausschalten,
rcall kommando ; Cursor aus, das Blinken aus
rcall verz3
pop temp
ret
;
lcd_clear: ; Anzeige loeschen:
;
push temp
cbi daten, rs ; Anzeige wird im "Space"-
ldi temp, 0x01 ; Modus geloescht
rcall kommando ; braucht mehr Zeit
rcall verz4 ; laengere Verzoegerung
pop temp
ret
;
lcd_entry_mode: ; Entry/Shift Mode Set:
;
push temp
cbi daten, rs
ldi temp, 0x06 ; Cursor von links nach rechts
rcall kommando ; kein Gesamtinhalt-Schieben
rcall verz3 ; mit Verarbeitungspausen
cbi daten, rs
ldi temp, 0x10 ; kein Schieben, Cursorbewegung
rcall kommando ; nach rechts
rcall verz3
cbi daten, rs
ldi temp, 0x0C ; Anzeige wieder einschalten
rcall kommando ; Cursor aus, das Blinken aus
rcall verz3
cbi daten, rs
ldi temp, 0x01 ; Puffer nochmals loeschen
rcall kommando
rcall verz4
pop temp
ret
;
lcd_entry_mode2: ; alternativer Setup (nur hier)
;
push temp ; Schreib-Position Ende Zeile 1
ldi temp, 0x8F
rcall kommando
rcall verz4
cbi daten, rs
ldi temp, 0x07 ; Text von rechts nach links
rcall kommando
rcall verz3
cbi daten, rs
ldi temp, 0x10 ; kein Gesamt-Schieben
rcall kommando
rcall verz3
cbi daten, rs
ldi temp, 0x0D ; Anzeige wieder sichtbar
rcall kommando ; Cursor ein, das Blinken ein
rcall verz3
pop temp
ret
;
datenuebernahme: ; uebergibt Charakter an LCD:
;
; ; gilt nur fuer Portbelegung PD0-PD3
; ; sonst "swap" als letzten Schritt
; ; und "andi" statt 0x0F dann 0xF0
; ; "ori" statt 0xF0 dann 0x0F
;
push temp ; rettet temp, temp1 auf Stapel
push temp1
mov temp1, temp ; Kopie des Wertes fuer weitere;
nop ; Operation unten
swap temp ; "oberes" Daten-Nibble nach "unten"
andi temp, 0b00001111 ; obere Portbits fuer Daten sperren
ori temp, 0b11110000 ; RS, etc. auf "high" maskieren Port D
sbi daten, rs ; setzt RS-Bit nochmals direkt
nop ; Pause zur Stabilisierung
out daten, temp ; zuerst oberes Daten-Nibble auf D0-D3
nop ; ausgeben
rcall enable ; Enableimpulsunterprogramm aufrufen
nop ; Pause zur Stabilisierung
; ; unveraenderte Kopie von "temp" auf
; ; "temp1" jetzt verwenden
andi temp1, 0b00001111 ; unteres Daten-Nibble durchstellen,
; ; zuletzt senden, steht schon "unten"
ori temp1, 0b11110000 ; RS, etc. auf "high" maskieren Port D
sbi daten, rs ; setzt RS-Bit nochmals direkt
nop ; Pause zur Stabilisierung
out daten, temp1 ; Ausgabe unteres Nibble auf D0-D3
nop ; Pause zur Stabilisierung
rcall enable ; zweiter Enableimpuls kommt
nop ; Pause zur Stabilisierung
pop temp1
pop temp ; Wiederherstellung von temp1, temp
rcall verz3 ; kurze Pause (kann auch entfallen)
ret
;
kommando: ; sendet Steuerzeichen an LCD:
;
; ; gilt nur fuer Portbelegung PD0-PD3
; ; sonst "swap" als letzten Schritt
; ; und "andi" statt 0x0F dann 0xF0
; ; "ori" bleibt 0x00
;
push temp ; rettet temp, temp1 auf Stapel
push temp1
mov temp1, temp ; Kopie des Wertes "temp" auf "temp1";
; ; fuer weitere Operation siehe unten
swap temp ; "oberes" Daten-Nibble nach "unten"
andi temp, 0b00001111 ; "obere" Portbits fuer Daten sperren
ori temp, 0b00000000 ; RS, etc. auf "low" maskieren Port D
cbi daten, rs ; loescht RS-Bit nochmals direkt
nop ; Stabilisierungspause
out daten, temp ; zuerst oberes Daten-Nibble auf D0-D3 ;
; ; ausgeben, LCD-Controller will's so
nop ; Stabilisierungspause
rcall enable ; ruft Enableimpulsunterprogramm auf
nop ; Stabilisierungspause
; ; unveraenderte Kopie von "temp"
; ; auf "temp1"jetzt verwenden siehe oben
andi temp1, 0b00001111 ; Port"oben"sperren,unteresDaten-Nibble
; ; zuletzt senden, steht schon unten
ori temp1, 0b00000000 ; RS, etc. auf "low" maskieren Port D
cbi daten, rs ; loescht RS-Bit nochmals direkt
nop ; Stabilisierungspause
out daten, temp1 ; Ausgabe unteres Nibble auf D0-D3
nop ; Stabilisierungspause
rcall enable ; zweiter Enableimpuls kommt
nop
pop temp1
pop temp ; Wiederherstellung von temp, temp1
rcall verz3 ; kurze Pause (kann auch entfallen)
ret
;
enable: ; Enableimpuls-Generierung:
;
nop
sbi daten, ena ; Enable auf "high" setzen
nop ; Zeitverzoegerung mindestens
nop ; 1000 Nanosekunden laut Datenblatt
nop ; lieber ein paar "nops" mehr
nop ; spendieren
nop
nop
nop
nop
nop
nop
nop
nop
cbi daten, ena ; Enable auf "low" zurücksetzen
nop
ret
;
verz3: ; ca. 20 ms Verzoegerung:
;
push temp ; Sicherung von Temporaerregistern...
push temp1 ; und Statusregister auf Stapel
push temp2
in temp2, SREG ; kann hier auch entfallen,
push temp2 ; bei Interrupts notwendig
clr xxl ; Loeschen von Registerpaar r24:r25
clr xxh
ldi xxl, low(zeit1) ; laedt Zaehlregisterpaar
ldi xxh, high(zeit1) ; mit oben deklariertem Wert...
rjmp v_schleife ; ..."zeit1"
;
v_schleife: ; 16-Bit-Subtraktionsschleife:
;
sbiw xxl:xxh, 1 ; decrementiert Doppelregister
brne v_schleife ; zaehlt bis Null
pop temp2 ; Wiederherstellung
out SREG, temp2 ; von "geretteten" Registern
pop temp2
pop temp1
pop temp
ret
;
verz4: ; ca. 200 ms Verzoegerung:
;
push temp ; Sicherung auf Stapel
push temp1
push temp2
in temp2, SREG
push temp2
clr xxl ; Loeschung Registerpaar
clr xxh
ldi xxl, low(zeit2) ; laedt Zaehlregisterpaar
ldi xxh, high(zeit2) ; mit deklariertem Wert...
rjmp v_schleife_a ; ..."zeit2"
; ; Sprung auf "v_schleife_a"
v_schleife_a: ; 16-Bit-Subtraktionsschleife (a):
;
sbiw xxl:xxh, 1 ; decrementiert Doppelregister
brne v_schleife_a ; zaehlt bis Null
rjmp verz4_a ; Sprung zum zweiten Durchlauf...
; ; "verz4_a",verdoppelt Verzoegerung
verz4_a:
;
clr xxl ; Loeschung Doppelregister
clr xxh
ldi xxl, low(zeit3) ; laedt Zaehlregisterpaar...
ldi xxh, high(zeit3) ; ...mit Wert "zeit3"
rjmp v_schleife_b ; Sprung auf "v_schleife_b"
;
v_schleife_b: ; 16-Bit-Subtraktionsschleife (b):
;
sbiw xxl:xxh, 1 ; decrementiert Doppelregister
brne v_schleife_b ; zaehlt bis Null
pop temp2 ; Wiederherstellung "geretteter"
out SREG, temp2 ; Register
pop temp2
pop temp1
pop temp
ret
; FIN
---------------------------------------------
Zum Seitenanfang
-------------------------------------------------------
Hinweise laut Copyright und Sicherheitsbestimmungen:
ATMEL ist ein eingetragenes Warenzeichen.
Weiterführende Informationen und Updates bei:
http://www.atmel.com
BASCOM ist eine mit C
verwandte, gerade bei "Einsteigern" sehr beliebte
Programmiersprache.
Während der vollständige Code für eine LCD-Initialisierung
in Assembler doch recht unübersichtlich anmutet,
scheint dasselbe Ergebnis mit Hilfe eines BASCOM-Programmes
relativ einfach gestrickt erreichbar zu sein, da "lästige"
Syntax-Ausformulierungen entfallen, hernach der Compiler
diese Übersetzungs-Aufgaben erledigen sollte bei entsprechender
Umsetzung in ein flashbares File.
"Sollte" deswegen, weil, wie bereits oben angedeutet, die Displays oft
nach "Extrawürsten" verlangen, gerade, was das "Timing" anbelangt.
Ein hier n i c h t getestetes BASCOM-File zur
LCD-Initialisierung sieht in etwa folgendermaßen aus:
$regfile = "2313def.inc"
$crystal = 4000000
Ddrd = &HFE
Config Lcdpin = Pin , Db4 = Portd.4 , Db5 = Portd.5 , Db6 = Portd.6 ,
Db7 = Portd.7 , E = Portd.3 , Rs = Portd.2
Config Lcd = 16 * 2
' Init
Ddrb.1 = 1
Cls
Cursor Noblink
Cursor Off
Lcd "LCD-Modell"
Print "Hallo"
' Main Loop
Do
Waitms 100
Loop
End
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
Die vorliegenden Assemblerprogrammbeispiele sind testweise von diesem
Browserfenster aus per "Copy and Paste" direkt in den ATMEL-Studio4-Editor
geladen und danach einwandfrei assembliert worden. Hier und bei
anderen Usern funktionieren sie bestimmungsgemäß - siehe auch
Forenbeiträge http://www.avr-praxis.de
Diese obigen Beispiele sind ohne vorherige Rücksprache mit dem Autor dieser
Homepage ausschließlich zur nichtkommerziellen Nutzung durch
den Elektronik-Interessierten frei verwendbar.
Es wird aber auch auf die im Impressum
aufgeführten Punkte (Disclaimer) verwiesen.-
Gefahrenhinweis:
LCDisplays (das Glas) dürfen keinesfalls irgendwie (gewaltsam)
geöffnet werden, da sie eine giftige Flüssigkeit enthalten.
Auf die Maßnahmen zur Verhinderung elektrostatischer Entladungen
im Umgang mit elektronischen Teilen möchte der Autor dieser Homepage
noch der Vollständigkeit halber hinweisen.
Dann kann ja eigentlich nichts mehr schief gehen.
Für Feedback wäre der Autor aber immer dankbar.
Email-Kontakt findet man im Impressum.
Initialisierung
Vierbitmodus
Zum Seitenanfang