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