Pfad: Home => AVR-DE => Anwendungen => LCD-Ansteuerung mit AVR
LCD klein AVR-Anwendungen

LCD-Ansteuerung mit AVR in Assembler
Logo

Eine LCD an einem AVR

lcd.inc bei der Arbeit Die ultimative Ansteuerungs-Software für eine LCD an einem AVR - funktioniert universell und ist einfach an jeden Bedarf anzupassen.
  1. Hardware
  2. Einstellung
  3. Software
Diese Seiten als PDF-Dokument herunterladen (50 Seiten, 1,4 MB).

1 Hardware

1.1 Anschlüsse und Betriebsarten der LCD

LCD-Anschluesse Jede LCD hat drei Kontrolleingänge und acht bidirektionale Datenbus-Ein- und -Ausgänge.

Die drei Kontrolleingänge bewirken folgendes:
  1. E-Eingang (Enable): Dieser Eingang wird vom AVR für eine Mikrosekunde lang auf logisch Eins gezogen, um einen Schreib- oder Lese-Vorgang auszulösen. Danach wird er wieder auf Logisch Null gezogen.
  2. RS-Eingang (Register Select): Ist dieser Eingang logisch Null, wird ein Kontrollbefehl an die LCD gesendet. Bei logisch Eins werden Daten (z. B. Zeichen) gelesen oder geschrieben.
  3. RW-Eingang (Read/Write): Bei logisch Eins werden Daten von der LCD gelesen (die LCD treibt den Datenbus), bei logisch Null werden Daten in die LCD geschrieben (der AVR treibt den Datenbus).
Die Datenbus-Ein- und Ausgänge der LCD können im 8-Bit- und im 4-Bit-Modus betrieben werden. Im 8-Bit-Modus werden bei jedem Schreib- und Lesevorgang jeweils 8 Bits übertragen. Die Umschaltung auf den 4-Bit-Modus erfolgt mit einem Kontrollbefehl (siehe unten bei Init). Ist dieser an die LCD gesendet, dann kommuniziert die LCD mit dem AVR nur noch über die oberen vier Datenbusanschlüsse. Bei jedem Schreib- und Lesevorgang wird dann zuerst das obere Nibble (4 Bits) übertragen, danach das untere Nibble. Das erfordert zwei Aktivierungen des E-Einganges. Die vier Bits können AVR-seitig entweder in den oberen vier Pins eines Ports oder in den unteren Bits liegen.

1.2 Busy- und Wait-Modus

Wird der RW-Eingang der LCD dauerhaft auf logisch Null gelegt, dann lassen sich keine Daten mehr aus der LCD auslesen (z. B. die Busy-Flagge, die anzeigt, dass die LCD empfangsbereit ist). Das Programm muss dann mit Warteschleifen (Wait) dafür sorgen dass der nächste Befehl oder das nächste Datum erst dann wieder an die LCD gesendet wird, wenn für die Verarbeitung des vorhergehenden genügend Zeit abgelaufen ist.

Die Einstellung, ob Busy- oder Wait-Modus verwendet werden soll, muss im Programm mit der Konstante LcdWait erfolgen. Ist diese Eins, wird mit Wartezyklen gearbeitet, Ist sie Null, dann wird Busy ausgelesen und gewartet, bis dies Null ist.

1.3 Anschlussarten

Eine LCD kann auf sechs verschiedene Arten an den AVR angeschlossen werden. Die Varianten ergeben sich folgenden Entscheidungen: Diese Varianten sehen dann so aus:
  1. Mit acht Datenbits und dem Read/Write-Kontrollpin R/W:

    LCD 8-Bit mit Busy Für diese Betriebsart werden 11 Portpins des AVR benötigt. Der Betrieb des Datenbus erfolgt bidirektional. Vor jedem Schreibvorgang in die LCD wird der Zustand der Busy-Flagge in der LCD abgefragt und erst dann geschrieben, wenn diese Null ist.
  2. Mit acht Datenbits und ohne Read/Write-Kontrollpin R/W:

    LCD 8-Bit mit Wait Der Kontrollpin R/W liegt dauerhaft auf logisch Null. Für diese Betriebsart werden 10 Portpins des AVR benötigt. Der Datenbus wird unidirektional betrieben. Nach jedem Schreibvorgang wird für eine bestimmte Zeit lang gewartet, bis der Befehl verarbeitet worden ist.
  3. Mit vier Datenbits und dem Read/Write-Kontrollpin R/W, der Datenbus am oberen Nibble des AVR-Ports:

    LCD 4-Bit mit Busy am oberen Nibble Für diese Betriebsart werden 7 Portpins des AVR benötigt. Der Betrieb des Datenbus erfolgt bidirektional. Vor jedem Schreibvorgang in die LCD wird der Zustand der Busy-Flagge in der LCD abgefragt und erst dann geschrieben, wenn diese Null ist.
  4. Mit vier Datenbits und dem Read/Write-Kontrollpin R/W, der Datenbus am unteren Nibble des AVR-Ports:

    LCD 4-Bit mit Busy am unteren Nibble Für diese Betriebsart werden 7 Portpins des AVR benötigt. Der Betrieb des Datenbus erfolgt bidirektional. Vor jedem Schreibvorgang in die LCD wird der Zustand der Busy-Flagge in der LCD abgefragt und erst dann geschrieben, wenn diese Null ist.
  5. Mit vier Datenbits ohne Read/Write-Kontrollpin R/W, der Datenbus am oberen Nibble des AVR-Ports:

    LCD 4-Bit mit Wait am oberen Nibble Für diese Betriebsart werden 6 Portpins des AVR benötigt. Der Betrieb des Datenbus erfolgt unidirektional. Nach jedem Schreibvorgang in die LCD wird gewartet, bis die LCD-Operation beendet ist.
  6. Mit vier Datenbits ohne Read/Write-Kontrollpin R/W, der Datenbus am unteren Nibble des AVR-Ports:

    LCD 4-Bit mit Wait am unteren Nibble Für diese Betriebsart werden 6 Portpins des AVR benötigt. Der Betrieb des Datenbus erfolgt unidirektional. Nach jedem Schreibvorgang in die LCD wird gewartet, bis die LCD-Operation beendet ist.
Die Entscheidung, welche der Varianten gewählt wird, kann sich an orientieren.

1.4 Kontroll- und Datenbefehle von LCDs

1.4.1 Kontrollbefehle

Die folgenden Befehle sind an die LCD absetzbar, wenn beim Schreiben
  1. der RS-Eingang auf Low steht,
  2. der RW-Eingang ebenfalls auf Low steht, und
  3. der E-Eingang für eine Mikrosekunde lang auf High gezogen und danach wieder auf Low geht.
Bits mit x in der Tabelle sind egal (0 oder 1). Bitte bei der Tabelle beachten, dass die angegebene Reihenfolge der Befehle nicht mit der Reihenfolge bei der Initialisierung übereinstimmt (siehe 2 Einstellung)!
BefehlDatenbyteAbkürzungenDauer
D7D6D5D4D3D2D1D0
Lösche Anzeige 00000001 - 1,52/1,64 ms
Rücksetzen 0000001x - 1,52/1,64 ms
Eingabemodus 000001I/DS I/D: Cursorrichtung 0=links, 1=rechts
S: Anzeige bei Eingabe verschieben 0=nein, 1=ja
37/40 µs
Anzeige Ein/Aus 00001DCB D: Anzeige 0=Aus, 1=Ein
C: Cursor 0=Aus 1=Ein
B: Cursor blinken 0=Aus 1=Ein
37/40 µs
Cursor verschieben 0001S/CR/Lxx S/C: 0=Cursor verschieben, 1=Display verschieben
R/L: 0=nach links, 1=nach rechts
37/40 µs
Funktionseinstellung 001DLNFxx DL: Datenbus 0=4 Bit, 1=8 Bit
N: Zeilen 0=1 Zeile, 1=2 oder 4 Zeilen
F: 0=Font 5x8 1=Font 5*10
37/40 µs
Setze Zeichengenerator RAM-Adresse 01a5a4a3a2a1a0 a5:a3 Zeichen 0 bis 7
a2:a0 Zeile 0 bis 7
37/40 µs
Setze Anzeige RAM-Adresse 1a6a5a4a3a2a1a0 a6:a0 Adresse, 0x80=Zeile 1, 0xC0=Zeile 2
0x80+Zeichen/Zeile=Zeile 3, 0xC0+Zeichen/Zeile=Zeile 4
37/40 µs

1.4.2 Lesebefehle

Steht der RS-Eingang auf Low und der RW-Eingang auf High, dann erscheint der folgende Inhalt auf dem Datenbus der LCD:
VorgangDatenbyteAbkürzungenDauer
D7D6D5D4D3D2D1D0
Busy-Flagge und
Display-RAM-Adresse lesen
BFa6a5a4a3a2a1a0 BF: Busy-Flagge 0=Bereit, 1=Nicht bereit
a6:a0: Anzeige-Adresse
E: 1 µs

1.4.3 Zeichengenerator-Daten und Anzeigedaten schreiben

Mit dem Eingang RS auf High werden

2 Einstellung der LCD

Für die Inbetriebnahme der LCD nach dem ersten Einschalten des Geräts sind in dieser verschiedene Einstellungen vorzunehmen (Betriebsarten, Zeilenzahl, Ein- und Ausschalten, Cursor-Einstellungen, u.a.m. Dieses Kapitel zeigt, wie dies in Assembler erledigt werden kann.

Noch eine wichtige Anmerkung: In jedem Handbuch über LCDs stehen unterschiedliche Zeiten verzeichnet, die das Initiieren, die Ausführung von Befehlen und die Ausgabe von Zeichen braucht. Das liegt daran, weil die in den LCD verbauten Controller mit verschiedenen Taktfrequenzen arbeiten.

Zu den Assembler-Beispielen ist noch zu beachten, dass diese in vielfältiger Weise die .IF-Direktiven verwenden. Diese sind nur mit moderneren Assemblern wie dem ATMEL Assembler 2 oder gavrasm übersetzbar.

Hier wird zunächst das Senden von Signalen (Kontroll- und Daten-Bytes) an die LCD beschrieben (2.1 Enable-Impuls), der eine vorgeschriebene Dauer einhalten muss. Für den Betrieb mit Abfrage des 2.2 Busy-Flags wird ferner beschrieben, wie mit dessen Abfrage bei einem 2.2.1 8-Bit-Datenbus und einem 2.2.2 4-Bit-Datenbus erledigt werden kann. Für die Initiierung der LCD sowie für den Betrieb ohne Busy-Flag-Abfrage sind ferner 2.3 Warteroutinen nötig, die warten, bis die LCD ansprechbar ist. In 2.4 Init wird gezeigt, wie die LCD erstmalig angesteuert wird und wie danach die Umstellung auf 2.4.5 4-Bit-Betrieb funktioniert. Im Kapitel 2.5 Andere werden weitere Operationen beschrieben, die mit der LCD anzustellen sind.

2.1 Dauer des Enable-Impulses

Der Enable-Impuls muss mindestens eine Mikrosekunde lang sein, um der Spezifikation der LCD zu entsprechen.

Der Enable-Impuls kann in der Assembler-Software mit den beiden Instruktionen

.equ pLcdCEO = PORTB ; Ausgabeport, in dem sich das LCD-Kontrollbit E befindet
.equ bLcdCEO = PORTB0 ; Ausgabepin des LCD-Kontrollbits E
LcdPulseE:
  sbi pLcdCEO,bLcdCEO ; Setze Bit bLcdCEO im LCD-Kontrollport
  cbi pLcdCEO,bLcdCEO ; Loesche Bit bLcdCEO

erzeugt werden. Soll von der LCD gelesen werden, muss die Folge lauten:

LcdInE:
  sbi pLcdCEO,bLcdCEO ; Setze Portbit bLcdCEO im LCD-Kontrollport
  nop ; Warte eine Mikrosekunde
  in R16,pLcdDI ; Lese Datenbus nach R16
  cbi pLcdCEO,bLcdCEO ; Loesche Portbit bLcdCEO

Dies funktioniert nur bei einer Taktfrequenz von 1 MHz zuverlässig. Liegen höhere Taktfrequenzen vor, müssen entsprechende Wartezyklen eingefügt werden. Z. B. so (clock ist die Taktfrequenz in Hz):

LcdPulseE:
  sbi pLcdCEO,bLcdCEO ; Setze Bit bLcdCEO im LCD-Kontrollport
  .if clock>1000000
    nop
    .endif
  .if clock>2000000
    nop
    .endif
  .if clock>3000000
    nop
    .endif
  ; ... usw. und so fort
  cbi pLcdCEO,bLcdCEO ; Loesche Bit bLcdCEO

Damit ist der Enable-Impuls an die Taktfrequenz gebunden und hat immer die korrekte Dauer.

2.2 Auf das Busy-Flag warten

Ist der LCD-RW-Pin an den AVR angeschlossen, kann das Busy-Flag der LCD gelesen und können Schreibvorgänge vorgenommen werden, sobald dieses Flag von der LCD gelöscht ist.

Das Warten auf das Busy-Flag unterscheidet sich geringfügig beim 2.2.1 8-Bit-Modus und beim 2.2.2 4-Bit-Modus.

2.2.1 Busy-Flag im 8-Bit-Modus

Warte auf Busy, 8-Bit Die folgende Routine wartet im 8-Bit-Modus auf eine gelöschte Busy-Flagge. Da PUSH und POP verwendet werden, um die Inhalte von Registern zu sichern, muss der Stapel funktionieren.

; Warte bis Busy-Flagge der LCD geloescht
;   Verwendet R16, stellt dessen Inhalt aber wieder her
;   Ports und Portpins der LCD:
    .equ pLcdDI = PINA ; Lese-Port Datenbus
    .equ pLcdDD = DDRA ; Richtungsport Datenbus
    .equ pLcdCRSO = PORTB ; Port in dem sich der LCD-RS-Pin befindet
    .equ bLcdCRSO = PORTB1 ; Portpin des LCD-RS-Pins
    .equ pLcdCRSO = PORTB ; Port in dem sich der LCD-RS-Pin befindet
    .equ bLcdCRSO = PORTB1 ; Portpin des LCD-RS-Pins
;
LcdBusy8:
  push R16 ; Inhalt von R16 sichern
  clr R16 ; Datenbus-Richtung auf Input
  out pLcdDD,R16 ; Richtungsregister loeschen
  cbi pLcdCRSO,bLcdCRSO ; RS-Pin auf Low
  sbi pLcdCRWO,bLcdCRWO ; RW-Pin auf High
LcdBusyWarte:
  rcall LcdInE ; Aktiviere E, lese Datenport, deaktiviere E
  lsl R16 ; Busy-Flag in Carry schieben
  brcs LcdBusyWarte ; Flagge 1, weiter warten
  cbi pLcdCRWO,bLcdCRWO ; RW-Pin auf Low
  ldi R16,0xFF  Datenbus wieder auf Ausgang
  out pLcdDD,R16 ; Richtungsport alle auf High
  pop R16 ; R16 wieder herstellen
  ret ; Fertig


2.2.2 Busy-Flag im 4-Bit-Modus

Busy beim 4-Bit-Bus Im 4-Bit-Modus kann der Datenbus im oberen Nibble des Ports liegen (Konstante Lcd4High = 1) oder im unteren (Lcd4High = 0). Die folgende Routine funktioniert mit beiden Varianten.

LcdBusy4:
  push R16 ; Inhalt von R16 sichern
  in R16,pLcdDD ; Datenbus Richtungsregister lesen
  .if Lcd4High == 1
    andi R16,0x0F ; Oberes Nibble auf Null
    .else
    andi R16,0xF0 ; Unteres Nibble auf Null
    .endif
  out pLcdDD,R16 ; Richtungsregister Datenbus-Pins setzen
  cbi pLcdCRSO,bLcdCRSO ; RS-Pin auf Low
  sbi pLcdCRWO,bLcdCRWO ; RW-Pin auf High
LcdBusyWarte:
  rcall LcdInE ; Aktiviere E, lese Datenport, deaktiviere E
  rcall LcdPulseE ; Unteres Nibble pulsen, aber nicht lesen
  .if Lcd4High != 1 ; Wenn am unteren Datenport angeschlossen
    swap R16 ; Unteres in oberes Nibble tauschen
    .endif
  lsl R16 ; Busy-Flag in Carry schieben
  brcs LcdBusyWarte ; Flagge 1, weiter warten
  cbi pLcdCRWO,bLcdCRWO ; RW-Pin auf Low
  in R16,pLcdDD ; Lese Datenbus Richtungsregister
  .if Lcd4High == 1
    ori R16,0xF0 ; Oberes Nibble auf Eins setzen
    .else
    ori R16,0x0F ; Unteres Nibble auf Eins
    .endif
  out pLcdDD,R16 ; Richtungsport LCD-Pins auf High
  pop R16 ; R16 wieder herstellen
  ret ; Fertig

Bitte beachten: befindet sich der Datenport im oberen Nibble des AVR-Ports, dann ist das eingelesene Bit 7 das Busy-Flag. Ist der Datenport an das untere Nibble angeschlossen, dann befindet es sich in Bit 3 des eingelesenen Datenbytes und muss dort abgefragt werden oder auch mit swap R16 in das obere Nibble von R16 verschoben werden.

2.3 Wartezyklen einfügen

Bei allen Betriebsarten sind Wartezyklen nötig:
  1. Vor der ersten Ansteuerung sind 50 ms zu warten, bis die LCD ihre Initiierung abgeschlossen hat.
  2. Nach der Umschaltung in den 8- oder 4-bit-Modus sind 5 ms zu warten.
Bei der Konfiguration ohne RW-Pin sind weitere Wartezyklen nötig:
  1. 1,64 ms für den Return/Home-Befehl,
  2. 40 µs für alle anderen Operationen.
In Abhängigkeit davon, ob der Pin bLcdCRWO definiert ist, werden auch die Wartezyklen 3 und 4 zur Verfügung gestellt.

Zur Erzeugung der Verzögerung wird
  1. das Registerpaar ZH:ZL mit einer Zahl geladen,
  2. mit SBIW ZL,1 abwärts gezählt, und
  3. bei Erreichen von Null zurückgekehrt.
Damit die Warteroutine mit verschiedenen Taktfrequenzen kompatibel ist, wird die Anzahl Wartezyklen aus der Taktfrequenz clock errechnet. Die Routine

; Warteroutinen
;
; 50 ms lang warten
;   Da bei Taktfrequenzen oberhalb von 3,1 MHz die
;   maximale Dauer der Z-Schleife ueberschritten
;   wuerde, wird 10 mal die 5ms-Routine aufgerufen
LcdWait50ms:
  push R16 ; 10 mal 5 ms aufrufen
  ldi R16,10
  rcall LcdWait5ms
  dec R16
  brne LcdWait50ms1
  pop R16
  ret
;
LcdWait5ms:
  ; 5 ms warten
  .equ cLcdZ5ms = (5*clock/1000 - 10 + 2) / 4
  ldi ZH,High(cLcdZ5ms)
  ldi ZL,Low(cLcdZ5ms)
  rjmp LcdWaitZ
;
.if LcdWait == 1 ; Wartemodus, mit Wait-Zyklen
  ; 1,6 ms warten
  .equ cLcdZ1600us = (1600*clock/1000000 - 10 + 2) / 4
  LcdWait1600us:
    ldi ZH,High(cLcdZ1600us)
    ldi ZL,Low(cLcdZ1600us)
    rjmp LcdWaitZ
  ;
  ; 40 us warten
  LcdWait40:
    ldi ZH,High(cLcdZ40us)
    ldi ZL,Low(cLcdZ40us)
    rjmp LcdWaitZ
  .endif
;
; Warteroutine mit Z Zyklen
LcdWaitZ:
  sbiw ZL,1 ; Abwaerts zaehlen, 2 Takte
  brne LcdWaitZ ; Nicht Null, weiter, 2 Takte bei Sprung, 1 Takt am Ende
  ret ; Zurueck, 4 Takte
;
  ; Anzahl Takte = 7 * 4 + (Z - 1) + 3 + 4
  ;                7: RCALL, LDI, LDI, RJMP
  ;                4: 4 Takte pro Z-Zyklus (minus 1)
  ;                3: 3 Takte fuer letzten Zyklus
  ;                5: RET 
  ;              = 4 * Z + 7 - 4 + 3 + 4
  ;              = 4 * Z + 10
  ; Z = (Anzahl Takte - 10 + 2) / 4
  ;                +2: Aufrunden vor Teilen durch 4

Eine denkbare Variante der Warteroutinen ist es, das Registerpaar ZH:ZL vor der Verwendung durch die Warteroutine zu sichern und nach deren Beendigung wieder herzustellen. Dann verändert sich die Berechnung von Z aus der Taktzahl (Frequenz) geringfügig.

5 ms Warten bei 2,45768 MHz 5 ms Warten bei 20 MHz Durch die Berechnung von Z aus der Taktfrequenz erzeugt die Warteroutine korrekte Verzögerungen unabhängig von der Taktfrequenz. Die simulierten Zeiten (mit avr_sim) sind für zwei sehr unterschiedliche Taktfrequenzen für 5 ms hier zu sehen.

2.4 Initiierung der LCD

Init der LCD Bevor die LCD angesprochen werden kann, müssen die mit der LCD verbundenen Portanschlüsse initiiert werden. Das betrifft die zwei bzw. drei Anschlüsse der Kontrollpins sowie die Anschlüsse zum Datenbus. Zuerst sind die Kontrollanschlüsse dran. Dann kommen die 2.4.2 Datenbus-Anschlüsse, die für 2.4.2.1 8-Bit-Betrieb etwas anders als für 2.4.2.2 4-Bit-Betrieb zu initiieren sind.

2.4.1 Kontrollanschlüsse

Zu konfigurieren sind
  1. der E-Anschluss,
  2. der RS-Anschluss,
  3. der RW-Anschluss, sofern der Betriebsmodus mit Abfrage des Busy-Flags erfolgen soll.
Damit die drei Anschlüsse in verschiedenen Ports liegen dürfen können, kriegen die Ports PORTn und DDRn der Kontrollanschlüsse symbolische Namen: Darin sind
  1. p Port-Symbole,
  2. Lcd solche mit LCD-Anschluss,
  3. C Kontrollanschlüsse,
  4. E, RS und RW die drei Kontrollpins, und
  5. O Output- und D Richtungs-Ports.
Um maximale Flexibilität zu haben, sind auch die Pins mit symbolischen Namen definiert. Sie beginnen mit b für Portbit und beziehen sich auf die Bits PORTnb (z. B. .equ bLcdCEO = PORTB2) oder DDnb, z. B. .equ bLcdCED = DDB2.

Die Symbole sind zwar etwas mehr Schreibarbeit, steigern aber die Nachvollziehbarkeit des Codes und haben sich in der Praxis als sehr sinnvoll erwiesen. Damit ergibt sich der folgende Code zum Initiieren der Kontrollanschlüsse:

  ; Init Control pins
  cbi pLcdCEO,bLcdCEO ; E Output Low
  sbi pLcdCED,bLcdCED ; E Richtung High
  cbi pLcdCRSO,bLcdCRSO ; RS Output Low
  sbi pLcdCRSD,bLcdCRSD ; RS Richtung High
  .ifdef bLcdCRWO ; Nur wenn der RW-Pin definiert ist
    cbi pLcdCRWO,bLcdCRWO ; RW Output Low
    sbi pLcdCRWD,bLcdCRWD ; RW Richtung High
    .endif

2.4.2 Datenbus-Init

Das Init des Datenbus erfolgt bei 2.4.2.1 8-Bit-Betrieb etwas anders als beim 2.4.2.2 4-Bit-Betrieb.

2.4.2.1 Datenbus-Init 8-Bit-Betrieb

Beim 8-Bit-Datenbus werden
  1. die Datenport-Ausgänge in PORTn (mit n=A, B, C, etc.) auf Null gelegt, und
  2. die Richtungs-Ausgänge in DDRn auf Eins gelegt.
Die Richtung ist daher defaultmäßig vom Prozessor zur LCD.

Schreibt man die Routine für eine ganz bestimmte Hardware, kann man natürlich die PORTn und DDRn als Portnamen direkt verwenden. Soll die Software an verschiedene Konfigurationen anpassbar sein, kriegen die beiden Ports einen symbolischen Alias-Namen, wie oben beschrieben.

In dieser Darstellung (und auch bei der in 3 Software zur Verfügung gestellten LCD-Include-Routine) kommt als 8-Bit-Register R16 zum Einsatz. Damit lautet die Init-Routine für den 8-Bit-Datenbus:

  ; Init 8-Bit-Datenbus
  clr R16 ; Portausgaenge auf Low
  out pLcdDO,R16 ; Output auf Low
  ldi R16,0xFF ; Richtungsport auf High
  out pLcdDD,R16 ; Richtung auf High

2.4.2.2 Datenbus-Init 4-Bit-Betrieb

Beim 4-Bit-Betrieb müssen die vier nicht mit dem LCD-Datenbus verbundenen Bits in den Ports pLcdDO und pLcdDD beim Init so erhalten bleiben, wie sie vor dem Init waren, damit andere Programmteile nicht gestört werden. Ob der LCD-Datenbus mit dem oberen oder unteren Nibble des Ports verbunden ist, steht in der Konstante "Lcd4High": ist sie Eins, liegt er am oberen Portnibble (Pn4..Pn7), bei Null am unteren (Pn0..Pn3). Der Code lautet dann so:

  ; Init 4-Bit-Datenbus
  in R16,pLcdDO ; Output-Port lesen
  .if Lcd4High == 1
    andi R16,0x0F ; Unteres Nibble erhalten, oberes Null
    .else
    andi R16,0xF0 ; Oberes Nibble erhalten, unteres Null
    .endif
  out pLcdDO,R16 ; Output-Port schreiben
  in R16,pLcdDD ; Richtungs-Port lesen
  .if Lcd4High == 1
    ori R16,0xF0 ; Oberes Nibble High setzen
    .else
    ori R16,0x0F ; Unteres Nibble High setzen
    .endif
  out pLcdDD,R16 ; Richtungs-Port schreiben

2.4.3 Warten für 50 ms

Da davon ausgegangen werden muss, dass sowohl der Prozessor als auch die LCD beim Programmbeginn erstmals unter Strom stehen, ist jetzt erstmal Abwarten angesagt, denn die LCD-Mimik muss sich erst mal sortieren. Es ist auch noch keine Abfrage des Busy-Flags möglich. Eine ausreichende Wartedauer auch für ältere LCDs sind 50 ms.

Hier kann die in 2.3 Wartezyklen beschriebene 50 ms Wartezeit verwendet werden.

2.4.4 Einstellung der LCD auf 8-Bit-Betrieb

Alle LCDs, auch wenn sie in 4-Bit-Manier betrieben werden sollen, müssen nach dem Warten erst mal wieder auf 8-Bit-Betrieb umgeschaltet werden. Falls die LCD aus einem vorherigen Init-Zyklus auf 4-Bit-Betrieb umgestellt war, stellt die folgende Routine erst mal den Grundzustand wieder her.

Man erreicht das bei einem 8-Bit-Datenbus, indem man
  1. den RS-Pin auf Low legt,
  2. 0x30 auf den Datenbus legt,
  3. den E-Eingang für eine Mikrosekunde lang High macht (danach wieder Low), und
  4. 1,6 ms lang wartet.
Beim 4-Bit-Datenbus muss das LCD-Datenbus-Nibble (entweder das obere oder untere) auf 0x3 stehen, die nicht gesendeten vier Bits Nullen werden von der LCD von den offenen Eingängen übernommen, die per Default auf Low liegen (E jetzt noch nicht doppelt aktivieren, denn wir wollen ja in den 8-Bit-Modus!). Macht man das Ganze drei oder viel Mal, sind wir ganz sicher im 8-Bit-Modus.

Der Code dazu im 8-Bit-Modus sieht so aus:

  ; Umschaltung auf 8-Bit-Betrieb im 8-Bit-Modus
  cbi pLcdCRSO,bLcdCRSO ; RS-Pin Low
  ldi R16,0x30 ; 8-Bit-Modus
  out pLcdDO,R16 ; an Datenbus
  rcall LcdPulseE ; E-Pin fuer eine us lang aktivieren
  rcall LcdWait1600us ; 1,6 ms lang warten

Die beiden letzten Instruktionen sind drei bis vier mal zu wiederholen.

Im 4-Bit-Modus sieht der Code so aus:

  ; Umschaltung auf 8-Bit-Betrieb im 4-Bit-Modus
  cbi pLcdCRSO,bLcdCRSO ; RS-Pin Low
  in R16,pLcdDO ; Output-Port lesen
  .if Lcd4High == 1
    andi R16,0x0F ; Oberes Nibble loeschen
    ori R16,0x30 ; Oberes Nibble auf 0x3
    .else
    andi R16,0xF0 ; Unteres Nibble loeschen
    ori R16,0x03 ; Unteres Nibble auf 0x3
    .endif
  out pLcdDO,R16 ; an Datenbus
  rcall LcdPulseE ; E-Pin fuer eine us lang aktivieren
  rcall LcdWait1600us ; 1,6 ms lang warten


2.4.5 Umstellung der LCD vom 8-Bit- auf 4-Bit-Betrieb

Umschaltung von 8- auf 4-Bit-Datenbus Dieser Teil kommt nur dann zum Einsatz, wenn die LCD im 4-Bit-Modus betrieben werden soll.

Der obige Code kommt erneut zum Einsatz, nur mit ori R16,0x20 anstelle von ori R16,0x30. Es wird aber nicht mehrfach gesendet, sondern nur ein einziges Mal. Danach ist das Interface umgestellt und alle Befehls- und Datenausgaben sowie auch alle Lesebefehle erfordern zwei Aktivierungen des Enable-Eingangs: zuerst wird das obere Nibble geschrieben oder gelesen, dann das untere.

2.4.6 Systemset der LCD

Nach der Umschaltung in den 8-Bit-Modus bzw. der Umschaltung in den 4-Bit-Modus erfolgt der Systemset. Er konfiguriert neben der Datenbusbreite auch die Anzahl der LCD-Zeilen. Wie ab jetzt üblich, unterscheiden sich 2.4.6.1 Systemset 8-Bit und 2.4.6.2 Systemset 4-Bit etwas und werden der Klarheit halber getrennt dargestellt.

2.4.6.1 Systemset der LCD im 8-Bit-Modus

Systemset 8 Bit Im 8-Bit-Modus ist mit der Einstellung LcdWait zu unterscheiden, ob Warte- oder Busy-Flag-Abfragebetrieb vorliegt. Falls Busy-Betrieb eingestellt ist, muss auf Busy = Low gewartet werden. Im Wartebetrieb geht es erst mal gleich weiter, das Warten erfolgt erst danach.

Dann wird das Systemset-Byte gesendet. Es besteht aus Es ist mit RS=Low als Kontrollwort zu senden und der E-Pin für eine Mikrosekunde lang zu aktivieren.

Ist Wartebetrieb gewählt, sind nun 1,64 ms lang abzuwarten, bis die LCD das ausgeführt hat. Daraus ergibt sich der folgende Code für die Ausgabe von Systemset im 8-Bit-Modus:

;
; Systemset8 gibt Systemset-Kontrollbyte
;   im 8-Bit-Modus an die LCD aus
;   LcdLines enthaelt die Anzahl Zeilen (1..4)
;     der LCD
SystemSet8:
  ldi R16,0x30 ; 8-Bit-Datenbus
  .if LcdLines == 2
     ori R16,0x08 ; Zweizeiliges Display
     else
     .if LcdLines == 4
      ori R16,0x0C ; Vierzeiliges Display
      .endif
    .endif
;
; LcdCtrl gibt Kontrollbyte in R16
;   im 8-Bit-Modus an LCD aus
LcdCtrl8:
  .if LcdWait == 0
    rcall LcdBusy ; Wenn kein Wartemodus auf Busy-Flagge warten
    .endif
  cbi pLcdCRSO,bLcdCRSO ; RS-Bit low
  out pLcdDO,R16 ; Eingabezahl auf Datenbus
  rcall LcdPulseE ; E aktivieren
  .if LcdWait == 1
    rjmp LcdWait1640us ; 1,64 ms warten
    .else
    ret
  .endif


2.4.6.2 Systemset der LCD vom 8-Bit- auf 4-Bit-Betrieb

Systemset 4 Bit Beim 4-Bit-Modus hat die erfolgte Umschaltung von 8- auf 4-Bit-Betrieb bereits einen Systemset vorgenommen. Allerdings war dabei die Anzahl Zeilen der LCD auf Eins gesetzt worden, weil das untere Nibble im Pseudo-8-Bit-Modus ja nicht übertragen werden konnte. Das ist nun nachzuholen und die LCD mit einem erneuten Systemset auf die korrekte Zeilenzahl umzustellen.

Ab hier, nach erfolgter Umschaltung auf 4-Bit-Datenbus, erfolgt im Busy-Betrieb das Warten auf die gelöschte Busy-Flagge, denn die kann nun ausgelesen werden. Bei Wartebetrieb erfolgt das nach dem Systemset.

Beim 4-Bit-Betrieb erfolgt der Systemset in zwei Portionen. Zuerst wird das obere Nibble mit 0x2 (4-Bit-Datenbus) mit RS=Low und RW=Low übertragen, danach erst das untere Nibble mit den Zeileneinstellungen.

Der entsprechende Code lautet:

;
; Systemset4 gibt Systemset-Kontrollbyte
;   im 4-Bit-Modus an die LCD aus
;   LcdLines enthaelt die Anzahl Zeilen (1..4)
;     der LCD
SystemSet8:
  ldi R16,0x20 ; 4-Bit-Datenbus
  .if LcdLines == 2
     ori R16,0x08 ; Zweizeiliges Display
     else
     .if LcdLines == 4
      ori R16,0x0C ; Vierzeiliges Display
      .endif
    .endif
;
; LcdCtrl gibt Kontrollbyte in R16
;   im 4-Bit-Modus an LCD aus
;   LcdHigh gibt an, ob die LCD am oberen
;     (1) oder am unteren Nibble (0)
;     angeschlossen ist
;   LcdWait gibt an, ob die LCD im
;     Wartemodus (1) oder im Busy-
;     modus (0) betrieben wird
LcdCtrl4:
  .if LcdWait == 0
    rcall LcdBusy4
    .endif
  cbi pLcdCRSO,bLcdCRSO ; RS-Bit low
  push ZL ; ZL retten
  push R16 ; R16 retten
  in ZL,pLcdDO ; Daten Output Port lesen
  .if Lcd4High == 1
    andi ZL,0x0F ; Oberes Nibble loeschen
    andi R16,0xF0 ; Oberes Nibble Eingabezahl loeschen
    .else
    andi ZL,0xF0 ; Unteres Nibble Output loeschen
    swap R16 ; Oberes und unteres Nibble vertauschen
    .endif
  or R16,ZL ; Kombinieren
  out pLcdDO,R16 ; und an Datenbus legen
  rcall LcdPulseE ; E aktivieren
  pop R16 ; R16 wieder herstellen
  push R16 ; und erneut sichern
  in ZL,pLcdDO ; Daten Output Port lesen
  .if Lcd4High == 1
    andi ZL,0x0F ; Unteres Nibble erhalten
    swap R16 ; Unteres und oberes Nibble vertauschen
    andi R16,0xF0
    .else
    andi ZL,0xF0 ; Oberes Nibble erhalten
    andi R16,0x0F ; Unteres Nibble erhalten
    .endif
  out pLcdDO,R16 ; Auf Datenbus legen
  rcall LcdPulseE ; E aktivieren
  pop R16 ; Eingabezahl wieder herstellen
  pop ZL ; ZL wieder herstellen
  .if LcdWait == 1
    rjmp LcdWait1640us ; 1,64 ms warten
    .else
    ret
    .endif


2.4.7 Init finalisieren

Init finalisieren Nach der Systemeinstellung können noch weitere Einstellungen der LCD vorgenommen werden.

Der Entry- oder Eingabe-Mode kann noch eingestellt werden. Er steht per Default von Rechts nach Links (japanisch) und kann mit 0x06 auf Links nach Rechts umgestellt werden.

Wer den Cursor mit Unterstrich anzeigen und/oder blinken lassen mag, kann dies mit der Display-Einstellung vornehmen.

Sollte nach dem Init im Display noch irgendein Zeichensalat herumgeistern hilft es, den Displayinhalt zu löschen. Ist der Wartemodus eingestellt, muss da noch eine Pause von 1,6 ms folgen.

Damit ist die Initialisierung abgeschlossen und es kann ans Eingemachte (das Ausgeben von Zeichen) gehen. Der Code dafür das Ausgeben lautet:

; Gibt das Zeichen A aus
  ldi R16,'A' ; Lade Zeichen A in R16
;
; LcdChar gibt das Zeichen in R16 auf dem Display aus
;   LcdWait gibt an, ob Warte- (1) oder Busy-(0)-Modus 
;   LcdBits gibt an, ob ein 4- oder 8-Bit-Datenbus vorliegt
;   Lcd4High gibt an, ob das Datenport-Nibble im oberen
;     (1) oder im unteren Nibble (0) liegt
LcdChar:
  .if LcdWait == 0
    rcall LcdBusy ; Warte auf LCD
    .endif
  sbi pLcdCRSO,bLcdCRSO ; RS-Pin High
  .if LcdBits == 4
    push ZL ; ZL retten
    push R16 ; R16 retten
    in ZL,pLcdDO ; Output-Port lesen
    .if Lcd4High == 1
      andi ZL,0x0F ; Oberes Nibble loeschen
      andi R16,0xF0 ; Unteres Nibble loeschen
      .else
      andi ZL,0xF0 ; Unteres Nibble loeschen
      andi R16,0x0F ; Oberes Nibble loeschen
      .endif
    or R16,ZL ; Kombinieren
    out pLcdDO,R16 ; auf Datenbus legen
    rcall LcdPulseE ; E aktivieren
    pop R16 ; R16 wieder herstellen
    in ZL,pLcdDO ; Output-Port lesen
    andi R16,0x0F ; Oberes Nibble loeschen
    .if Lcd4High == 1
      andi ZL,0x0F ; Oberes Nibble loeschen
      swap R16 ; Unteres und oberes Nibble tauschen 
      .else
      andi ZL,0xF0 ; Unteres Nibble loeschen
      .endif
    pop R16 ; R16 wieder herstellen
    pop ZL ; ZL wieder herstellen
    .endif
  out pLcdDD,R16 ; Auf Datenbus legen
  rcall LcdPulseE ; E aktivieren
  .if LcdWait == 1
    rjmp LcdWait40us ; 40 us warten
    .else
    ret
    .endif

Das klingt auf den ersten Blick etwas kompliziert, funktioniert aber in allen Anwendungsfällen korrekt, weil alle Varianten in einer einzigen Routine mit abgedeckt sind, die erst vom Assembler in für den Anwendungsfall lauffähigen optimalen Code verwandelt wird.

2.5 Ausgabeposition setzen

Position setzen Häufig kommt es vor, dass man eine ganz bestimmte Position auf der LCD ansteuern will, um genau dort eine Ausgabe zu tätigen. Diese Adressänderung ist etwas eigenwillig konstruiert.

Der Beginn der ersten und der zweiten Zeile, jeweils mit der ersten Spalte, ist noch übersichtlich: es ist 0x00 und 0x40. Zusammen mit dem obersten Bit, 0x80, das die Display-Adresse als Ziel auswählt, ist daher 0x80 für die erste Zeile und 0xC0 für die zweite Zeile auszugeben. Die einzelnen Spalten in beiden Zeilen sind dann 0x80, 0x81, etc. bzw. in Zeile 2 0xC0, 0xC1, etc.

Verrückt wird es erst in den Zeilen 3 und 4, wenn die LCD diese hat. Die Display-RAM-Adressen ergeben sich durch Addieren der Anzahl Zeichen pro Zeile (N = 8, 16, 20 oder 24) zu der Grundadresse in Zeile 1 (0x80) bzw. Zeile 2 (0xC0).

Eine Assembler-Routine zum Setzen der Adresse könnte also lauten:

;
; Setze Position auf zweite Zeile und Spalte 8
LcdPos28:
  ldi ZH,2-1 ; Setze Zeile zwei
  ldi ZL,8-1 ; Setze Spalte acht
;
; LcdPos setzt den LCD Cursor an die Position in Z
;   ZH: Zeile (0 bis Anzahl Zeilen - 1)
;   ZL: Spalte (0 bis Anzahl Spalten - 1)
LcdPos:
  cpi ZH,1 ; Zeile = 1?
  ldi R16,0x80 ; Zeile 0
  .if LcdLines < 2 ; LCD hat nur eine Zeile
    rjmp LcdPos1
    .endif
  brcs LcdPos1
  ldi R16,0xC0 ; Zeile 2 bei zweizeiliger LCD
  .if LcdLines == 2
    rjmp LcdPos1
    .endif
  breq LcdPos1
  ldi R16,0x80+LcdCols ; Zeile 3
  cpi ZH,2 ; Zeile = 3
  breq LcdPos1
  ldi R16,0xC0+LcdCols ; Zeile 4
LcdPos1:
  add R16,ZL ; Spaltenadresse addieren
  rjmp LcdCtrl

2.6 Eigene Zeichen erzeugen

Adressen und Daten des Zeichengenerators In jeder LCD sind die ersten acht Zeichen (Zeichen 0x00 bis 0x07) vom Anwender definierbar und können für eigene Zeichen verwendet werden.

Das Zeichengenerator-RAM der acht Zeichen ist so aufgebaut:
  1. Die Adresse umfasst sechs Bits: Beim Schreiben der Adresse sind die beiden Bits 0b01 voranzustellen und das Byte mit RS=Low an die LCD zu senden.
  2. Die Daten umfassen fünf Bits. Einsen beleuchten das Pixel, Nullen stellen das Pixel auf die Hintergrundfarbe. Die Datenbits werden mit vorangestelltem 0b000 und mit RS=High an die LCD gesendet. Ist die Cursor-Richtung I/D beim Initiieren auf Increase eingestellt, können die acht Bytes eines Zeichens nacheinander ausgegeben werden, ohne deren Adresse voranzustellen (Auto-Increment).
Um solche Spezialzeichen komfortabel zu senden, sollten diese in eine Tabelle geschrieben werden. Damit die Zeichen in beliebiger Anzahl und in beliebiger Reihenfolge in diese Tabelle geschrieben werden ist dieses Format hilfreich (das Dummy-Byte wurde hinzugefügt, um eine geradzahlige Anzahl an Bytes in jeder Tabellenzeile zu erreichen, damit der Assembler keine Null-Bytes einfügt):
AdresseDummyZeilen
12345678
Byte0ByteByteByteByteByteByteByteByte
Byte0ByteByteByteByteByteByteByteByte
... weitere Zeichen ...
Byte0ByteByteByteByteByteByteByteByte
00(Ende der Tabelle)
Um solche Tabellen komfortabel zu generieren, kann eine Tabellenkalkulation herangezogen werden. Hier gibt es solche Tabellengeneratoren mit Beschreibung. Die erzeugte Tabelle in der Tabellenkalkulation kann mit Copy und Paste direkt in der Quellcode kopiert werden.

Der folgende Code schreibt den Tabelleninhalt in die LCD:

;
; LcdSpec erzeugt Spezialzeichen auf der LCD
;   Z zeigt auf 2*Tabellenadresse
;   Tabellenformat:
;     1.Byte: Adresse des Zeichens 0b01zzz000,
;             0: Ende der Tabelle
;     2.Byte: Dummy
;     3. bis 10.Byte: Daten der Zeilen 1 bis 8
LcdSpec:
  push R0 ; R0 ist Zaehler
LcdSpec1:
  lpm R16,Z+ ; Lese Adresse des Zeichens
  tst R16 ; Ende der Tabelle?
  breq LcdSpec3 ; Ende Tabelle
  rcall LcdCtrl ; Adresse schreiben
  adiw ZL,1 ; Ueberlese Dummy
  ldi R16,8 ; 8 Byte pro Zeichen
  mov R0,R16 ; R1 ist Zaehler
LcdSpec2:
  lpm R16,Z+ ; Datenbyte lesen
  rcall LcdData ; Datenbyte ausgeben
  dec R0 ; Abwaerts zaehlen
  brne LcdSpec2 ; Weiter Daten ausgeben
  rjmp LcdSpec1 ; Naechstes Zeichen
LcdSpec3:
  pop R0 ; R0 wieder herstellen
  ldi ZH,0 ; Cursor Home
  ldi ZL,0
  rjmp LcdPos
;


Pfeilsymbole als Spezialzeichen Die folgende Tabelle erzeugt die Pfeilsymbole auf der LCD.

;
; Tabelle der Codezeichen
Codezeichen:
.db 64,0,0,12,6,31,6,12,0,0 ; Z = 0, Pfeil rechts
.db 72,0,0,6,12,31,12,6,0,0 ; Z = 1, Pfeil links
.db 80,0,4,14,31,21,4,4,4,0 ; Z = 2, Pfeil hoch
.db 88,0,4,4,4,21,31,14,4,0 ; Z = 3, Pfeil runter
.db 96,0,0,15,3,5,9,16,0,0 ; Z = 4, Pfeil rechts hoch
.db 104,0,0,16,9,5,3,15,0,0 ; Z = 5, Pfeil rechts runter
.db 112,0,0,1,18,20,24,30,0,0 ; Z = 6, Pfeil links runter
.db 120,0,0,30,24,20,18,1,0,0 ; Z = 7, Pfeil links hoch
.db 0,0 ; Ende der Tabelle
;


Spezialzeichen Pfeile Und so sieht das Ganze dann auf der LCD aus.

3 Software

Die Software zur Ansteuerung aller LCDs

3.1 Quellcode

Den Assembler-Quellcode der inc-Datei gibt es hier zum Herunterladen und hier im HTML-Format für den Browser. Zum Assemblieren ist ein Assembler nötig, der .IF- und .ERROR-Direktiven beherrscht.

3.2 Parameter

3.2.1 Konstanten im Hauptprogramm

Zur Einstellung aller nötigen Parameter zum Einbinden der Include-Datei kann die Vorlage dienen, die ab Zeile 61 der Include vorgegeben ist. Für alle Parameter sind die Größen an den Anwendungsfall anzupassen und das Semikolon am Zeilenanfang zu entfernen.

; ***********************************
;  P A R A M E T E R - V O R L A G E
; ***********************************
;
; Standard-Parameter-Satz der Einstellungen
;.equ clock = 1000000 ; Taktfrequenz Prozessor in Hz
; LCD-Groesse:
  ;.equ LcdLines = 1 ; Anzahl Zeilen (1, 2, 4)
  ;.equ LcdCols = 8 ; Anzahl Zeichen pro Zeile (8..24)
; LCD-Ansteuerung
  ;.equ LcdBits = 4 ; Busbreite (4 oder 8)
  ; Wenn 4-Bit-Ansteuerung:
    ;.equ Lcd4High = 1 ; Busnibble (1=Oberes, 0=Unteres)
  ;.equ LcdWait = 0 ; Ansteuerung (0 mit Busy, 1 mit Warteschleifen)
; LCD-Datenports
  ;.equ pLcdDO = PORTA ; Daten-Ausgabe-Port
  ;.equ pLcdDD = DDRA ; Daten-Richtungs-Port
; LCD-Kontrollports und -pins
  ;.equ pLcdCEO = PORTB ; Control E Ausgabe-Port
  ;.equ bLcdCEO = PORTB0 ; Controll E Ausgabe-Portpin
  ;.equ pLcdCED = DDRB ; Control E Richtungs-Port
  ;.equ bLcdCED = DDB0 ; Control E Richtungs-Portpin
  ;.equ pLcdCRSO = PORTB ; Control RS Ausgabe-Port
  ;.equ bLcdCRSO = PORTB1 ; Controll RS Ausgabe-Portpin
  ;.equ pLcdCRSD = DDRB ; Control RS Richtungs-Port
  ;.equ bLcdCRSD = DDB1 ; Control RS Richtungs-Portpin
; Wenn LcdWait = 0:
  ;.equ pLcdDI = PINA ; Daten-Input-Port
  ;.equ pLcdCRWO = PORTB ; Control RW Ausgabe-Port
  ;.equ bLcdCRWO = PORTB2 ; Control RW Ausgabe-Portpin
  ;.equ pLcdCRWD = DDRB ; Control RW Richtungs-Port
  ;.equ bLcdCRWD = DDB2 ; Control RW Richtungs-Portpin
; Wenn Dezimalausgaberoutinen erwuenscht sind:
  ;.equ LcdDecimal = 1
; Wenn Hexadezimalausgabe erwuenscht ist:
  ;.equ LcdHex = 1
; Wenn nur Simulation im SRAM:
  ;.equ avr_sim = 1 ; 1=Simulieren, 0 oder undefiniert=Nicht simulieren
;

Die drei Schalter LcdDecimal, LcdHex und avr_sim können auskommentiert bleiben, wenn sie nicht zur Anwendung kommen sollen.

Ist der Port pLcdCRWO und der Portpin bLcdCRWO definiert, aber mit LcdWait = 1 Warten eingeschaltet, wird dieser Pin beim Init auf Low gesetzt, aber bei der Kontroll- und Zeichenausgabe nicht verwendet. Dadurch kann die Ansteuerung wahlweise mit Busy-Abfrage und Waitzyklen erfolgen und der Pin befindet sich in beiden Fällen auf korrektem Niveau.

Beim Assemblieren wird mit den aktuellen Einstellungen überprüft, ob alle nötigen Parameter angegeben und vorhanden sind. Fehlende Angaben werden mit Error-Messages bemängelt und führen zum Abbruch des Assemblierens durch Fehler.

3.3 Anwendung

Die Funktionen, die die Include-Datei zur Verfügung stellt, sind im Kopf der Datei näher beschrieben. Angegeben ist

;
; ***********************************************
; *  L C D   I N T E R F A C E  R O U T I N E N *
; ***********************************************
;
; +-------+----------------+--------------+
; |Routine|Funktion        |Parameter     |
; +-------+----------------+--------------+
; |LcdInit|Initiiert die   |Ports, Pins   |
; |       |LCD im einge-   |              |
; |       |stellten Modus  |              |
; +-------+----------------+--------------+
; |LcdText|Gibt den Text im|Z=2*Tabellen- |
; |       |Flash ab Zeile 1|  adresse     |
; |       |aus             |0x0D: Naechste|
; |       |                |      Zeile   |
; |       |                |0xFF: Ignorie-|
; |       |                |      re      |
; |       |                |0xFE: Textende|
; +-------+----------------+--------------+
; |LcdSram|Gibt den Text im|Z=SRAM-Adresse|
; |       |SRAM aus        |R16: Anzahl   |
; +-------+----------------+--------------+
; |LcdChar|Gibt ein Zeichen|R16: Zeichen  |
; |       |aus             |              |
; +-------+----------------+--------------+
; |LcdCtrl|Gibt Kontrollbe-|R16: Befehl   |
; |       |fehl aus        |              |
; +-------+----------------+--------------+
; |LcdPos |Setzt Ausgabe-  |ZH: Zeile 0123|
; |       |position        |ZL: Spalte 0..|
; +-------+----------------+--------------+
; |LcdSpec|Erzeugt Spezial-|Z: 2*Tabellen-|
; |       |zeichen         |   adresse    |
; +-------+----------------+--------------+
; | S C H A L T E R   L C D D E C I M A L |
; +-------+----------------+--------------+
; |LcdDec2|Gibt zwei Dezi- |R16: Binaer-  |
; |       |malstellen aus  |     zahl     |
; +-------+----------------+--------------+
; |LcdDec3|Gibt drei Dezi- |R16: Binaer-  |
; |       |malstellen aus  |     zahl     |
; +-------+----------------+--------------+
; |LcdDec5|Gibt fuenf Dezi-|Z: Binaerzahl |
; |       |malstellen aus  |   16 Bit     |
; +-------+----------------+--------------+
; |    S C H A L T E R   L C D H E X      |
; +-------+----------------+--------------+
; |LcdHex2|Gibt zwei Hexa- |R16: Binaer-  |
; |       |dezimalen aus   |     zahl     |
; +-------+----------------+--------------+
; |LcdHex4|Gibt vier Hexa- |Z: Binaerzahl |
; |       |dezimalen aus   |   16 Bit     |
; +-------+----------------+--------------+
;

Die Routinen sind so programmiert, dass sie die Register ZH und ZL sowie bei der Dezimalwandlung R2, R1 und R0 zwar verwenden, aber nach ihrer internen Verwendung wieder auf ihren Ausgangswert zurücksetzen. Ausnahmen sind ZH:ZL bei der Ausgaben von Texten. R16 wird ebenfalls verwendet und nicht immer auf den Ausgangswert rückgesetzt.

3.3.1 LCD initiieren

Mit rcall LcdInit kann die LCD initiiert werden. Die Routine wird einmalig beim Initiieren aufgerufen. Sie
  1. konfiguriert die Kontroll- und Datenbus-Ausgänge gemäß den angegebenen Ports und Portbits,
  2. wartet 50 ms, bis die LCD sich selbst initiiert hat,
  3. schaltet die LCD mehrfach in den 8-Bit-Modus,
  4. schaltet, falls dies gewählt ist, in den 4-Bit-Modus um,
  5. nimmt die Systemeinstellungen vor (Modus und Zeilenzahl),
  6. schaltet die LCD ein und Cursor- und Blinkeinstellung aus, und
  7. löscht die LCD.

3.3.2 Text auf der LCD ausgeben

Mit rcall LcdInit kann die LCD mit vordefiniertem Text aus einer Tabelle im Flash beschrieben werden. Die Es muss nicht der komplette Inhalt der Zeile überschrieben werden, mit 0x0D bleibt der nicht angegebene Rest der Zeile erhalten.

3.3.3 Text aus dem SRAM auf der LCD ausgeben

Mit rcall LcdSram wird der Text im SRAM, auf den das Registerpaar Z zeigt, auf der LCD ausgegeben. Es werden R16 Zeichen ausgegeben.

3.3.4 Ein Zeichen auf der LCD ausgeben

rcall LcdChar gibt das Zeichen in R16 an der aktuellen Position auf der LCD aus.

3.3.5 Ein Kontrollzeichen an die LCD ausgeben

rcall LcdCtrl sendet das Byte R16 als Kontrollbefehl an die LCD.

3.3.6 Die Ausgabeposition auf der LCD einstellen

rcall LcdPos stellt die Ausgabeposition des LCD-Display-RAM auf die in ZH angegebene Zeile (0...LcdLines-1) und die in ZL angegebenen Spalte ein (0...LcdCols-1).

3.3.7 Spezialzeichen in der LCD erzeugen

Mit rcall LcdSpec wird eine Tabelle mit Spezialzeichen an die LCD gesendet. Die Tabelle besteht
  1. aus einem Adressbyte (0b01zz.z000), worin z das Zeichen (0...7) angibt,
  2. aus einem Dummybyte (z. B. 0), sowie
  3. acht Datenbytes, die in den unteren fünf Bits die beleuchteten Pixel von der obersten (0), von rechts nach links, bis zur untersten Zeile (7) enthalten,
  4. aus einem Nullbyte als Adressbyte, das die Ausgabe beendet.
Die Spezialzeichen können mit 0x00 bis 0x07 in Textausgaben integriert werden.

3.3.8 Binärzahlen dezimal auf der LCD anzeigen

Diese Routinen sind nur dann im assemblierten Code enthalten und mit rcall LcdDec2, rcall LcdDec3 und rcall LcdDec5 ansprechbar, wenn der Schalter LcdDecimal im Quellcode der Hauptroutine gesetzt ist.

LcdDec2 gibt die Binärzahl in R16 mit zwei Dezimalstellen aus. Ist R16 größer als 99 wird 99 ausgegeben. Eine führende Null wird nicht unterdückt.

LcdDec3 gibt die 8-Bit-Binärzahl in R16 in dreistelligem Dezimalformat auf der LCD an der aktuellen Position aus. Führende Nullen werden als Leerzeichen ausgegeben.

LcdDec5 gibt die 16-Bit-Binärzahl in Z (ZH:ZL) in fünfstelligem Dezimalformat an der aktuellen Position aus. Führende Nullen werden durch Leerzeichen ersetzt.

3.3.9 Binärzahlen hexadezimal auf der LCD anzeigen

Diese Routinen sind nur dann im assemblierten Code enthalten, wenn der Schalter LcdHex im Quellcode definiert ist.

LcdHex2 gibt die 8-Bit-Binärzahl in R16 in zweistelligem Hexadezimalformat aus. LcdHex4 gibt die 16-Bit-Binärzahl in Hexadezimalformat aus.

3.4 Umfang Programmworte der assemblierten Varianten

Die Größe des erzeugten Programmcodes unterscheidet sich je nach den gewählten Parametern. In der Tabelle sind die Größen zusammengestellt. Alle Angaben enthalten nur Programmworte, die innerhalb der LCD-Include enthalten sind.
VersionUmfang
Worte
Akkumuliert
Minimal version (1 MHz, 8-Bit, Wait-Mode, ohne Dez/Hex)138-
Wait ==> Busy+9147
1 MHz ==> 20 MHz+20158
8-Bit ==> 4-Bit+44182
LcdDecimal+86224
LcdHex+14152
Maximalversion
(20 MHz, 4-Bit, Busy-Mode,
Dezimal- und Hex-Wandlung
+197335
Die Include-Datei ist durch umfangreiche bedingte Assemblierung optimiert. Entsprechend niedrig ist der Programmaufwand, da nur Code erzeugt wird, der tatsächlich auch für den jeweiligen Anwendungsfall benötigt wird und unnötiger Blow-Up-Code, wie er von irgendwelchen C- oder Basic-Compilern reichlich erzeugt werden würde, vermieden wird.

3.5 Beispielprogramm

Die Verwendung der Include-Software zur LCD-Ansteuerung demonstrieren zwei Beispielprogramme:
  1. Ansteuerung über 8-Bit-Datenbus mit Ausgabe von Dezimal- und Hexadezimalzahlen, und
  2. Ansteuerung über 4-Bit-Datenbus mit Erzeugung und Ausgabe von acht Spezialzeichen.

3.5.1 Beispielprogramm 8-Bit

Beispiel 8-Bit mit Zahlenausgabe Das Beispielprogramm (im Assembler Quellcode-Format hier, im HTML-Format hier) zeigt mit einem ATmega8 die 8-Bit-Ansteuerung und die Ausgabe von Dezimal- und Hexadezimalzahlen auf der LCD und das Einbinden der Include-Datei lcd.inc in den Quellcode. In dieser Anwendung läuft der ATmega8 quarzgetrieben mit 8 MHz Takt.

Die Routine LcdInit initiiert die LCD, mit LcdText wird die Ausgabemaske auf der LCD in der Tabelle InitText: ausgegeben. LcdPos positioniert den LCD-Cursor in die Zeilen 3 und 4 und gibt dort mit LcdDec5 die Dezimalzahl bzw. mit LcdHex4 die Hexadezimalzahl aus.

3.5.2 Beispielprogramm 4-Bit

Beispiel 4-Bit mit Spezialzeichen Das Beispielprogramm (im Assembler Quellcode-Format hier, im HTML-Format hier) zeigt mit einem ATtiny24 die 4-Bit-Ansteuerung und die Erzeugung und Ausgabe von acht eigenen definierten Zeichen auf der LCD und wie dies mit der Include-Datei lcd.inc in den Quellcode integriert wird. In dieser Anwendung läuft der ATtiny24 mit 1 MHz Takt.

Die Routine LcdInit initiiert die LCD, mit LcdSpec werden die Spezialzeichen (Pfeile) in Tabelle Codezeichen: in die LCD geschrieben. Mit LcdText wird der Text in der Tabelle InitText: ausgegeben, die auch die Spezialzeichen ausgibt.


Hardware Einstellung Software


Lob, Tadel, Fehlermeldungen, Genöle und Geschimpfe oder Spam bitte über das Kommentarformular an mich.

Zum Anfang dieser Seite

©2018 by http://www.avr-asm-tutorial.net