Home ==> Mikrobeginner ==> 11. EEPROM mit LCD-Anzeige
ATtiny24

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

  1. Einführung in das EEPROM
  2. Einführung in die Dezimalumwandlung
  3. Hardware, Bauteile, Aufbau
  4. Ein Einschaltzähler
  5. Ein 16-Bit-Einschaltzähler

11.1 Einführung in das EEPROM

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äme 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

EECR Das Auslesen von Inhalten des EEPROMs umfasst folgende Einzelschritte:
  1. Durch Abfragen des EEPE-Bits im EEPROM-Kontrollregister EECR ist sicherzustellen, dass das EEPROM zum Lesen bereit ist.
  2. 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.
  3. 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:
  1. Zunächst ist wie beim Lesen sicherzustellen, dass Schreibvorgänge abgeschlossen sind.
  2. Durch Schreiben von Null in das EECR sicherstellen, dass EEPM1 und EEPM0 sowie EERE gelöscht sind (M1 und M0 = Null löschen die Zelle zuerst und beschrieben sie danach in einem Schritt).
  3. MSB und LSB der Adresse in die Adressregister EEARH und EEARL schreiben.
  4. Das zu programmierende Byte in das Datenregister EEDR schreiben.
  5. Das EEMPE-Bit im EEPROM-Kontrollregister auf Eins setzen. Dieses Bit löscht sich nach vier Taktzyklen selbst.
  6. 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.

Home Top EEPROM Dezimalwandlung Hardware Bytezähler Wortzähler


11.2 Einführung in die Dezimalumwandlung

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

Home Top EEPROM Dezimalwandlung Hardware Bytezähler Wortzähler


11.3 Aufgabe, Hardware, Bauteile und Aufbau

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

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.


Home Top EEPROM Dezimalwandlung Hardware Bytezähler Wortzähler


11.4 Bytezähler

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            *
; ************************************************
;
; -------- Verwendete Register -------------------
; rmp, rmo, rLine, rLese, R0
;
; -------- 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
;
; ------- 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.

Ergebnis Das hier ist das Ergebnis der Bemühungen. Sieht perfekt aus, oder?


Home Top EEPROM Dezimalwandlung Hardware Bytezähler Wortzähler


11.5 Wortzähler

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

Home Top EEPROM Dezimalwandlung Hardware Bytezähler Wortzähler


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