Die einzelnen Pins bedeuten folgendes:
Pin | Name | Bedeutung |
---|
1 | VSS | Betriebsspannung, Minus |
2 | VDD | Betriebsspannung, Plus, 3 oder 5 Volt |
3 | VEE | Regelspannung für Kontrast |
4 | RS | Register Select, 0=Control, 1=Daten |
5 | R/W | Read/Write, 0=Schreiben in LCD, 1=Lesen von LCD |
6 | E | Enable, 0=inaktiv, 1=aktiv/Lesen/Schreiben |
7 | D0 | Datenbit 0 |
8 | D1 | Datenbit 1 |
9 | D2 | Datenbit 2 |
10 | D3 | Datenbit 3 |
11 | D4 | Datenbit 4 |
12 | D5 | Datenbit 5 |
13 | D6 | Datenbit 6 |
14 | D7 | Datenbit 7 |
15 | A | Hintergrundbeleuchtung Anode |
16 | K | Hintergrundbeleuchtung Kathode |
Die zweireihige Steckervariante eignet sich für Verbindungen mit
Flachbandkabel. Abweichende Pinfolgen sind den Datenblättern zu
entnehmen.
Die Regelspannung für die Kontrasteinstellung wird meistens mit einem
Trimmer eingestellt, der die Betriebsspannung teilt. Er wird auf maximalen
Kontrast justiert. Liegen hier 0 Volt oder die Betriebsspannung an, sind die
Zeichen nicht zu erkennen.
Bei den meisten LCDs bewirkt das Einschalten der Betriebsspannung und die
Kontrastregelung alleine, d. h. ohne Prozessortätigkeit, dass
Zeichenpositionen erkennbar werden, bei mehrzeiligen LCDs bleiben einzele
Zeilen aber auch leer (dunkel). Immerhin kann man daran erkennen, dass die
Betriebsspannung korrekt angeschlossen ist (wenn nicht: LCD in die Tonne
treten) und ob die Kontrastregelung funktioniert.
8-Bit-Interface
Im Startzustand, bei Anlegen der Betriebsspannung, sind LCDs auf die
Kommunikation im 8-Bit-Modus eingestellt. Bei jedem Schreib- und Lesevorgang
werden zwischen Prozessor und LCD acht Datenbits übertragen. Alle
Datenbit-Eingänge liegen übrigens per default auf High-Pegel.
4-Bit-Interface
Mit einem speziellen Befehl lassen sich LCDs in den 4-Bit-Modus umschalten.
In diesem Modus werden nur die obersten vier Datenbits zur Kommunikation
verwendet (Pin-Spar- und Verkabelungsvermeidungs-Modus). Dafür
müssen alle Datenbytes in zwei Portionen an die LCD gesendet und von
der LCD gelesen werden. Nur die Anschlüsse D4 bis D7 werden verwendet.
Zuerst werden die oberen vier Bits gesendet/gelesen, dann die unteren vier
Bits.
10.1.3 Ansteuerung von LCD-Anzeigen
Initiierung
Mit folgendem Ablauf werden LCDs initiiert, d. h. erstmalig in Betrieb
genommen. In allen Fällen muss der Anschluss RS auf Null gehalten werden.
Die Übertragung erfolgt durch Aktivieren des LCD-Eingangs E für
mindestens 1 µs Dauer.
"x" bedeutet, dass diese Bits egal sind.
- Nach dem Einschalten der Betriebsspannung ist zunächst zu warten,
bis der LCD-Prozessor seinen Aufwärmvorgang abgeschlossen hat.
Bevor das erfolgt, gelingt keine Kommunikation mit der LCD. Der Zeitraum
wird unterschiedlich lang angegeben, 50 ms sind in jedem Fall
dafür ausreichend.
- Als nächstes wird der Function Set durchgeführt. Er lautet
0b001L.NFxx.
- Ist ein 8-Bit-Interface angeschlossen, dann ist L=1. N gibt die
Anzahl der Zeilen an (0: einzeilig, 1: mehrzeilig), F den Zeichenfont
(0: 5x8).
- Ist ein 4-Bit-Interface angeschlossen, dann ist zunächst drei
mal 0b0011.xxxx an die LCD zu senden, um sie sicher in den
8-Bit-Modus zu schalten. Erst dann erfolgt die Umschaltung auf den
4-Bit-Modus durch Ausgabe von 0b0010.xxxx, noch im vorher
eingestellten 8-Bit-Modus. Nachfolgend erfolgt die Einstellung von
N und F durch erneute Ausgabe von 0b0010.NFxx, jetzt aber im
4-Bit-Verfahren (Ausgabe von 0b0010 zuerst, dann von 0bNFxx).
Die Umschaltung dauert etwas mehr als 1 ms, 5 ms Warten sind
ausreichend.
- Die nachfolgenden Befehle benötigen etwa 40 µs für
die Ausführung.
Ab jetzt ist es aber auch möglich, das Busy-Flag abzufragen. Dazu
wird der Datenport auf Eingang geschaltet (Richtungsbits löschen)
und der R/W-Eingang auf Eins gesetzt. Mit Aktivieren des LCD-Eingangs E
gibt die LCD den Zustand des Busy-Flags (Bit 7) und die aktuelle Adresse
der Zeichenausgabe (Bit 6 bis 4) zurück. Im 4-Bit-Modus sind zwei
Aktivierungen des E-Eingangs nötig, bei den unteren vier Bits sendet
die LCD die Bits 3 bis 0 der aktuellen Adresse. Ist das Busy-Flag Null,
ist die interne Verarbeitung des vorausgehenden Befehls abgeschlossen und
die nächste Operation kann erfolgen.
- Als nächtes erfolgt mit 0b0000.DCBx das Einschalten der Anzeige
(mit D=1), das Einschalten des Cursors (mit C=1) und das Blinken des
Cursors (mit B=1).
- Mit dem Befehl 0b0000.0001 wird die Anzeige gelöscht.
- Mit dem Befehl 0b0000.00IS wird Autoincrement der Displayadresse
(mit I=1) und der Schiebemodus des Displays (mit S=1) eingestellt.
- Mit dem Befehl 0b0000.001x wird die Ausgabeadresse auf das erste
Zeichen der ersten Zeile gesetzt und Verschiebungen (bei S=1) aufgehoben.
Dieser Befehl benötigt etwa mehr als 1 ms, 5 ms sind
ausreichend.
Damit ist die Initiierung abgeschlossen und es kann an das Ausgeben von
Zeichen beginnen.
Datenausgabe
Um Daten zur LCD zu senden, muss der RS-Eingang der LCD auf Eins, der R/W-
Eingang auf Null gesetzt werden. Die nachfolgend an die LCD ausgegebenen
ASCII-Zeichen erscheinen bei Auto-Indent (I=1) nacheinander auf dem Display.
Um die Adresse des Displays zu ändern, bei der das nächste Zeichen
erscheinen soll, sind mit RS=0 und R/W=0 folgende Zahlen auszugeben (bei
einer mehrzeiligen LCD mit N Zeichen pro Zeile):
- Zeile 1: 0x80 + (Spalte - 1),
- Zeile 2: 0xC0 + (Spalte - 1),
- Zeile 3: 0x80 + N + (Spalte - 1),
- Zeile 4: 0xC0 + N + (Spalte -1).
10.1.4 Beleuchtung von LCD-Anzeigen
LCD-Anzeigen sind (besonders im Dunklen) besser ablesbar (und auch
schöner), wenn sie eine Hintergrundbeleuchtung haben. Um diese
anzusteuern, wird der K-Eingang auf Minus gebracht und über den
A-Eingang ein Strom auf die Hintergrund-LEDs gegeben. Die Höhe des
Stroms ist im Datenblatt spezifiziert, für die hier verwendete
vierzeilige LCD sind 70 mA angegeben.
Die folgenden Bilder zeigen, dass oberhalb von 5 mA Strom kein
sichtbarer Effekt eintritt.
 I = 2,1 mA, U = 2,9 V |
 I = 7,4 mA, U = 3,4 V |
 I = 12,0 mA, U = 3,8 V |
 I = 18,3 mA, U = 4,3 V |
Der Effekt für die Batteriehaltbarkeit oder die Akkulaufzeit
dürfte bei dem spezifizierten Strom von 70 mA relevanter sein als
der erzielbare optische Effekt.
Der ATtiny24 ist ein 14-poliger Prozessortyp. Wie die Vielfachbelegung seiner
Pins zeigt, ist er nicht nur mit mehr Pins sondern auch mit viel mehr
interner Hardware ausgestattet. Der Port A ist mit allen acht Bits
zugänglich, zusätzlich sind drei Bits des Ports B vorhanden. Wenn
man auf die ISP-Programmierung verzichtet, kann noch ein weiterer Bit des
Ports verwendet werden, der defaultmäßig den Reset-Eingang bildet.
Acht Anschlüsse lassen sich als AD-Wandler-Eingänge ADC0 bis ADC7
verwenden.
Hinter OC0A und OC0B steckt ein 8-Bit-Timer, hinter OC1A und OC1B ein
16-Bit-Timer. Alle vier Pins können als Takt- oder PWM-Ausgänge
verwendet werden.
An die Pins 2 und 3 kann ein externer Quarz angeschlossen werden und mit den
Fuses eingeschaltet werden.
Alle Pins können mit zwei PCINT (0 .. 7, 8 .. 10/11) auf Pegelwechsel
überwacht werden.
Natürlich kann mit USCK, MISO, MOSI und RESET auch eine
ISP-Programmierung erfolgen.
Der ATtiny24 hat einen internen 8 MHz-Oszillator. Mit der
defaultmäßig gesetzten DIV8-Fuse taktet dieser mit 1 MHz,
kann aber, wie schon beim ATtiny13 gezeigt wurde, mit der Fuse auf
höhere und mit CLKPR auf niedrigere Taktraten umgestellt werden.
Wem also wie uns die Pins beim ATtiny13 zu wenige sind, kann zu diesem Typ
greifen. Die Mehrfachbelegung der Pins macht deutlich, worum es bei der
optimalen Auswahl des AVR-Typs geht: man muss ein klares Bild von dem haben,
was man in seiner Schaltung alles an prozessorinterner Hardware benötigt,
welche Pins man für welche Zwecke benötigt und bei welchen Pins man
optimalerweise eine bestimmte Reihenfolge benötigt, z. B. für
unsere vier Bits Datenbus zur LCD. Natürlich könnte man dazu
beliebige Pins in beliebigen Ports verwenden, nur muss man dann jedes Bit
eines ASCII-Zeichens an den richtigen Pin bringen.
Hat man weder über die Hardware noch über solche Vorbedingungen
einen Überblick, neigt man eher zur Überdimensionierung. Hat man
sich nämlich einmal für einen zu kleinen Typ entschieden und braucht
dann noch zusätzlich ein oder zwei Portpins mehr oder benötigt
unbedingt einen OC0A- oder OC1B-Ausgang, muss man nicht nur einige Zeilen
im Programm ändern. Die Interrupt-Vektor-Tabelle anzupassen ist noch
der geringere Aufwand, manchmal ist dazu aber auch ein völliges
Neudesign der Schaltung nötig. Auf so was lässt man sich ein, wenn
man halt keinen Elefanten (Arduino) in den eigenen kleinen Porzellanladen
reinlassen will.
Das Thema Mehrfachnutzungen von Pins ist im Design auch ein Thema. Da wir
ISP zum Programmieren benutzen wollen, sind die Pins USCK, MOSI und MISO
sowieso schon gesetzt. Da sich deren Nutzung durchaus mit der Nutzung als
Datenbus zur LCD verträgt (wenn der LCD-Eingang R/W nicht dauerhaft auf
Lesen steht), stören sich diese beiden Mehrfachverwendungen nicht. Die
Verwendung des USCK-Pins z. B. für die Messung von Analogspannungen
oder für die Ansteuerung eines Motors würde hingegen zahlreiche
Fragen aufwerfen.
10.3.1 Schaltbild
Das Schaltbild zeigt, wie die LCD vom ATtiny24 angesteuert wird:
- Die Pins PB0 (LCD-RS) und PB2 (LCD-E) steuern die Kontrollpins der LCD,
der Datenbus wird vom oberen Nibble (Nibble = 4-Bit) des Datenports PA
bedient. Der R/W-Eingang liegt dauerhaft auf Minus, da wir bei der
ersten Aufgabe den Read-Modus der LCD nicht verwenden.
- Die ISP-Schnittstelle ist an die entsprechenden Ports angeschlossen,
die drei Signale werden parallel auch vom LCD-Datenbus verwendet.
- Die Hintergrundbeleuchtung der LCD wird über einen
220 Ω-Widerstand mit einem sparsamen Strom angetrieben.
10.3.2 Bauteil LCD-Anzeige
Die LCD wird mit einer 16-poligen Stiftleiste ausgestattet, damit sie in die
Löcher unseres Breadboards passt. Wer eine andere LCD verwendet, muss
sich hier was einfallen lassen.
10.3.3 Bauteil ATtiny24
Das hier ist der 14-polige ATtiny24.
10.3.4 Bauteil Fassung 14-polig
In diese Fassung passt der Prozessor.
10.3.5 Trimmer
Der Trimmer dient der Kontrasteinstellung der LCD. Die beiden Anschlüsse
im Vordergrund des rechten Bilds sind die Endpunkte der Widerstandsbahn, der
mittlere Pin im Hintergrund ist der Schleifer.
10.3.6 Aufbau
So wird der Tiny24 und die LCD-Anzeige aufgebaut.
Noch ein Hinweis zu solchen Breadboards: die Kontakte solcher Boards leiern
mit der Zeit aus, wenn man z. B. dicke Stiftleisten über längere
Zeit in den gleichen Löchern stecken lässt. Bei Einfachstboards
ohne metallene Unterlage kann sich die Plastikunterlage herausdrücken,
die Kontaktsicherheit ist dann gleich Null. Das kann üble Wackelkontakte
zur Folge haben. Besonders bei gedrehten IC-Fassungen sind die Pins sehr
dünn und kurz. Das kann den Tod von Prozessoren oder einzelner Portpins
verursachen, wenn die Versorgungsleitung einen Wackler hat. Bei Verdacht mit
einem Durchgangsprüfer oder einem Ohmmeter den tatsächlichen
Kontakt lieber noch mal prüfen.
10.3.7 Alternativer Aufbau
Wer dieses oder die nächsten Experimente lieber auf kompaktere Weise
aufbauen möchte und die vielen Zuleitungen zur LCD fest verdrahtet
sehen möchte, kann sich das
Board hier
als gedruckte Platine bauen. Alle Programmbeispiele dieser und der
nachfolgenden Lektionen laufen darauf ohne Änderungen.
10.4.1 Zeitbedarf und Konstruktion von Warteschleifen
Die folgenden Zeitbedarfe sind bei der Ansteuerung der LCD zu beachten:
- Initiierung: 50 ms
- Rücksetzen der Anzeige: 5 ms
- Ausgabe von Zeichen, einfache Kontrollbefehle: 40 µs
Sinnvollerweise läuft die Warteschleife parallel zur Tätigkeit der
LCD, also nach Absetzen des jeweiligen Befehls.
Für alle genannten Zeiten ist eine 16-Bit-Warteschleife geeignet, wie
sie in der Lektion 3 vorgestellt wurde. Die Formulierung
ldi ZH,HIGH(n)
ldi ZL(n)
Schleife:
sbiw ZL,1
brne Schleife
verzögert um t = 2 + 4* (n-1) + 3 = 4 * n + 1 Takte, bei 1 Mhz
Takt 4 * n + 1 µs. Zusammen mit dem Aufruf der Verzögerungsroutine,
einer "RCALL"-Instruktion (3 Takte), einer "RET"-Instruktion
(4 Takte) und einem Sprung zur Zählroutine "RJMP" mit
2 Takten ergeben sich t = 4 * n + 10 Takte. Soll das Registerpaar ZH:ZL
vor dem Zählen noch gesichert und danach wieder hergestellt werden
(2 * "PUSH" und "POP" zu je 2 Takten ergeben sich
t = 4 * n + 18 Takte.
; rcall warte50ms ; + 3 Takte
warte50ms:
push ZH ; + 2 Takte
push ZL ; + 2 Takte
ldi ZH,HIGH(n) ; + 1 Takt
ldi ZL,LOW(n) ; + 1 Takt
rjmp warte ; + 2 Takte
warte:
sbiw ZL,1 ; + 2 Takte
brne warte ; +1/2 Takte
pop ZL ; + 2 Takte
pop ZH ; + 2 Takte
ret ; + 4 Takte
Kehrt man die Formel um, um n zu ermitteln, lautet die Formel
n = (t - 18) / 4, mit Aufrunden
n = (t - 16) / 4
Für 50 ms ergeben sich
n = (50.000 - 16) / 4 = 12.496
Auf ähnliche Weise lassen sich die Zählkonstanten für andere
Zeiten oder Taktraten errechnen.
10.4.2 Aufgabenstellung
Auf der LCD sollen auf den vier Zeilen (alternativ: 1, 2 oder 3 Zeilen) mit
20 (alternativ: 8, 16, 20, 24) Zeichen einer Meldung ausgegeben werden. Die
Ausführungszeiten sollen mit Warteschleifen überbrückt werden.
10.4.3 Programm
Das hier ist das Programm (zum Quellcode geht
es hier). Es ist linear angelegt, weil zum Initiieren und zur Textausgabe
an die LCD keine Interrupts benötigt werden.
;
; ***************************************
; * LCD-Display 4*20 am ATtiny24, 4-Bit *
; * (C)2016 by www.gsc-elektronic.net *
; ***************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Programmablauf ------------
;
; Initiiert die an den ATtiny24 angeschlossene
; 4*20-LCD mit Verzoegerungswarteschleifen
; und gibt dann einen Text auf der LCD aus.
;
; ----------- Ports, Portpins -----------
; LCD-Kontrollport
.equ pLcdCO = PORTB ; LCD-Kontrollport-Ausgabe
.equ pLcdCR = DDRB ; LCD-Kontrollport-Richtung
.equ bLcdCOE = PORTB2 ; LCD Enable Pin Output
.equ bLcdCRE = DDB2 ; LCD Enable Pin Richtung
.equ bLcdCORS = PORTB0 ; LCD RS Pin Output
.equ bLcdCRRS = DDB0 ; LCD RS Pin Richtung
.equ bLcdCORW = PORTB1 ; LCD RW Pin Output
.equ bLcdCRRW = DDB1 ; LCD RW Pin Richtung
; LCD-Datenport
.equ pLcdDO = PORTA ; LCD-Datenport-Ausgabe
.equ pLcdDI = PINA ; LCD-Datenport-Eingabe
.equ pLcdDR = DDRA ; LCD-Datenport-Richtung
.equ mLcdDR = 0xF0 ; LCD-Datenport-Richtungsmaske
;
; ------- Register ----------------------
; frei: R0 .. R15
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; weiteres Vielzweckregister
.def rLine = R18 ; Zeilenzaehler LCD
; frei R19 .. R29
; verwendet: R31:R30, ZH:ZL, fuer Zaehlen
;
; ------- Konstanten --------------------
.equ Takt = 1000000 ; Default-Taktfrequenz
;
; -------- Programmstart, Init ----------
.CSEG ; Code Segment
.ORG 0 ; Start bei 0
; Stapel Init fuer Unterprogramme
ldi rmp,LOW(RAMEND) ; Init Stapel
out SPL,rmp ; in Stapelzeiger
; Port-Ausgaenge initiieren
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<&kt;bLcdCRRW)
out pLcdCR,rmp ; Kontrollport-Ausgaenge
clr rmp ; Ausgaenge aus
out pLcdCO,rmp ; an Kontrollport
ldi rmp,mLcdDR ; Datenport-Ausgabemaske
out pLcdDR,rmp ; auf Ausgabe
; LCD initiieren
rcall LcdInit
; Text auf LCD ausgeben
ldi ZH,HIGH(2*Texttabelle)
ldi ZL,LOW(2*Texttabelle)
rcall LcdText ; Text ab ZH:ZL ausgeben
; Sleep enable
ldi rmp,1<<SE
out MCUCR,rmp
Schleife:
sleep ; schlafen
rjmp Schleife
;
; Ausgabetext auf LCD
Texttabelle:
.db "LCD-Display ATtiny24",0x0D,0xFF ; Zeile 1
.db " gsc-elektronic.net ",0x0D,0xFF ; Zeile 2
.db " Testprogramm 4-Bit ",0x0D,0xFF ; Zeile 3
.db " mit Warteschleifen",0x00 ; Zeile 4
;
; --------- LCD-Ansteuerung Init ----------
LcdInit:
; Warte 50 ms bis LCD hochgefahren ist
rcall Warte50ms ; Karenzzeit 50 ms
; Versetze in 8-Bit-Modus (drei Mal)
ldi rmp,0x30 ; 8-Bit-Modus
rcall LcdC8Byte ; Schreibe im 8-Bit-Mode
rcall Warte5ms ; Warte 5 ms
ldi rmp,0x30
rcall LcdC8Byte
rcall Warte5ms
ldi rmp,0x30
rcall LcdC8Byte
rcall Warte5ms
; Umstellung auf 4-Bit-Modus
ldi rmp,0x20 ; Schalte in 4-Bit-Modus um
rcall LcdC8Byte
rcall Warte5ms
; Funktionseinstellungen LCD
ldi rmp,0x28 ; 4-Bit-Modus, 4 Zeilen, 5*7
rcall LcdC4Byte
rcall Warte5ms
ldi rmp,0x0F ; Display ein, Blinken
rcall LcdC4Byte
rcall Warte5ms
ldi rmp,0x01 ; Display loeschen
rcall LcdC4Byte
rcall Warte5ms
ldi rmp,0x06 ; Autoindent
rcall LcdC4Byte
rjmp Warte40us
;
; Ausgabe von Text auf der LCD
; Z zeigt auf Text im Flash
LcdText:
clr rLine ; Zeilenzaehler
LcdText1:
lpm rmp,Z+ ; lese Zeichen aus Flash
cpi rmp,0 ; Ende Ausgabe?
breq LcdTextRet ; nein
cpi rmp,0xFF ; Fuellzeichen?
breq LcdText1 ; ja, naechstes Zeichen
cpi rmp,0x0D ; Zeilenwechsel?
brne LcdText2 ; Kein Zeilenwechsel
inc rLine ; Naechste Zeile
mov rmp,rLine ; Setze Zeile
rcall LcdLineSet ; Stelle Zeile ein
rjmp LcdText1 ; weiter mit Zeichen
LcdText2: ; Ausgabe Zeichen
rcall LcdD4Byte ; Zeichen ausgeben
rcall Warte40us ; Warten
rjmp LcdText1 ; und weiter
LcdTextRet:
ret ; Fertig
;
; Setzt den Ausgabecursor auf den
; Zeilenanfang der ausgewaehlten Zeile
; Zeile: rmp 0 .. 3
LcdLineSet:
cpi rmp,1 ; Zeile 2?
brcs LcdLineSet1 ; nach Zeile 1
breq LcdLineSet2 ; nach Zeile 2
cpi rmp,2 ; Zeile 3?
breq LcdLineSet3 ; nach Zeile 3
rjmp LcdLineSet4 ; nach Zeile 4
LcdLineSet1:
ldi rmp,0x80 ; Zeile 1
rjmp LcdLineSetRmp
LcdLineSet2:
ldi rmp,0xC0 ; Zeile 2
rjmp LcdLineSetRmp
LcdLineSet3:
ldi rmp,0x80+20 ; Zeile 3
rjmp LcdLineSetRmp
LcdLineSet4:
ldi rmp,0xC0+20 ; Zeile 4
rjmp LcdLineSetRmp
LcdLineSetRmp:
rcall LcdC4Byte ; Ausgabe Kontrollwort
rjmp Warte40us
;
; Datenwort-Ausgabe im 4-Bit-Modus
; Daten in rmp
LcdD4Byte:
sbi pLcdCO,bLcdCORS ; setze RS-Bit
rjmp Lcd4Byte ; gib Byte in rmp aus
;
; Kontrollwort-Ausgabe im 4-Bit-Modus
; Daten in rmp
LcdC4Byte:
cbi pLcdCO,bLcdCORS ; loesche RS-Bit
; Ausgabe Byte im 4-Bit-Modus
Lcd4Byte:
push rmp ; rmp auf Stapel legen
andi rmp,0xF0 ; unteres Nibble loeschen
in rmo,pLcdDI ; Lese Data-Input-Port
andi rmo,0x0F ; oberes Nibble loeschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
pop rmp ; rmp wieder herstellen
andi rmp,0x0F ; oberes Nibble loeschen
swap rmp ; Nibble vertauschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
ret ; fertig
;
; Kontrollwort-Ausgabe im 8-Bit-Modus
; Datenbyte in rmp
LcdC8Byte:
cbi pLcdCO,bLcdCORS ; RS-Bit loeschen
andi rmp,0xF0 ; unteres Nibble loeschen
in rmo,pLcdDI ; Lese Data-Input-Port
andi rmo,0x0F ; oberes Nibble loeschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
ret ; fertig
;
; ------ Warteroutinen ------------------
Warte50ms: ; Warteroutine 50 ms
.equ c50ms = 50000
.equ n50ms = (c50ms-16)/4
; rcall: + 3
push ZH ; + 2
push ZL ; + 2
ldi ZH,HIGH(n50ms) ; + 1
ldi ZL,LOW(n50ms) ; + 1
rjmp Warte ; + 2, gesamt = 11
Warte5ms: ; Warteroutine 5 ms
.equ c5ms = 5000
.equ n5ms = (c5ms-16)/4
push ZH
push ZL
ldi ZH,HIGH(n5ms)
ldi ZL,LOW(n5ms)
rjmp Warte
Warte100us: ; Warteroutine 100us
.equ c100us = 100
.equ n100us = (c100us-16)/4
push ZH
push ZL
ldi ZH,HIGH(n100us)
ldi ZL,LOW(n100us)
rjmp Warte
Warte40us: ; Warteroutine 40us
.equ c40us = 40
.equ n40us = (c40us-16)/4
push ZH
push ZL
ldi ZH,HIGH(n40us)
ldi ZL,LOW(n40us)
rjmp Warte
; Warteroutine Z Takte
Warte: ; Warteschleife, Takte = 4*(n-1)+11 = 4*n + 7
sbiw ZL,1 ; + 2
brne Warte ; + 1 / 2
pop ZL ; + 2
pop ZH ; +2
ret ; + 4, Gesamt=4*n+18
;
In dem Programm ist eine neue Instruktion verwendet,
"SWAP Register". Sie vertauscht die unteren vier
Bits 0 bis 3 in einem Register mit den oberen vier Bits 4 bis 7. Wir brauchen
diese Instruktion, weil wir ja die oberen vier Bits zuerst auf den
LCD-Datenbus bringen müssen (der im oberen Port-A-Bereich liegt) und
danach die unteren vier Bits auf den oberen Port-A-Bereich legen müssen.
Um die unteren vier Bits zu den oberen zu machen, könnten wir auch vier
mal Linksschieben (mit "LSL Register"), aber ANDI und SWAP sind
schneller.
10.4.4 Simulieren der Warteroutinen
Hier macht es Sinn, die Warteroutinen per Simulation zu verifizieren.
Also starten wir avr_sim,
füttern den Quellcode in den Simulator und fügen
direkt hinter der Initiierung des Stapelzeigers die folgenden
Instruktionen ein:
rcall Wait50ms
rcall Wait5ms
rcall Wait100us
rcall Wait40us
nop
Nach dem Assemblieren setzen wir Breakpunkte auf alle diese
Instruktionen und löschen die Stoppuhr nach jedem
ausgeführten rcall.
Die Unterroutine ist recht exakt, nur zwei Taktzyklen
länger als berechnet.
Dasselbe bei der Routine für 5 ms Verzögerung.
Und wiederum für 100 µs.
Und, es wird schon fast langweilig, dasselbe für
die 40 µs-Schleife.
Es sieht so aus, als ob die Formel ausreichend korrekt ist
und jedenfalls für den Zweck der Verzögerung bei
der LCD-Ansteuerung gut geeignet ist.
10.5.1 Abfragen des Busy-Flags
Für das Abfragen des Busy-Flags muss der LCD-Datenbus auf Schreiben
(R/W = 1) gesetzt werden, die ATtiny-Eingänge des Datenbuses müssen
vorher als Eingänge konfiguriert werden. Das setzt voraus, dass ein
weiterer Pin den R/W-Eingang schaltet. Die R/W-Steuerung ist in diesem
Schaltbild an PB1 realisiert.
10.5.2 Aufgabenstellung
Die Aufgabe ist die gleiche (Textausgabe auf der LCD), nur soll jetzt soweit
als möglich das Busy-Bit der LCD zur Zeitsteuerung verwendet werden.
10.5.3 Programm
Das hier ist das Programm (den Quellcode gibt
es hier).
;
; ************************************************
; * LCD-Display 4*20 am ATtiny24, 4-Bit mit Busy *
; * (C)2016 by http://www.gsc-elektronic.net *
; ************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Programmablauf ------------
;
; Initiiert das 4*20-LCD am ATtiny24 im
; Busy-Modus (Abfrage des Busy-Flags der
; LCD statt Verzoegerungsroutinen) und
; gibt einen Text auf der LCD aus.
;
; ----------- Ports, Portpins -----------
; LCD-Kontrollport
.equ pLcdCO = PORTB ; LCD-Kontrollport-Ausgabe
.equ pLcdCR = DDRB ; LCD-Kontrollport-Richtung
.equ bLcdCOE = PORTB2 ; LCD Enable Pin Output
.equ bLcdCRE = DDB2 ; LCD Enable Pin Richtung
.equ bLcdCORS = PORTB0 ; LCD RS Pin Output
.equ bLcdCRRS = DDB0 ; LCD RS Pin Richtung
.equ bLcdCORW = PORTB1 ; LCD R/W Pin Output
.equ bLcdCRRW = DDB1 ; LCD R/W Pin Richtung
; LCD-Datenport
.equ pLcdDO = PORTA ; LCD-Datenport-Ausgabe
.equ pLcdDI = PINA ; LCD-Datenport-Eingabe
.equ pLcdDR = DDRA ; LCD-Datenport-Richtung
.equ mLcdDRW = 0xF0 ; LCD-Datenport-Maske Schreiben
.equ mLcdDRR = 0x00 ; LCD-Datenport-Maske Lesen
;
; ------- Register ----------------------
; frei: R0 .. R15
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; weiteres Vielzweckregister
.def rLine = R18 ; Zeilenzaehler LCD
.def rLese = R19 ; Leseergebnis vom LCD-Datenport
; frei R20 .. R29
; verwendet: R31:R30, ZH:ZL, fuer Zaehlen
;
; ------- Konstanten --------------------
.equ Takt = 1000000 ; Default-Taktfrequenz
;
; -------- Programmstart, Init ----------
.CSEG ; Code Segment
.ORG 0 ; Start bei 0
; Stapel Init fuer Unterprogramme
ldi rmp,LOW(RAMEND) ; Init Stapel
out SPL,rmp ; in Stapelzeiger
; Port-Ausgaenge initiieren
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; Kontrollport-Ausgaenge
clr rmp ; Ausgaenge aus
out pLcdCO,rmp ; an Kontrollport
ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
out pLcdDR,rmp ; auf Ausgabe
; LCD initiieren
rcall LcdInit
; Text auf LCD ausgeben
ldi ZH,HIGH(2*Texttabelle)
ldi ZL,LOW(2*Texttabelle)
rcall LcdText ; Text ab ZH:ZL ausgeben
; Sleep enable
ldi rmp,1<<SE
out MCUCR,rmp
Schleife:
sleep ; schlafen
rjmp Schleife
;
; Ausgabetext auf LCD
Texttabelle:
.db "LCD-Display ATtiny24",0x0D,0xFF ; Zeile 1
.db " gsc-elektronic.net ",0x0D,0xFF ; Zeile 2
.db " Testprogramm 4-Bit ",0x0D,0xFF ; Zeile 3
.db "mit Busy-Flag lesen",0x00 ; Zeile 4
;
; --------- LCD-Ansteuerung Init ----------
LcdInit:
; Warte 50 ms bis LCD hochgefahren ist
rcall Warte50ms ; Karenzzeit 50 ms
; Versetze in 8-Bit-Modus (drei Mal)
ldi rmp,0x30 ; 8-Bit-Modus
rcall LcdC8Byte ; Schreibe im 8-Bit-Mode
rcall Warte5ms ; Warte 5 ms
ldi rmp,0x30
rcall LcdC8Byte
rcall Warte5ms
ldi rmp,0x30
rcall LcdC8Byte
rcall Warte5ms
; Umstellung auf 4-Bit-Modus
ldi rmp,0x20 ; Schalte in 4-Bit-Modus um
rcall LcdC8Byte
rcall Warte5ms
; Funktionseinstellungen LCD
ldi rmp,0x28 ; 4-Bit-Modus, 4 Zeilen, 5*7
rcall LcdC4Byte ; Byte an Kontrollregister LCD
ldi rmp,0x0F ; Display ein, Blinken
rcall LcdC4Byte ; Byte an Kontrollregister LCD
ldi rmp,0x01 ; Display loeschen
rcall LcdC4Byte ; Byte an Kontrollregister LCD
ldi rmp,0x06 ; Autoindent
rjmp LcdC4Byte ; Byte an Kontrollregister LCD
;
; Ausgabe von Text auf der LCD
; Z zeigt auf Text im Flash
LcdText:
clr rLine ; Zeilenzaehler
LcdText1:
lpm rmp,Z+ ; lese Zeichen aus Flash
cpi rmp,0 ; Ende Ausgabe?
breq LcdTextRet ; nein
cpi rmp,0xFF ; Fuellzeichen?
breq LcdText1 ; ja, naechstes Zeichen
cpi rmp,0x0D ; Zeilenwechsel?
brne LcdText2 ; Kein Zeilenwechsel
inc rLine ; Naechste Zeile
mov rmp,rLine ; Setze Zeile
rcall LcdLineSet ; Stelle Zeile ein
rjmp LcdText1 ; weiter mit Zeichen
LcdText2: ; Ausgabe Zeichen
rcall LcdD4Byte ; Zeichen ausgeben
rjmp LcdText1 ; und weiter
LcdTextRet:
ret ; Fertig
;
; Setzt den Ausgabecursor auf den
; Zeilenanfang der ausgewaehlten Zeile
; Zeile: rmp 0 .. 3
LcdLineSet:
cpi rmp,1 ; Zeile 2?
brcs LcdLineSet1 ; nach Zeile 1
breq LcdLineSet2 ; nach Zeile 2
cpi rmp,2 ; Zeile 3?
breq LcdLineSet3 ; nach Zeile 3
rjmp LcdLineSet4 ; nach Zeile 4
LcdLineSet1:
ldi rmp,0x80 ; Zeile 1
rjmp LcdLineSetRmp
LcdLineSet2:
ldi rmp,0xC0 ; Zeile 2
rjmp LcdLineSetRmp
LcdLineSet3:
ldi rmp,0x80+20 ; Zeile 3
rjmp LcdLineSetRmp
LcdLineSet4:
ldi rmp,0xC0+20 ; Zeile 4
rjmp LcdLineSetRmp
LcdLineSetRmp:
rjmp LcdC4Byte ; Ausgabe Kontrollwort
;
; Datenwort-Ausgabe im 4-Bit-Modus
; Daten in rmp
LcdD4Byte:
rcall LcdBusy ; warte bis busy = Null
sbi pLcdCO,bLcdCORS ; setze RS-Bit
rjmp Lcd4Byte ; gib Byte in rmp aus
;
; Kontrollwort-Ausgabe im 4-Bit-Modus
; Daten in rmp
LcdC4Byte:
rcall LcdBusy ; warte bis busy = Null
cbi pLcdCO,bLcdCORS ; loesche RS-Bit
; Ausgabe Byte im 4-Bit-Modus mit Busy
Lcd4Byte:
push rmp ; rmp auf Stapel legen
andi rmp,0xF0 ; unteres Nibble loeschen
in rmo,pLcdDI ; Lese Data-Input-Port
andi rmo,0x0F ; oberes Nibble loeschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
pop rmp ; rmp wieder herstellen
andi rmp,0x0F ; oberes Nibble loeschen
swap rmp ; Nibble vertauschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
ret ; fertig
;
; Warte bis Busy Null
LcdBusy:
push rmp ; rette rmp
ldi rmp,mLcdDRR ; Lesemaske
out pLcdDR,rmp ; in Richtungsregister
cbi pLcdCO,bLcdCORS ; loesche RS
sbi pLcdCO,bLcdCORW ; R/W setzen
LcdBusy1:
sbi pLcdCO,bLcdCOE ; setze LCD-Enable
nop
in rLese,pLcdDI ; lese oberes Nibble
cbi pLcdCO,bLcdCOE ; loesche LCD-Enable
andi rLese,0xF0 ; unteres Nibble loeschen
sbi pLcdCO,bLcdCOE ; setze LCD-Enable
nop
in rmp,pLcdDI ; lese unteres Nibble
cbi pLcdCO,bLcdCOE ; loesche LCD-Enable
andi rmp,0xF0 ; loesche unteres Nibble
swap rmp ; oberes/unteres Nibble tauschen
or rLese,rmp ; oberes und unteres Nibble
sbrc rLese,7 ; ueberspringe bei Busy=0
rjmp LcdBusy1 ; wiederhole bis Busy=0
cbi pLcdCO,bLcdCORW ; R/W loeschen
ldi rmp,mLcdDRW ; Schreibmaske
out pLcdDR,rmp ; in Richtungsregister
pop rmp ; rmp wieder herstellen
ret ; zurueck
;
; Kontrollwort-Ausgabe im 8-Bit-Modus
; Datenbyte in rmp
LcdC8Byte:
cbi pLcdCO,bLcdCORS ; RS-Bit loeschen
andi rmp,0xF0 ; unteres Nibble loeschen
in rmo,pLcdDI ; Lese Data-Input-Port
andi rmo,0x0F ; oberes Nibble loeschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
ret ; fertig
;
; ------ Warteroutinen ------------------
Warte50ms: ; Warteroutine 50 ms
.equ c50ms = 50000
.equ n50ms = (c50ms-16)/4
; rcall: + 3
push ZH ; + 2
push ZL ; + 2
ldi ZH,HIGH(n50ms) ; + 1
ldi ZL,LOW(n50ms) ; + 1
rjmp Warte ; + 2, gesamt = 11
Warte5ms: ; Warteroutine 5 ms
.equ c5ms = 5000
.equ n5ms = (c5ms-16)/4
push ZH
push ZL
ldi ZH,HIGH(n5ms)
ldi ZL,LOW(n5ms)
rjmp Warte
; Warteroutine Z Takte
Warte: ; Warteschleife, Takte = 4*(n-1)+11 = 4*n + 7
sbiw ZL,1 ; + 2
brne Warte ; + 1 / 2
pop ZL ; + 2
pop ZH ; +2
ret ; + 4, Gesamt=4*n+18
;
In diesem Programm sind keine neuen Instruktionen enthalten.
10.6.1 Der Zeichengenerator in LCDs
Alle LCD bieten die Möglichkeit, eigene Zeichen zu definieren,
allerdings nur die Zeichen mit dem Code 0 bis 7. Sie sind defaultmässig
belegt, ihr Inhalt kann aber überschrieben werden.
Das Überschreiben funktioniert so:
- Ein Byte mit dem Aufbau 0b01NNNZZZ wird an den Kontrollport der LCD
gesendet ( mit RS = Null). Darin bedeutet NNN die Zeichennummer, die
definiert werden soll (0 bis 7). ZZZ ist die Zeile des Zeichens, 0 ist
die oberste Zeile und 7 die unterste.
- Danach wird ein Byte mit dem Aufbau 0b000BBBBB an den Datenport der
LCD gesendet (mit RS = Eins), worin B die fünf Pixel dieser Zeile
angibt (vorderstes Bit = linkestes Pixel).
- Für alle acht Zeilen des Zeichens ist ein Paar aus Adresse und
Daten zu schreiben.
10.6.2 Software für das Erzeugen neuer Zeichen
Um sich das Entwerfen solcher Zeichen zu erleichtern, kann man eine
Tabellenkalkulation verwenden.
So lassen sich komfortabel Zeichen entwerfen: hier ist eine
Tabellenkalkulation am Werk (im
Open-Office-Format hier, im
M$-Office-Format hier). Um ein Pixel weiss zu machen, trägt man in
die Zelle eine Eins ein, um es nicht leuchten zu lassen eine Null. Die
Adressen- und Daten in Dezimalformat zeigt die Tabelle rechts an.
Daraus macht die Tabellenkalkulation auf der rechten Seite direkt eine
assemblergerechte Tabelle. Sie beginnt mit der ersten Adresse, ab der das
Zeichen abgelegt werden soll, gefolgt von einer Null (geradzahlig ist bei
Tabellen immer Pflicht). Darauf folgen die acht Datenzeilen des Zeichens.
Sind alle gewünschten Zeichen definiert (es können auch weniger
als acht sein), kommt eine Null für den Abbruch. Die Tabelle kann mit
Strg-C/Strg-V direkt in den Assembler-Quellcode eingefügt werden.
10.6.3 Aufgabenstellung
Definiere die Zeichen "Pfeil nach rechts", "Pfeil nach
links", "Pfeil nach oben", "Pfeil nach unten",
"Pfeil nach rechts oben", "Pfeil nach rechts unten",
"Pfeil nach links unten" und "Pfeil nach links oben"
und lasse diese auf der LCD auf Zeile 4 erscheinen.
10.6.4 Das Programm
Die Tabellenkalkulation
Die Tabellenkalkulation dazu gibt es hier
im Open-Office-Format und
im Excel-Format.
Umbauten im Programm
Um die Aufgabe zu lösen, kann das vorherige Programm als Basis verwendet
werden. Folgende Umbauten sind dazu nötig:
- Zwischen dem Initiieren der LCD und der Textausgabe muss ein neuer
Programmteil eingebaut werden, der die neuen Zeichen in die LCD schreibt.
- Der Text zur Ausgabe muss für die neuen Zeichen 0 bis 7
umgeschrieben werden. Da bei der bisherigen Textausgaberoutine die Null
das Beenden der Ausgabe bewirkt hat, muss für die Beendigung der
Ausgabe die Null durch ein anderes Zeichen ersetzt werden (ich finde
das FE dafür eine gute Wahl).
Der Code
Hier ist der Quellcode (hier im
asm-Format).
;
; ***************************************************
; * LCD-Display 4*20 am ATtiny24/4-Bit/Busy/Zeichen *
; * (C)2016 by http://www.gsc-elektronic.net *
; ***************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ----------- Programmablauf ------------
;
; Initiiert die an den ATtiny24 angeschlossene
; 4*20-LCD im Busy-Modus, schreibt acht neue
; Zeichen in die LCD (Richtungszeichen) und
; stellt diese dar.
; ----------- Ports, Portpins -----------
; LCD-Kontrollport
.equ pLcdCO = PORTB ; LCD-Kontrollport-Ausgabe
.equ pLcdCR = DDRB ; LCD-Kontrollport-Richtung
.equ bLcdCOE = PORTB2 ; LCD Enable Pin Output
.equ bLcdCRE = DDB2 ; LCD Enable Pin Richtung
.equ bLcdCORS = PORTB0 ; LCD RS Pin Output
.equ bLcdCRRS = DDB0 ; LCD RS Pin Richtung
.equ bLcdCORW = PORTB1 ; LCD R/W Pin Output
.equ bLcdCRRW = DDB1 ; LCD R/W Pin Richtung
; LCD-Datenport
.equ pLcdDO = PORTA ; LCD-Datenport-Ausgabe
.equ pLcdDI = PINA ; LCD-Datenport-Eingabe
.equ pLcdDR = DDRA ; LCD-Datenport-Richtung
.equ mLcdDRW = 0xF0 ; LCD-Datenport-Maske Schreiben
.equ mLcdDRR = 0x00 ; LCD-Datenport-Maske Lesen
;
; ------- Register ----------------------
; frei: R0 .. R15
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; weiteres Vielzweckregister
.def rLine = R18 ; Zeilen bei LcdText, Zaehler bei LcdChars
.def rLese = R19 ; Leseergebnis vom LCD-Datenport
.def rAddr = R20 ; Zeilenadresse LCD-Zeichen
; frei R21 .. R29
; verwendet: R31:R30, ZH:ZL, fuer Zaehlen
;
; ------- Konstanten --------------------
.equ Takt = 1000000 ; Default-Taktfrequenz
;
; -------- Programmstart, Init ----------
.CSEG ; Code Segment
.ORG 0 ; Start bei 0
; Stapel Init fuer Unterprogramme
ldi rmp,LOW(RAMEND) ; Init Stapel
out SPL,rmp ; in Stapelzeiger
; Port-Ausgaenge initiieren
ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
out pLcdCR,rmp ; Kontrollport-Ausgaenge
clr rmp ; Ausgaenge aus
out pLcdCO,rmp ; an Kontrollport
ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
out pLcdDR,rmp ; auf Ausgabe
; LCD initiieren
rcall LcdInit
; Zeichen definieren
rcall LcdChars ; Neue Zeichen definieren
; Text auf LCD ausgeben
ldi ZH,HIGH(2*Texttabelle)
ldi ZL,LOW(2*Texttabelle)
rcall LcdText ; Text ab ZH:ZL ausgeben
; Sleep enable
ldi rmp,1<<SE
out MCUCR,rmp
Schleife:
sleep ; schlafen
rjmp Schleife
;
; Ausgabetext auf LCD
Texttabelle:
.db "LCD-Display ATtiny24",0x0D,0xFF ; Zeile 1
.db " gsc-elektronic.net ",0x0D,0xFF ; Zeile 2
.db "Eigene Zeichen hier:",0x0D,0xFF ; Zeile 3
.db " ",0x00," ",0x01," ",0x02," ",0x03 ; Zeile 4 links
.db " ",0x04," ",0x05," ",0x06," ",0x07 ; Zeile 4 rechts
.db 0xFE,0xFE ; Ende des Texts
;
; --------- LCD-Ansteuerung Init ----------
LcdInit:
; Warte 50 ms bis LCD hochgefahren ist
rcall Warte50ms ; Karenzzeit 50 ms
; Versetze in 8-Bit-Modus (drei Mal)
ldi rmp,0x30 ; 8-Bit-Modus
rcall LcdC8Byte ; Schreibe im 8-Bit-Mode
rcall Warte5ms ; Warte 5 ms
ldi rmp,0x30
rcall LcdC8Byte
rcall Warte5ms
ldi rmp,0x30
rcall LcdC8Byte
rcall Warte5ms
; Umstellung auf 4-Bit-Modus
ldi rmp,0x20 ; Schalte in 4-Bit-Modus um
rcall LcdC8Byte
rcall Warte5ms
; Funktionseinstellungen LCD
ldi rmp,0x28 ; 4-Bit-Modus, 4 Zeilen, 5*7
rcall LcdC4Byte ; Byte an Kontrollregister LCD
ldi rmp,0x0F ; Display ein, Blinken
rcall LcdC4Byte ; Byte an Kontrollregister LCD
ldi rmp,0x01 ; Display loeschen
rcall LcdC4Byte ; Byte an Kontrollregister LCD
ldi rmp,0x06 ; Autoindent
rjmp LcdC4Byte ; Byte an Kontrollregister LCD
;
; Eigene Zeichen definieren
LcdChars:
ldi ZH,HIGH(2*Codezeichen) ; Zeiger auf Zeichentabelle
ldi ZL,LOW(2*Codezeichen)
LcdChars1:
lpm rAddr,Z ; Lese Adresse
tst rAddr ; auf Null pruefen
breq LcdChars3 ; fertig
adiw ZL,2 ; auf naechstes Zeichen
ldi rLine,8
LcdChars2:
mov rmp,rAddr ; Adresse setzen
rcall LcdC4Byte ; an LCD
lpm rmp,Z+ ; lese Daten
rcall LcdD4Byte ; Ausgabe an LCD
inc rAddr ; Adresse erhoehen
dec rLine ; Zaehler abwaerts
brne LcdChars2 ; noch welche
rjmp LcdChars1 ; naechstes Zeichen
LcdChars3:
ret ; fertig
; 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
;
; Ausgabe von Text auf der LCD
; Z zeigt auf Text im Flash
LcdText:
rcall LcdLineSet1 ; Auf erste Zeile
clr rLine ; Zeilenzaehler
LcdText1:
lpm rmp,Z+ ; lese Zeichen aus Flash
cpi rmp,0xFE ; Ende Ausgabe?
breq LcdTextRet ; ja
cpi rmp,0xFF ; Fuellzeichen?
breq LcdText1 ; ja, naechstes Zeichen
cpi rmp,0x0D ; Zeilenwechsel?
brne LcdText2 ; Kein Zeilenwechsel
inc rLine ; Naechste Zeile
mov rmp,rLine ; Setze Zeile
rcall LcdLineSet ; Stelle Zeile ein
rjmp LcdText1 ; weiter mit Zeichen
LcdText2: ; Ausgabe Zeichen
rcall LcdD4Byte ; Zeichen ausgeben
rjmp LcdText1 ; und weiter
LcdTextRet:
ret ; Fertig
;
; Setzt den Ausgabecursor auf den
; Zeilenanfang der ausgewaehlten Zeile
; Zeile: rmp 0 .. 3
LcdLineSet:
cpi rmp,1 ; Zeile 2?
brcs LcdLineSet1 ; nach Zeile 1
breq LcdLineSet2 ; nach Zeile 2
cpi rmp,2 ; Zeile 3?
breq LcdLineSet3 ; nach Zeile 3
rjmp LcdLineSet4 ; nach Zeile 4
LcdLineSet1:
ldi rmp,0x80 ; Zeile 1
rjmp LcdC4Byte ; Ausgabe Kontrollwort
LcdLineSet2:
ldi rmp,0xC0 ; Zeile 2
rjmp LcdC4Byte ; Ausgabe Kontrollwort
LcdLineSet3:
ldi rmp,0x80+20 ; Zeile 3
rjmp LcdC4Byte ; Ausgabe Kontrollwort
LcdLineSet4:
ldi rmp,0xC0+20 ; Zeile 4
rjmp LcdC4Byte ; Ausgabe Kontrollwort
;
; Datenwort-Ausgabe im 4-Bit-Modus
; Daten in rmp
LcdD4Byte:
rcall LcdBusy ; warte bis busy = Null
sbi pLcdCO,bLcdCORS ; setze RS-Bit
rjmp Lcd4Byte ; gib Byte in rmp aus
;
; Kontrollwort-Ausgabe im 4-Bit-Modus
; Daten in rmp
LcdC4Byte:
rcall LcdBusy ; warte bis busy = Null
cbi pLcdCO,bLcdCORS ; loesche RS-Bit
; Ausgabe Byte im 4-Bit-Modus mit Busy
Lcd4Byte:
push rmp ; rmp auf Stapel legen
andi rmp,0xF0 ; unteres Nibble loeschen
in rmo,pLcdDI ; Lese Data-Input-Port
andi rmo,0x0F ; oberes Nibble loeschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
pop rmp ; rmp wieder herstellen
andi rmp,0x0F ; oberes Nibble loeschen
swap rmp ; Nibble vertauschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
ret ; fertig
;
; Warte bis Busy Null
LcdBusy:
push rmp ; rette rmp
ldi rmp,mLcdDRR ; Lesemaske
out pLcdDR,rmp ; in Richtungsregister
cbi pLcdCO,bLcdCORS ; loesche RS
sbi pLcdCO,bLcdCORW ; R/W setzen
LcdBusy1:
sbi pLcdCO,bLcdCOE ; setze LCD-Enable
nop
in rLese,pLcdDI ; lese oberes Nibble
cbi pLcdCO,bLcdCOE ; loesche LCD-Enable
andi rLese,0xF0 ; unteres Nibble loeschen
sbi pLcdCO,bLcdCOE ; setze LCD-Enable
nop
in rmp,pLcdDI ; lese unteres Nibble
cbi pLcdCO,bLcdCOE ; loesche LCD-Enable
andi rmp,0xF0 ; loesche unteres Nibble
swap rmp ; oberes/unteres Nibble tauschen
or rLese,rmp ; oberes und unteres Nibble
sbrc rLese,7 ; ueberspringe bei Busy=0
rjmp LcdBusy1 ; wiederhole bis Busy=0
cbi pLcdCO,bLcdCORW ; R/W loeschen
ldi rmp,mLcdDRW ; Schreibmaske
out pLcdDR,rmp ; in Richtungsregister
pop rmp ; rmp wieder herstellen
ret ; zurueck
;
; Kontrollwort-Ausgabe im 8-Bit-Modus
; Datenbyte in rmp
LcdC8Byte:
cbi pLcdCO,bLcdCORS ; RS-Bit loeschen
andi rmp,0xF0 ; unteres Nibble loeschen
in rmo,pLcdDI ; Lese Data-Input-Port
andi rmo,0x0F ; oberes Nibble loeschen
or rmp,rmo ; unteres und oberes Nibble
out pLcdDO,rmp ; an Datenport LCD
nop ; ein Takt warten
sbi pLcdCO,bLcdCOE ; LCD-Enable aktivieren
nop ; ein Takt warten
cbi pLcdCO,bLcdCOE ; LCD-Enable aus
ret ; fertig
;
; ------ Warteroutinen ------------------
Warte50ms: ; Warteroutine 50 ms
.equ c50ms = 50000
.equ n50ms = (c50ms-16)/4
; rcall: + 3
push ZH ; + 2
push ZL ; + 2
ldi ZH,HIGH(n50ms) ; + 1
ldi ZL,LOW(n50ms) ; + 1
rjmp Warte ; + 2, gesamt = 11
Warte5ms: ; Warteroutine 5 ms
.equ c5ms = 5000
.equ n5ms = (c5ms-16)/4
push ZH
push ZL
ldi ZH,HIGH(n5ms)
ldi ZL,LOW(n5ms)
rjmp Warte
; Warteroutine Z Takte
Warte: ; Warteschleife, Takte = 4*(n-1)+11 = 4*n + 7
sbiw ZL,1 ; + 2
brne Warte ; + 1 / 2
pop ZL ; + 2
pop ZH ; +2
ret ; + 4, Gesamt=4*n+18
;
Und das hier ist der Lohn der ganzen Arbeit: jede Menge schöne Pfeile.
Hier wurde statt CPI mal "TST Register"
verwendet. Neu ist ADIW Register,N, was die Konstante N zum Doppelregister
hinzuzählt (geht nur mit R24, R26, R28 und R30).
Noch ein Hinweis: Alle hier beschriebenen Modi (4/8-Bit-Ansteuerung, Warten/Busy,
Spezialzeichenerzeugung) sind in einer fortgeschrittenen LCD-Include-Datei
zusammengeführt, die man recht einfach in jeden Assembler-Quellcode
einfügen kann, nach Bedarf konfigurieren und die alle Aufrufe zur
Verfügung stellt, die man zur Ansteuerung von LCDs braucht. Sie ist unter
dieser URL
beschrieben und
hier als
Assembler-Include-Datei herunterladbar.
©2016-2018 by http://www.gsc-elektronic.net