Pfad: Home ==> Mikrobeginner ==> 10. LCD-Anzeige     This page in English (external): Flag EN
ATtiny24

Lektion 10: Lcd-Anzeige am ATtiny24


Um eine LCD an einen AVR anzuschließen, brauchen wir ein paar Pins mehr. Die folgenden Experimente verwenden daher einen ATtiny24. Die LCD wird mit zwei Methoden angesteuert: mit festen Warteschleifen und mit Auswertung des Busy-Flags.

10.0 Übersicht

  1. Einführung in LCD-Anzeigen
  2. Einführung in den ATtiny24
  3. Hardware, Bauteile, Aufbau
  4. Ansteuerung mit Warteschleifen
  5. Ansteuerung mit Busy-Flag
  6. Darstellung von eigenen Zeichen

10.1 Einführung in LCD-Anzeigen

10.1.1 Allgemeines über LCD-Anzeigen

LCD oben Es gibt unzählige Farben und Formen von LCDs. Grau, grün, gelb, blau, da ist für jeden Geschmack etwas dabei. Es gibt Text- und Graphikpixel-Anzeigen. Dies hier befasst sich mit Textanzeigen. Diese sind am einfachsten anzusteuern und bieten für die meisten Zwecke auch ausreichend Möglichkeiten der Darstellung.

Textanzeigen arbeiten meist mit einer Pixelmatrix von 5x8, was für die Erkennung von Zeichen auch aus einiger Entfernung völlig ausreicht.

Der Vorteil von LCD-Anzeigen (gegenüber z. B. Siebensegmentanzeigen) ist der niedrige Strombedarf von etwa 1 bis 2 mA. Für Batteriebetrieb ist das ideal. Um den gleichen Kontrast zu erzielen ist bei Siebensegment mindestens das 20-fache nötig. Außerdem ist Textdarstellung mit Siebensegment grausig, und bei 8 oder gar 16 Zeichen ist ein arger Hardwareverhau nötig. Das entfällt bei LCDs weitgehend.

LCD unten Auch bei der Anzahl Zeilen und Zeichen herrscht große Vielfalt. Einzeilige LCD mit acht Zeichen sind ideal für kleine Messgeräte, da stören mehr Zeichen eher. Zweizeilige LCD mit 8, 16 oder 20 Zeichen pro Zeile können schon eine ganze Menge darstellen. Für mehr gibt es vierzeilige LCD mit 20 oder 24 Zeichen.

10.1.2 Interfaces von LCD-Anzeigen

Die Anschlüsse von LCD sind quasi normiert, d. h. nahezu alle verwenden die gleiche Reihenfolge von Anschlüssen. Es gibt aber Varianten, z. B. 18-pinnige.

Anschluesse
Die einzelnen Pins bedeuten folgendes:
PinNameBedeutung
1VSSBetriebsspannung, Minus
2VDDBetriebsspannung, Plus, 3 oder 5 Volt
3VEERegelspannung für Kontrast
4RSRegister Select, 0=Control, 1=Daten
5R/WRead/Write, 0=Schreiben in LCD, 1=Lesen von LCD
6EEnable, 0=inaktiv, 1=aktiv/Lesen/Schreiben
7D0Datenbit 0
8D1Datenbit 1
9D2Datenbit 2
10D3Datenbit 3
11D4Datenbit 4
12D5Datenbit 5
13D6Datenbit 6
14D7Datenbit 7
15AHintergrundbeleuchtung Anode
16KHintergrundbeleuchtung 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. 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):

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.
2,1 mA
I = 2,1 mA, U = 2,9 V
7,4 mA
I = 7,4 mA, U = 3,4 V
12,0 mA
I = 12,0 mA, U = 3,8 V
18,3 mA
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.

Home Top LCD-Anzeigen Hardware Wartemodus Busy-Modus Zeichen


10.2 Einführung in den ATtiny24

ATtiny24 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.

Home Top LCD-Anzeigen Hardware Wartemodus Busy-Modus Zeichen


10.3 Hardware, Bauteile und Aufbau

10.3.1 Schaltbild

Schalbild LCD Das Schaltbild zeigt, wie die LCD vom ATtiny24 angesteuert wird:

10.3.2 Bauteil LCD-Anzeige

Stiftleiste 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

ATtiny24 Das hier ist der 14-polige ATtiny24.

10.3.4 Bauteil Fassung 14-polig

Fassung In diese Fassung passt der Prozessor.

10.3.5 Trimmer

Trimmer 10k 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

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.

Home Top LCD-Anzeigen Hardware Wartemodus Busy-Modus Zeichen


10.4 Ansteuerung der LCD mit Warteschleifen

10.4.1 Zeitbedarf und Konstruktion von Warteschleifen

Die folgenden Zeitbedarfe sind bei der Ansteuerung der LCD zu beachten: 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.

Home Top LCD-Anzeigen Hardware Wartemodus Busy-Modus Zeichen


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.

Warten 50 ms Die Unterroutine ist recht exakt, nur zwei Taktzyklen länger als berechnet.

Warten 5 ms Dasselbe bei der Routine für 5 ms Verzögerung.

Wait 100 µs Und wiederum für 100 µs.

Warten 40 µ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.


Home Top LCD-Anzeigen Hardware Wartemodus Busy-Modus Zeichen


10.5 Ansteuerung im Busy-Modus

10.5.1 Abfragen des Busy-Flags

Schaltbild 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.

Home Top LCD-Anzeigen Hardware Wartemodus Busy-Modus Zeichen


10.6 Neue Zeichen definieren

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:
  1. 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.
  2. 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).
  3. 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

Zeichengenerator 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.

Assembler-Tabelle

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:

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
;

Zeichenausgabe 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.

Home Top LCD-Anzeigen Hardware Wartemodus Busy-Modus Zeichen


©2016-2018 by http://www.gsc-elektronic.net