Pfad:
Home ==>
Mikrobeginner ==> 11. EEPROM mit LCD-Anzeige
This page in English (external):
Lektion 11: EEPROM mit LCD am ATtiny24
Neu in dieser Lektion ist der lesende und schreibende Zugriff auf das interne
EEPROM und die Darstellung von Dezimalzahlen auf der LCD.
11.0 Übersicht
- Einführung in das EEPROM
- Einführung in die Dezimalumwandlung
- Hardware, Bauteile, Aufbau
- Ein Einschaltzähler
- Ein 16-Bit-Einschaltzähler
11.1.1 Das EEPROM als nichtflüchtiger Speicher
Das prozessorinterne statische RAM ist flüchtig, das heißt sein
Inhalt übersteht ein Abschalten der Betriebsspannung nicht. Startet der
Prozessor dann neu, muss er den Inhalt des SRAM wieder auf vorprogrammierte
Anfangswerte setzen.
Das eingebaute EEPROM ist hingegen nichtflüchtig, d. h. sein Inhalt
bleibt auch ohne Betriebsspannungsversorgung über nahezu beliebig lange
Zeiträume erhalten. Typische Inhalte, für die das sinnvoll ist, sind
z. B. verstellbare Weckzeitpunkte einer Uhr, voreingestellte und vom
Anwendber einstellbare Laufzeiten oder versteckte Einschaltzähler
für elektrische Geräte.
Da EEPROM-Speicher nur eine gewisse Anzahl Schreibvorgänge verkraften,
sind sie als Erweiterung des SRAM ungeeignet. Sie sind nicht gedacht für
einige 100 Schreibvorgänge pro Sekunde. Da pro Schreibvorgang ohnehin
etwa 3 ms nötig sind, kriegt man mehr schon gar nicht hin.
Der ATtiny24 hat 128 Byte EEPROM. Es startet an Adresse 0x0000 und
endet daher bei 0x007F.
11.1.2 Das EEPROM programmieren
Das EEPROM wird beim Löschen ("Erase") des Flash-Speichers
ebenfalls gelöscht. Ebenso wie beim Flash-Speicher bedeutet Löschen
das vollständige Beschreiben aller EEPROM-Zellen mit Einsen (0xFF). Die
meisten und die neueren AVR-Typen erlauben es jedoch, dieses doppelte
Löschen abzuschalten. Dazu muss im Fuses-Bereich die Fuse
"EESAVE" gesetzt werden. Bei gesetztem Fusebit ist das Ändern
des EEPROM-Inhaltes nur über das explizierte Programmieren und über
Schreiboperationen des Prozessors möglich.
Programmieren des EEPROMs geht folgendermaßen. Im Assemblercode wird
folgendes geschrieben:
.ESEG ; Umschalten zum EEPROM-Segment
.ORG 0 ; Bei Adresse 0 beginnen
.db 0,1,2,3,4 ; Zahlen schreiben
.db "01234" ; Text schreiben
Die Direktive ".ESEG" bewirkt, dass die erzeugten Bytes der
folgenden Operationen in das EEPROM-Segment ausgegeben werden. Sie landen
beim Assemblieren in einer Datei mit Namen "Quellcode.eep", die
im Intel-Hex-Format kodiert ist. Ihr Inhalt kann mit einem einfachen
Texteditor inspiziert werden.
Da das EEPROM byteweise organisiert ist, kann jede beliebige Anzahl Bytes
in das EEPROM-Segment geschrieben werden, bis es voll ist. Mit
".ORG Adresse" kann bewirkt werden, dass nur der vom Segment
definierte Bereich des EEPROMs beschrieben wird und andere Inhalte des
EEPROMs unverändert erhalten bleiben.
Im EEPROM-Segment sind nur ".DB"- und ".DW"-Direktiven
zulässig.
Zum eigentlichen Schreiben des erzeugten Inhalts in das EEPROM öffnet
man die Tools im Studio, wählt im EEPROM-Programmierunterfenster durch
Klicken auf das kleine Quadrat mit "..." die erzeugte
".eep"-Datei aus. Ihr Inhalt wird mit "WRITE" in das
EEPROM übertragen.
Im gleichen Programmierdialog kann man den Inhalt des EEPROMs auch auslesen
oder verifizieren.
11.1.3 Vom Programm aus das EEPROM auslesen
Das Auslesen von Inhalten des EEPROMs umfasst folgende Einzelschritte:
- Durch Abfragen des EEPE-Bits im EEPROM-Kontrollregister EECR ist
sicherzustellen, dass das EEPROM zum Lesen bereit ist.
- Die zu lesende EEPROM-Adresse wird in die beiden Register EEARH (MSB)
und EEARL (LSB) zu schreiben. Selbst wenn der AVR nur bis zu
256 Byte hat, sollte das EEARH mit Nullen beschrieben werden.
- Dann wird das Read Enable Bit EERE im Kontrollregister Eins gesetzt.
Dies blockiert die Tätigkeit des Prozessors für vier Takte und
der Inhalt des EEPROMs an der eingestellten Adresse kann danach sofort
aus dem Datenregister "EEDR" ausgelesen werden.
Bei nachfolgenden Lesevorgängen muss nur die LSB-Adresse neu geschrieben
werden, solange sich das MSB nicht ändert.
12.1.4 Daten vom Programm aus in das EEPROM schreiben
Das Schreiben geht folgendermaßen:
- Zunächst ist wie beim Lesen sicherzustellen, dass
Schreibvorgänge abgeschlossen sind.
- Durch Schreiben von Null in das EECR sicherstellen, dass EEPM1 und
EEPM0 sowie EERE gelöscht sind (M1 und M0 = Null: lösche die
Zelle zuerst und beschreibe sie danach in einem Schritt).
- MSB und LSB der Adresse in die Adressregister EEARH und EEARL
schreiben.
- Das zu programmierende Byte in das Datenregister EEDR schreiben.
- Das EEMPE-Bit im EEPROM-Kontrollregister auf Eins setzen. Dieses Bit
löscht sich nach vier Taktzyklen selbst.
- Innerhalb der vier Taktzyklen das EEPE-Bit auf Eins setzen. Der
Programmiervorgang dauert 3,2 ms, danach setzt sich das EEPE-Bit
selbst zurück.
Bei gestartem Programmiervorgang (nicht vorher!) kann durch Setzen des
EERIE-Bits das Auslösen des EE_RDY-Interrupts ausgelöst werden.
Dieses Bit muss nach Abschluss der Programmiervorgänge unbedingt und
sofort wieder gelöscht werden, da das schreibbereite EEPROM sonst einen
Dauerinterrupt auslöst und den Prozessor blockiert.
Da wir jetzt eine LCD haben und schon in diesem Kapitel gerne Zahlen in
lesbarer Form ausgeben wollen, muss es jetzt sein: wir lernen
Binärzahlen in Dezimalzahlen umzuwandeln.
11.2.1 Die Primitivstversion
Um eine 8-Bit-Binärzahl in eine Dezimalzahl zu verwandeln, könnten
wir mit "CPI rZahl,200" zunächst abfragen, ob die erste Ziffer
eine 2 ist. Wenn ja, käme eine ASCII-Zwei (hex 32, dezimal 50) zur
Anzeige. Wenn nicht, testen wir auf 100, was bei Erfolg eine ASCII-1
produziert. Wenn nicht, ist es eine ASCII-0 oder, wenn wir fürende
Nullen unterdrücken wollen, ein Leerzeichen (hex 20, dezimal 32) an die
LCD zu senden.
Nun käme die zweite Ziffer dran. War die Zahl größer oder
gleich 200, müssten wir von der Zahl mit "SUBI rZahl,200" die
200 abziehen. Analog bei größer oder gleich 100. Um die zweite
Ziffer zu ermitteln, müssten wir nun 90, 80, 70 bis herunter zu 10
vergleichen. Das würde eine lustige, aber langwierige Angelegenheit.
Wers nicht glaubt, kann ja mal den Assemblercode dafür in die Tasten
hauen und mit dem Studio simulieren, ob es auch richtig herauskommt.
Die dritte, letzte Ziffer ist dann relativ einfach: zum Rest wird einfach
hex 32 addiert und zur Anzeige gesendet. Hingeschrieben: "ADDI
rZahl,0x32" und reingefallen: ADDI gibt es in AVR-Assembler gar nicht.
Der Kenner schreibt stattdessen "SUBI rZahl,-0x32", was selbiges
tut.
11.2.2 Die bessere Version
Es gibt natürlich komfortablere Versionen der Umwandlungsaufgabe. Die
besteht darin, mit einem Zähler festzustellen, wie oft man 100 bzw. bei
der zweiten Ziffer 10 von der Zahl abziehen kann. Das kann so gehen:
; die Binaerzahl ist in R0
ldi R16,100
clr R1 ; R1 ist Zaehler
Zaehlen1:
cp R0,R16 ; vergleiche mit 100
brcs Ziffer1 ; Ueberlauf, Ziffer fertig
sub R0,R16 ; 100 abziehen
inc R1 ; Zaehler erhoehen
rjmp Zaehlen1 ; weiter vergleichen
Ziffer1:
ldi R16,'0' ; ASCII-Null
add R16,R1 ; Ergebnis dazu zaehlen
; Ziffer 1 ausgeben
ldi R16,10 ; jetzt die Zehner
clr R1 ; bei Null anfangen
Zaehlen2:
cp R0,R16 ; vergleiche mit 10
brcs Ziffer2 ; Ueberlauf, Ziffer fertig
sub R0,R16 ; 10 abziehen
inc R1 ; Zaehler erhoehen
rjmp Zaehlen2 ; weiter vergleichen
Ziffer2:
ldi R16,'0' ; ASCII-Null
add R16,R1 ; Zaehler addieren
; Ziffer 2 ausgeben
ldi R16,'0' ; ASCII-Null
add R16,R0 ; Addiere Zahlenrest
; Ziffer 3 ausgeben
Neu ist die Instruktion CP Register,Register, die das zweite Register
temporär vom ersten Register subtrahiert und die entsprechenden Flaggen
setzt.
Die beiden rot markierten Teile kommen doppelt vor, wir könnten diesen
Teil als Unterfunktion formulieren und mit RCALL aufrufen.
11.2.3 Die 16-Bit-Version
Haben wir eine 16-Bit-Zahl in zwei Registern, dann muss das Vergleichen und
Abziehen im 16-Bit-Modus erfolgen. Das geht dann so:
; die Binaerzahl ist in R1:R0
ldi ZH,HIGH(10000) ; Zehntausender
ldi ZL,LOW(10000)
rcall Zaehlen
; Ziffer 1 ausgeben
ldi ZH,HIGH(1000) ; Tausender
ldi ZL,LOW(1000)
rcall Zaehlen
; Ziffer 2 ausgeben
ldi ZH,HIGH(100) ; Hunderter
ldi ZL,LOW(100)
rcall Zaehlen
; Ziffer 3 ausgeben
ldi ZL,LOW(10) ; Zehner
rcall Zaehlen
; Ziffer 4 ausgeben
ldi R16,'0'
add R16,R0
; Ziffer 5 ausgeben
; fertig
; Unterprogramm
Zaehlen:
clr R16 ; R16 ist Zaehler
Zaehlen1:
sub R0,ZL ; ziehe LSB ab
sbc R1,ZH ; ziehe MSB ab
brcs Zaehlen2 ; Uebertrag
inc R16
rjmp Zaehlen1
Zaehlen2:
add R0,ZL ; addiere LSB
adc R1,ZH ; addiere MSB
subi R16,-'0' ; addiere ASCII-Null
ret ; zurueck
Neue Instruktionen hier:
- SBC Register,Register: subtrahiere das zweite Register plus das Carry-Bit vom Ersten.
Noch ein besonderer Hinweis zur Z-Flagge bei der SBC-Instruktion. Die
ist sehr interessant konstruiert. Mit der Z-Flagge kann man nicht
nur die bei letzten Subtraktion festgestellte Gleichheit erkennen.
Wenn die vor der SBC-Instruktion nicht Eins war, dann bleibt sie
unanhängig vom Ergebnis des Subtrahierens mit Übertrag
auch Null. Nur wenn sie gesetzt ist und das SBC auch Gleichheit
ergab, dann bleibt sie auch gesetzt. Dieses etwas ungewöhnliche
Verhalten der Z-Flagge ermöglecht es, mehr als nur ein einziges
SUB oder SBC zu verwenden, um die Gleichheit bei zwei oder mehr
Bytes herauszufinden: nur wenn nach allen SBCs die Z-Flagge gesetzt
ist, sind auch alle Null geworden.
Das Problem ist gelöst, nur hat die Lösung noch einen Makel:
führende Nullen würden als Nullen ausgegeben.
Damit ist das Instrumentarium und die Methode klar, um selbst 24-Bit-
oder 32-Bit-Zahlen zu handhaben.
11.3.1 Aufgabe
Die folgende Aufgabe ist zu lösen: Bei jedem Reset und beim Anlegen der
Betriebsspannung ist ein Zähler zu erhöhen und sein alter und
neuer Stand auf der LCD auszugeben.
11.3.2 Schaltbild
Damit wir nicht ständig die Betriebsspannung abziehen müssen,
montieren wir einen Resettaster.
Die Bauteile kennen wir alle schon, die Montage ist auch simpel.
11.3.3 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.
11.4.1 LCD-Routinen als Include-Datei
So langsam wird das Einfügen der immer gleichen LCD-Routinen langweilig.
Wir nehmen diese Routinen aus dem Programm heraus, fügen noch die eine
oder andere zusätzliche Routine hinzu, stellen die Eigenschaften in
einem aussagekräftigen Kopf dar und formulieren so eine Include-Datei.
Im eigentlichen Programm fügen wir diese dann mit .INCLUDE
"Dateiname.inc" an einer freien Stelle in den Code ein. Der
Assembler tut dann so, als ob dort enthaltenen Zeilen im Quellcode
stünden.
Das hier ist die perfekte 4-Bit-Busy-LCD-Include-Datei
(zum Quellcode geht es hier), ohne Pfadangabe muss
die Include Routine in den gleichen Ordner in dem die aufrufende .asm-Datei
liegt).
;
; ************************************************
; * Include Code zur 4-Bit-Ansteuerung einer LCD *
; * im Busy-Flag-Modus, mit Basisroutinen *
; * (C)2016 by www.gsc-elektronic.net *
; ************************************************
;
; Der Include-Code enthaelt Basisroutinen fuer die
; 4-Bit-Ansteuerung einer 4*20-LCD am ATtiny24 im
; Busy-Modus (bidirektionaler Datenbus).
;
; Diese angegebenen Register sind im Hauptprogramm
; zu definieren:
;
; -------- Verwendete Register -------------------
; rmp, rmo, rLine, rLese, R0
;
; Die folgenden Routinen werden zur Verfuegung ge-
; stellt:
;
; -------- Routinen ------------------------------
; Routine Macht Aufrufparameter Register
; ------- ------------ -------------------- --------
; LcdInit Initiieren Keine rmp,rmo
; rLese
; LcdChars Erzeuge ei- ZH:ZL Zeichentabelle rmp,rmo
; gene Zeichen rLese,R0
; LcdText Gib den Text ZH:ZL Texttabelle rmp,rmo
; im Flash in 0D=Naechste Zeile rLine
; der Tabelle FF=Ignoriere rLese
; in Zeile 1 FE=Ende Tabelle
; aus
; LcdTextC Gib den Text (wie LcdText) (wie LcdText)
; im Flash an
; der aktuellen
; Position aus
; LcdSRam Gib den Text XH:XL: zeigt auf rmp,rmo
; im SRAM aus Position im SRAM rLese
; ZH:ZL: Lcd-Position
; rmp: Anzahl Zeichen
; LcdPos Setze Ausga- ZH: Zeile 0..3 rmp,rmo
; beposition ZL: Spalte 0..19 rLese
; LcdLine Zeile ein- rmp: Zeile 0..3 rmp,rmo
; stellen, rLese
; Spalte=0
; LcdLineN Zeile ein- N: Zeile 1..4 rmp,rmo
; stellen, rLese
; Spalte=0
;
; ---------Ports und Portpins der LCD ------------
; 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 = 0x0F ; LCD-Datenport-Maske Lesen
;
; --------- 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 im Flash auf der LCD
; Z zeigt auf Text im Flash
LcdText:
rcall LcdLine1 ; auf Zeile 1
LcdTextC: ; Setze an aktuelle Position fort
clr rLine ; Zeilenzaehler
LcdText1:
lpm rmp,Z+ ; lese Zeichen aus Flash
cpi rmp,0xFE ; 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 LcdLine ; Stelle Zeile ein
rjmp LcdText1 ; weiter mit Zeichen
LcdText2: ; Ausgabe Zeichen
rcall LcdD4Byte ; Zeichen ausgeben
rjmp LcdText1 ; und weiter
LcdTextRet:
ret ; Fertig
;
; Ausgabe von Text im SRAM auf dem LCD
; ZH = Zeile 0..3; ZL = Spalte 0..19,
; XH:XL = Adresse im SRAM, rmp: Anzahl
LcdSram:
mov R0,rmp ; kopieren Anzahl
rcall LcdPos ; setze LCD-Position
LcdSram1:
ld rmp,X+ ; lese Ausgabe-Byte
rcall LcdD4Byte ; auf LCD ausgeben
dec R0 ; Zaehler abwaerts
brne LcdSram1 ; noch Zeichen
ret ; Fertig
;
; Setzt den Ausgabecursor auf den
; Zeilenanfang der ausgewaehlten Zeile
; Zeile: rmp 0 .. 3
LcdLine:
cpi rmp,1 ; Zeile 2?
brcs LcdLine1 ; nach Zeile 1
breq LcdLine2 ; nach Zeile 2
cpi rmp,2 ; Zeile 3?
breq LcdLine3 ; nach Zeile 3
rjmp LcdLine4 ; nach Zeile 4
LcdLine1:
ldi rmp,0x80 ; Zeile 1
rjmp LcdC4Byte ; Ausgabe Kontrollwort
LcdLine2:
ldi rmp,0xC0 ; Zeile 2
rjmp LcdC4Byte ; Ausgabe Kontrollwort
LcdLine3:
ldi rmp,0x80+20 ; Zeile 3
rjmp LcdC4Byte ; Ausgabe Kontrollwort
LcdLine4:
ldi rmp,0xC0+20 ; Zeile 4
rjmp LcdC4Byte ; Ausgabe Kontrollwort
;
; Setzt den Ausgabecursor auf die Position
; in ZH:ZL, ZH ist Zeile (0..3), ZL ist Spalte
LcdPos:
ldi rmp,0x80 ; Setze Zeile 1
cpi ZH,1 ; Zeile 2?
brcs LcdPos1 ; Zeile = 1
ldi rmp,0xC0 ; Setze Zeile 2
breq LcdPos1 ; Zeile = 2
ldi rmp,0x80+20 ; Setze Zeile 3
cpi ZH,2 ; Zeile 3?
breq LcdPos1 ; Zeile = 3
ldi rmp,0xC0+20 ; Zeile = 4
LcdPos1:
add rmp,ZL ; addiere Spalte
rjmp LcdC4Byte ; Ausgabe Kontrollwort
;
; Eigene Zeichen definieren
LcdChars:
lpm ; Lese Adresse
tst R0 ; auf Null pruefen
breq LcdChars2 ; fertig
adiw ZL,2 ; ueberlese Fuellbyte
ldi rLine,8
LcdChars1:
mov rmp,R0 ; Adresse setzen
rcall LcdC4Byte ; an LCD
lpm rmp,Z+ ; lese Daten
rcall LcdD4Byte ; Ausgabe an LCD
inc R0 ; Adresse erhoehen
dec rLine ; Zaehler abwaerts
brne LcdChars1 ; noch welche
rjmp LcdChars ; naechstes Zeichen
LcdChars2:
ret ; fertig
;
; 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,pLcdDO ; 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
in rmp,pLcdDR
andi 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
in rmp,pLcdDR
ori rmp,mLcdDRW
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,pLcdDO ; 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
;
; Ende Include
;
Mit dieser Include werden die nachfolgenden Programme sehr viel kürzer.
11.4.2 Das Programm
Das Programm für die Lösung der Aufgabe ist nachfolgend
dargestellt (zum Quellcode geht es hier, kombiniert mit
den LCD-Routinen).
;
; *****************************************************
; * EEPROM lesen und beschreiben mit ATtiny24 und LCD *
; * (C)2016 by http://www.gsc-elektronic.net *
; *****************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ------- Programmablauf ----------------
;
; Initiiert die 4*20-LCD am ATtiny24, liest
; einen 8-Bit-Zaehler aus dem EEPROM, er-
; hoeht ihn und schreibt den neuen Stand
; in das EEPROM. Zeigt alten und neuen
; Stand dezimal auf der LCD an.
;
; ------- Ports, Portbits ---------------
; (Alle LCD-Ports sind in der Include-Routine definiert)
;
; ------- Register ----------------------
; benutzt: R0 lokal von der Zeichendefinitionsroutine
; frei: R1 .. R15
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; weiteres Vielzweckregister
.def rLine = R18 ; Zeilenzaehler LCD
.def rLese = R19 ; Leseergebnis vom LCD-Datenport
.def rEep = R20 ; Datenbyte fuer EEPROM-Zaehler
; frei R21 .. R25
; benutzt: XH:XL R27:R26 Zeiger
; frei YH:YL R29:R28
; verwendet: R31:R30, ZH:ZL, fuer Zaehlen und LCD
;
; ------- Konstanten --------------------
.equ Takt = 1000000 ; Default-Taktfrequenz
.equ EepAdresse = 0 ; Lese- und Schreibadresse EEPROM
;
; ------- SRAM --------------------------
.DSEG ; in Datensegment
.ORG 0x0060 ; an den Beginn
Zahl: ; Umwandlung Byte in ASCII-Ziffernfolge
.BYTE 3 ; braucht drei Zeichen
;
; -------- EEPROM-Startinhalt -----------
.ESEG ; EEPROM-Segment
.ORG EepAdresse
.db 0 ; Init EEPROM-Zaehler
;
; -------- 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
; Lese Byte von EEPROM und gib in Zeile 3 aus
rcall EepRead
inc rEep ; Erhoehe EEPROM-Byte
rcall EepWrite ; in EEPROM schreiben
; 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 "Alter Stand = ",0x0D,0xFF ; Zeile 3
.db "Neuer Stand = ",0xFE ; Zeile 4
;
; EEPROM-Byte lesen
EepRead:
; Warten bis EEPROM bereit ist
sbic EECR, EEPE ; EEPROM-Enable-Bit abfragen
rjmp EepRead ; noch nicht fertig
; EEPROM-Lese-Adresse setzen
ldi rmp,HIGH(EepAdresse) ; MSB in Adressregister
out EEARH,rmp ; fuer ATtiny24/44 unnoetig, da weniger als 257 Byte EEPROM
ldi rmp,LOW(EepAdresse) ; LSB
out EEARL,rmp ; in LSB-Adressregister
; EERE-Bit im EEPROM-Kontrollregister setzen
sbi EECR,EERE
; Lese Byte aus Datenregister
in rEep,EEDR
; Gelesene Zahl in ASCII umwandeln
mov R0,rEep ; Zahl in R0 kopieren
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ergebnis
ldi XL,LOW(Zahl) ; dto. LSB
rcall Dezimal ; Wandle in Dezimalzahl um
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ausgabe
ldi XL,LOW(Zahl) ; dto., LSB
ldi ZH,2 ; in Zeile 3
ldi ZL,17 ; in Spalte 17
ldi rmp,3 ; drei Zeichen
rcall LcdSram ; Aufruf Include-Routine
ret
;
; In EEPROM schreiben
EepWrite:
; Warten bis EEPROM fertig ist
sbic EECR, EEPE ; Enable-Bit im Kontrollregister
rjmp EepWrite ; noch nicht fertig
; EEPROM-Programmiermodus setzen
ldi rmp,(0<<EEPM1)|(0<<EEPM0) ; loeschen und schreiben
out EECR,rmp ; in EEPROM-Kontrollregister
; Adresse in Adressenregister
ldi rmp,HIGH(EepAdresse) ; MSB
out EEARH,rmp ; bei ATtiny24/44 nicht noetig
ldi rmp,LOW(EepAdresse) ; LSB
out EEARL,rmp
; Zu schreibender Inhalt in Datenregister
out EEDR,rEep ; schreiben in Datenregister
; Memory Program enable EEMPE setzen
sbi EECR,EEMPE ; Schreiben ermoeglichen
; EEPROM schreiben mit EEPE = Eins
sbi EECR,EEPE ; schreiben starten, 3,4 ms Dauer
; neuen Inhalt auf LCD ausgeben
mov R0,rEep ; Zahl in R0 kopieren
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ergebnis
ldi XL,LOW(Zahl) ; dto. LSB
rcall Dezimal ; Wandle in Dezimalzahl um
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ausgabe
ldi XL,LOW(Zahl) ; dto., LSB
ldi ZH,3 ; in Zeile 4
ldi ZL,17 ; in Spalte 17
ldi rmp,3 ; drei Zeichen
rcall LcdSram ; Aufruf Include-Routine
ret
;
; Wandle R0 in eine Dezimalzahl um
; XH:XL = Position im SRAM
Dezimal:
set ; Setze T-Flagge fuer fuehrende Nullen
ldi rmp,100 ; dezimal 100
clr R1 ; R1 ist Ergebnis
Dezimal100:
cp R0,rmp ; Kleiner als 100?
brcs Dezimal2 ; ja
sub R0,rmp ; Abziehen 100
inc R1 ; Ergebnis um Eins erhoehen
rjmp Dezimal100 ; weiter abziehen
Dezimal2: ; fuehrende Nullen unterdruecken
tst R1 ; ist Ergebnis Null?
brne Dezimal3 ; nein
ldi rmp,' ' ; Leerzeichen
rjmp Dezimal4 ; weiter
Dezimal3: ; keine fuehrende Null
clt ; Flagge loeschen
ldi rmp,0x30 ; ASCII-Null
add rmp,R1 ; Ergebnis addieren
Dezimal4: ; Ergebnis speichern
st X+,rmp ; in SRAM speichern
; Zehner ermitteln
ldi rmp,10 ; dezimal 10
clr R1 ; Ergebnis loeschen
Dezimal10: ; Zehnerschleife
cp R0,rmp ; Vergleiche mit 10
brcs Dezimal5 ; schon fertig
sub R0,rmp ; Abziehen
inc R1
rjmp Dezimal10 ; Zehnerschleife
Dezimal5: ; Zehner sind ermittelt
brtc Dezimal6 ; fuehrende Nullflagge ist aus
tst R1 ; fuehrende Null
brne Dezimal6 ; keine Null
ldi rmp,' ' ; Leerzeichen
rjmp Dezimal7 ; direkt ausgeben
Dezimal6:
ldi rmp,'0' ; ASCII-Null
add rmp,R1 ; Ergebnis addieren
Dezimal7:
st X+,rmp ; Zehner speichern
; Einer sind uebrig
ldi rmp,'0' ; ASCII-Null
add rmp,R0 ; Rest addieren
st X+,rmp ; in SRAM speichern
ret
;
; Lcd4Busy-Routinen hinzufuegen
.include "Lcd4Busy.inc"
;
; Ende Quellcode
;
Beim Programmieren nicht vergessen, nach dem Brennen des Programmes die
erzeugte .eep-Datei in das EEPROM zu übertragen.
Das hier ist das Ergebnis der Bemühungen. Sieht perfekt aus, oder?
11.4.3 Die Simulation
Mit einer Simulation lässt sich die korrekte Simulation
der Funktionen verifizieren. Im Folgenden wird
avr_sim
verwendet um den Quellcode zu erproben.
Zuvor werden im Quellcode alle Aufrufe der LCD-Routinen (LcdInit,
LcdText, LcdSram) durch auskommentieren der Zeilen mit ";"
unwirksam gemacht. Zum Assemblieren des Quellcodes muss sich dennoch
die LCD-Include-Datei im gleichen Pfad befinden. Dann kann assembliert
werden.
Das ist der EEPROM-Inhalt wenn die Simulation beginnt. Der
Zähler bei Adresse 0x0000 ist auf Null gesetzt, der
Rest des EEPROMs ist gelöscht (0xFF).
Der Zählerinhalt des EEPROMs bei Adresse 0x0000 wurde
gelesen. Hier war der Inhalt 0x00. Das wird jetzt in
eine Dezimalzahl verwandelt und in ASCII-Kodierung in das
SRAM geschrieben.
Die ersten beiden Zeichen sind Leerzeichen (Platzhalter fuer
Hunderter und Zehner), dann kommt die ASCII-Null. Diese drei
Bytes werden in die LCD geschrieben (was hier nicht simuliert
wird).
Der Zähler rEep in R20 wurde hier um Eins erhöht.
Nun muss der erhöhte Zähler in das EEPROM
zurückgeschrieben werden. Um das zu tun, werden die
EEPROM-Portregister für die Adresse und das Datenbyte
beschrieben (hier markiert).
Zuerst wird dann das Bit Master Write Enable gesetzt.
Innerhalb von vier Taktzyklen des Prozessors muss dann das
Bit Write Enable gesetzt werden. Die Programmierung startet
nun.
Die erste Stufe des Schreibprozesses ist das Löschen
der Zelle, auf die das Adressregister zeigt. Ungefähr
zur halben Zeit des Schreibablaufs ist der Inhalt gelöscht
(alle Bits auf Eins).
Nachdem der Schreibprozess abgeschlossen ist, hat sich der
Inhalt der Zelle an Adresse 0x0000 geändert.
Die Zeit, die zum Schreiben benötigt wurde, beträgt
ca. 3,5 ms.
Nach dem Neustart der Simulation, ohne Neubeschreibung des
EEPROMs, ist der Zähler Eins, wird in ASCII umgewandelt
und in das SRAM geschrieben.
Natürlich führt die zweite Erhöhung zu
"2" in rEep in R20. Und so weiter, und so
weiter, ...
11.5.1 Aufgabe
Das vorherige Programm ist so umzubauen, dass nach Erreichen eines
definierten Zählerstands der Chip seine weitere Mitarbeit einstellt
(geplante Obsoleszenz). Damit auch höhere Grenzen eingestellt werden
können, ist dafür ein 16-Bit-Zähler zu verwenden. Bei
Erreichen der Grenze ist in Zeile 4 der LCD eine entsprechende Meldung
auszugeben.
11.5.2 Das Programm dazu
Dies hier ist das Programm dazu (zum Quellcode
geht es hier, kombiniert mit den LCD-Routinen).
;
; ********************************************************
; * EEPROM-Wort lesen und schreiben mit ATtiny24 und LCD *
; * (C)2016 by http://www.gsc-elektronic.net *
; ********************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ------- Programmablauf ----------------
;
; Initiiert die vierzeilige LCD am ATtiny24
; im 4-Bit-Busy-Modus. Liest einen 16-Bit-
; Zaehler aus dem EEPROM, erhoeht ihn,
; schreibt ihn wieder zurueck und stellt
; beide Zaehlerstaende auf der LCD dar.
; Erreicht der Zaehler eine voreingstellte
; Zahl, erscheint eine Ende-Meldung.
;
; ------- Ports, Portbits ---------------
; (Alle LCD-Ports sind in der Include-Routine definiert)
;
; ------- Register ----------------------
; benutzt: R0 bis R3 lokal von der Zeichendefinition und Dezimal
; frei: R4 .. R15
.def rmp = R16 ; Vielzweckregister
.def rmo = R17 ; weiteres Vielzweckregister
.def rLine = R18 ; Zeilenzaehler LCD
.def rLese = R19 ; Leseergebnis vom LCD-Datenport
.def rEepH = R20 ; MSB Datenbyte fuer EEPROM-Zaehler
.def rEepL = R21 ; dto., LSB
; frei R22 .. R25
; benutzt: XH:XL R27:R26 Zeiger
; frei YH:YL R29:R28
; verwendet: R31:R30, ZH:ZL, fuer Zaehlen, LCD
;
; ------- Konstanten --------------------
.equ Takt = 1000000 ; Default-Taktfrequenz
.equ EepAdresse = 0 ; Lese- und Schreibadresse EEPROM
.equ Obsoleszenz = 3 ; Obsoleszenzkriterium 1..65535
;
; ------- SRAM --------------------------
.DSEG ; in Datensegment
.ORG 0x0060 ; an den Beginn
Zahl: ; Umwandlung Byte in ASCII-Ziffernfolge
.BYTE 5 ; braucht fuenf Zeichen
;
; -------- EEPROM-Startinhalt -----------
.ESEG ; EEPROM-Segment
.ORG EepAdresse
.db LOW(32767),HIGH(32767) ; Init EEPROM-Zaehler
;
; -------- 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
; Lese Wort von EEPROM und gib in Zeile 3 aus
rcall EepReadW
inc rEepL ; Erhoehe EEPROM-Byte
brne MsbOk ; Kein Ueberlauf
inc rEepH
MsbOk:
; Obsolenz pruefen
rcall Obsolenz ; Ende-Meldung ausgeben
brcc Obsolet ; Geraet ist obsolet
rcall EepWriteW ; in EEPROM schreiben und in Zeile 4
Obsolet:
; 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 "Alter Stand = ",0x0D,0xFF ; Zeile 3
.db "Neuer Stand = ",0xFE ; Zeile 4
;
; Obsolenz prufen
Obsolenz:
cpi rEepL,LOW(Obsoleszenz) ; vergleiche mit Konstante
ldi rmp,HIGH(Obsoleszenz)
cpc rEepH,rmp ; mit Uebertrag
brcs ObsolenzRet
; Ende der Mitarbeit erreicht
rcall LcdLine4 ; in Zeile 4
ldi ZH,HIGH(2*ObsoletText) ; Text
ldi ZL,LOW(2*ObsoletText)
rcall LcdTextC
clc ; Carry-Flagge loesen
ObsolenzRet:
ret
; Text der Meldung
ObsoletText:
.db "Hoechstzahl erreicht",0xFE,0xFE
;
; EEPROM-Wort lesen
EepReadW:
; Warten bis EEPROM bereit ist
sbic EECR, EEPE ; EEPROM-Enable-Bit abfragen
rjmp EepReadW ; noch nicht fertig
; EEPROM-Lese-Adresse setzen
ldi rmp,HIGH(EepAdresse) ; MSB in Adressregister
out EEARH,rmp ; fuer ATtiny24/44 unnoetig, da weniger als 257 Byte EEPROM
ldi rmp,LOW(EepAdresse) ; LSB
out EEARL,rmp ; in LSB-Adressregister
; EERE-Bit im EEPROM-Kontrollregister setzen
sbi EECR,EERE
; Lese Byte aus Datenregister
in rEepL,EEDR ; in LSB
cbi EECR,EERE ; lesen aus
ldi rmp,LOW(EepAdresse+1) ; Adresse naechstes Byte
out EEARL,rmp ; in Adressregister
sbi EECR,EERE ; lesen einschalten
in rEepH,EEDR ; MSB aus Datenregister lesen
; Gelesene Zahl in ASCII umwandeln
mov R0,rEepL ; Zahl in R1:R0 kopieren
mov R1,rEepH
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ergebnis
ldi XL,LOW(Zahl) ; dto. LSB
rcall DezimalW ; Wandle Datenwort in Dezimalzahl um
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ausgabe
ldi XL,LOW(Zahl) ; dto., LSB
ldi ZH,2 ; in Zeile 3
ldi ZL,15 ; in Spalte 17
ldi rmp,5 ; drei Zeichen
rcall LcdSram ; Aufruf Include-Routine
ret
;
; In EEPROM schreiben
EepWriteW:
; Warten bis EEPROM fertig ist
sbic EECR,EEPE ; Enable-Bit im Kontrollregister
rjmp EepWriteW ; noch nicht fertig
; EEPROM-Programmiermodus setzen
ldi rmp,(0<<EEPM1)|(0<<EEPM0) ; loeschen und schreiben
out EECR,rmp ; in EEPROM-Kontrollregister
; Adresse in Adressenregister
ldi rmp,HIGH(EepAdresse) ; MSB
out EEARH,rmp ; bei ATtiny24/44 nicht noetig
ldi rmp,LOW(EepAdresse) ; LSB
out EEARL,rmp
; Zu schreibender Inhalt in Datenregister
out EEDR,rEepL ; schreiben in Datenregister
; Memory Program enable EEMPE setzen
sbi EECR,EEMPE ; Schreiben ermoeglichen
; EEPROM schreiben mit EEPE = Eins
sbi EECR,EEPE ; schreiben starten, 3,4 ms Dauer
EepWrite1:
sbic EECR,EEPE ; warten bis fertig
rjmp EepWrite1 ; noch nicht fertig
ldi rmp,LOW(EepAdresse+1) ; MSB
out EEARL,rmp
; Zu schreibender Inhalt in Datenregister
out EEDR,rEepH ; schreiben in Datenregister
; Memory Program enable EEMPE setzen
sbi EECR,EEMPE ; Schreiben ermoeglichen
; EEPROM schreiben mit EEPE = Eins
sbi EECR,EEPE ; schreiben starten, 3,4 ms Dauer
; neuen Inhalt auf LCD ausgeben
mov R0,rEepL ; Zahl in R1:R0 kopieren
mov R1,rEepH
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ergebnis
ldi XL,LOW(Zahl) ; dto. LSB
rcall DezimalW ; Wandle in Dezimalzahl um
ldi XH,HIGH(Zahl) ; SRAM-Puffer-Zeiger fuer Ausgabe
ldi XL,LOW(Zahl) ; dto., LSB
ldi ZH,3 ; in Zeile 4
ldi ZL,15 ; in Spalte 17
ldi rmp,5 ; drei Zeichen
rcall LcdSram ; Aufruf Include-Routine
ret
;
; Wandle R1:R0 in eine Dezimalzahl um
; XH:XL = Position im SRAM
; verwendet R3:R2, ZH:ZL
DezimalW:
set ; Setze T-Flagge fuer fuehrende Nullen
ldi ZH,HIGH(2*DezTab) ; Z ist Zeiger auf Dezimaltabelle
ldi ZL,LOW(2*DezTab)
DezimalW1:
lpm R2,Z+ ; Lese LSB aus Dezimaltabelle
tst R2 ; ist LSB Null?
breq DezimalWEiner ; ja, beendet
lpm R3,Z+ ; Lese MSB aus Dezimaltabelle
clr rmp ; rmp ist Zaehler
DezimalW2:
sub R0,R2 ; LSB subtrahieren
sbc R1,R3 ; MSB subtrahieren
brcs DezimalW3 ; zu viel, Uebertrag
inc rmp ; Ergebnis erhoehen
rjmp DezimalW2 ; weiter subtrahieren
DezimalW3:
add R0,R2 ; Subtrahieren rueckgaengig
adc R1,R3
brtc DezimalW6 ; keine fuehrenden Nullen mehr unterdruecken
tst rmp ; noch eine Null?
brne DezimalW5 ; nein
ldi rmp,' ' ; Leerzeichen ausgeben
rjmp DezimalW7 ; direkt ausgeben
DezimalW5:
clt ; T-Flagge ausschalten
DezimalW6:
subi rmp,-'0' ; ASCII-Null addieren
DezimalW7:
st X+,rmp ; in SRAM
rjmp DezimalW1 ; naechstniedrige Dezimalstelle
DezimalWEiner:
ldi rmp,'0' ; ASCII-Null
add rmp,R0 ; Einer addieren
st X+,rmp ; in SRAM speichern
ret
;
; Dezimaltabelle
DezTab:
.dw 10000 ; Zehntausender
.dw 1000 ; Tausender
.dw 100 ; Hunderter
.dw 10 ; Zehner
.dw 0 ; Tabellenende
;
; Lcd4Busy-Routinen hinzufuegen
.include "Lcd4Busy.inc"
;
; Ende Quellcode
;
Neu ist die Instruktion CPC Register,Register, die das zweite Register plus das
Übertragsbit temporär vom ersten Register abzieht und die Flaggen setzt.
11.5.3 Simulation
Die Simulation wird wieder mit
avr_sim
vorgenommen. Die Aufrufe der LCD-Routinen werden vor der
Simulation wieder auskommentiert.
Das EEPROM hat den 16-Bit-Zähler in 0x0000 und 0x0001
auf 0x00FF gesetzt (siehe Definition im .ESEG-Segment).
Die Dezimalwandlung im SRAM umfasst nun fünf Bytes weil
Zahlen bis 65.535 auftreten können.
Die beiden Zählerbytes wurden hier aus dem EEPROM
gelesen und in die Register R20:R21 kopiert, mit dem
höherwertigen Byte (MSB, most significant byte)
in R20.
Der Wert des Zählers wurde erhöht und in Dezimalformat
in das SRAM geschrieben.
Als Erstes wird das LSB des erhöhten Zählers in das
EEPROM geschrieben, an die Adresse 0x0000.
Nach erfolgter Beendigung des Schreibprozesses wird das MSB in
das EEPROM geschrieben.
Beide bytes sind jetzt im EEPROM.
Rund sieben Millisekunden sind vergangen bis beide Bytes in das
EEPROM geschrieben sind.
©2016-2022 by http://www.gsc-elektronic.net