Pfad: Home => AVR-Überblick => Programmiertechniken => Adressierung => EEPROM    (This page in English: Flag EN) Logo

Adressieren von Speicherzellen in AVRs

  1. Adressieren von SRAM,
  2. Adressieren von Portregistern,
  3. Adressieren von EEPROM,
    1. EEPROM-Speicher
    2. Die .ESEG-Direktive
    3. EEPROM-Portregister
    4. EEPROM-Adressen
    5. Lesen aus EEPROM
    6. Schreiben in EEPROM
    and
  4. Adressieren von Flashspeicher.

3.1 Adressieren von EEPROM

Alle AVRs haben mindetens 64 Byte und bis zu 4.096 Bytes EEPROM an Bord. EEPROM ist ein Speichertyp, der seinen Inhalt dauerhaft behält, selbst wenn die Betriebsspannung verloren geht. Wenn dann die Betriebsspannung wieder da ist, kann sein Inhalt erneut ausgelesen werden.

Unbeschriebene EEPROM-Speicherbereiche sind alle auf 0xFF eingestellt. Die Inhalte von EEPROM-Speicherzellen können mit zwei Methoden beschrieben werden:
  1. Beim Assemblieren von Quellcode werden alle Inhalte, die in das .ESEG-Segment geschrieben werden, in eine Hex-Datei mit der Endung .eep geschrieben. Diese Datei kann mit der Brenner-Software in das EEPROM geschrieben werden.
  2. In dem aktiven Programm des Controllers können EEPROM-Zellen gelöscht (auf 0xFF eingestellt) und danach erneut beschrieben werden. Die dazu notwendige Prozedur benötigt etwas Zeit, das Ende des Schreibvorganges kann über einen Interrupt signalisiert werden, wenn dies gewünscht und eingestellt ist.
Das Auslesen von EEPROM-Speicherzellen funktioniert etwas anders. Lesen ist sehr schnell und braucht nur wenige Taktzyklen.

3.2 EEPROM-Initierung mit der .ESEG-Direktive

EEPROM-Inhalt content aus einem ESEG-Segment Um das EEPROM als Speicher zu nutzen, sollte es zu Beginn mit einem Inhalt befüllt werden. Das macht die .ESEG-Direktive möglich. Das Folgende schreibt Inhalte in dieses Segment:

.CSEG ; Code-Segment
Sprungadresse:
  ; Instruktionen zum Ausfuehren
;
.ESEG
.ORG 0x0000 ; Die Startadresse des EEPROM-Inhaltes, Default=0
  .db 1,2,3,4,'A','a' ; Bytes und ASCII-Zeichen in das ESEG
  .dw 1234, 4567 ; 16-Bit-Worte in das ESEG
  .db "Text to be written to EEPROM",0x00 ; Textzeichenkette null-terminiert
  .dw Sprungadresse ; Sprungadresse als Wort in das ESEG
; Ende ESEG
;
.CSEG ; Code-Segment
; ... Weiterer ausführbarer Code

Nach dem Assemblieren und Programmieren des EEPROMs mit der .eep-Datei sieht der Inhalt des EEPROMs dann so aus wie im Bild zu sehen.

Normalerweise wird beim Brennen des Flashspeichers mit ausführbarem Code gleichzeitig auch das EEPROM gelöscht. Soll dies nicht erfolgen und der bisherige Inhalt des EEPROM erhalten bleiben, dann gibt es dafür eine Fuse. Wenn die gesetzt ist, bleibt alles im EEPROM wie es ist. Da einige Speicherbits im EEPROM aber von vorher bereits Nullen enthalten können und weil Nullen im EEPROM nur beim vorherigen generellen Löschen zu Einsen gemacht werden können, würde das erneute Brennen des EEPROMs beim Verifieren Fehler anzeigen, da die neu programmierten Nullen sich mit den schon vorhandenen Nullen mischen würden und Nullen, die nunmehr Einsen werden sollen, schlicht Nullen bleiben würden.

3.3 EEPROM-Portregister

Zugriff auf das EEPROM mittels Portregistern Das Schreiben in oder das Lesen aus dem EEPROM erfolgt über Portregister. Davon gibt es entweder drei oder vier:
  1. Das oder die Adressregister: Die niedigen 8 Bits der EEPROM-Adresse werden in das Portregister EEARL geschrieben. Falls der AVR-Typ mehr als 256 Bytes EEPROM besitzt, wird das MSB der Adresse noch nach EEARH geschrieben.
  2. Ein Datenregister: Beim Schreiben in das EEPROM werden die 8 Bits in das Daten-Portregister EEDR geschrieben, bevor der Schreibprozess gestartet wird. Beim Lesen aus dem EEPROM wird das gelesene Byte in diesem Datenregister abgelegt und kann von dort mit IN ausgelesen werden.
  3. Ein Kontrollregister: Das Schreiben nach und das Lesen aus dem EEPROM wird von einigen Bits kontrolliert, die im Portregister EECR gesetzt oder gelöscht werden können. Beim Schreiben ist das EEPE-Bit massgebend, beim Lesen das EERE-Bit.
Bitte beachten, dass die hier angegebenen Portadressen und Bitbenennungen auch anders lauten können. Daher bitte immer die Symbole aus der def.inc als Portadresse und Bitbenennung verwenden.

3.4 Einstellung der EEPROM-Adresse

Wenn im Programm EEPROM beschrieben oder gelesen werden soll, muss stets zuerst die Adresse gesetzt werden. Der dazu notwendige Assembler-Quellcode:

.equ EepromAdresse = 0x0010
;
; Zuerst Warten bis irgendwelche Schreibvorgaenge zu Ende sind
WarteEep:
  sbic EECR,EEPE ; Pruefe EEPE-Bit
  rjmp WarteEep ; Warte weiterhin
;  
; Setzen der Adresse
  ldi R16,Low(EepromAdresse)
  out EEARL,R16
  .ifdef EEARH ; Wenn mehr als 256 Bytes EEPROM vorhanden sind
    ldi R16,High(EepromAdresse)
    out EEARH,R16
    .endif
  ; Hier die Lese- oder Schreibprozedur
  ReadWriteEep:
    ; Die Lese- oder Schreibprozedur hier

Bitte beachten, dass vor jedem Schreiben der EEPROM-Adresse zuerst geprüft werden sollte, ob kein Schreibvorgang mehr aktiv ist.

Die .IFDEF-Direktive fügt auch das MSB noch hinzu, aber nur dann, wenn mehr als 256 Bytes EEPROM vorhanden sind.

Die eingestellte Adresse wird beim Lesen und Schreiben nicht geändert. Wenn mehrere Lese- und Schreibvorgänge in einer Reihe ausgeführt werden sollen, muss die Adresse nicht gänzlich neu geschrieben werden. Soll ein gespeichertes Byte nur um Eins erhöht werden, kann auf den Lesevorgang direkt der Schreibvorgang folgen, ohne dass die Adresse neu geschrieben werden muss. Unter Umständen kann auch nur das LSB neu beschrieben werden, wenn sich das MSB nicht ändert.

In dieser Prozedur wird ein EEPROM-Bereich gelesen oder beschrieben und immer die Adresse neu angepasst.

.equ EepStartAdr = 0x0010
.equ EepEndAdr = 0x001F
  ; Setze die Adresse in das Registerpaar R1:R0
  ldi R16,High(EepStartAdr)
  mov R1,R16
  ldi R16,Low(EepStartAdr)
  mov R0,R16
EepSchleife:
  sbic EECR,EEPE
  rjmp EepLoop
  ; Ausgabe des LSB der Adresse
  out EEARL,R0
  .ifdef EEARH
    ; Ausgabe des MSB der Adresse
    out EEARH,R1
    .endif
EepReadWrite:
  ; Fuege Read- oder Write-Prozedur hier ein
  inc R0 ; Erhoehe LSB
  brne EepChkEnd
  inc R1
EepChkEnd:
  ldi R16,Low(EepEndAdr+1)
  cp R0,R16
  brne EepSchleife
  ldi R16,High(EepEndAdr+1)
  brne EepSchleife

3.5 Lesen aus dem EEPROM

Das Auslesen aus dem EEPROM wird gestartet, indem das EERE-Bit auf Eins gesetzt wird. Das hält die CPU für vier Takte lang an und liefert dann im Portregister EEDR das Ergebnis. Das Auslesen eines Bytes sieht dann so aus:

.equ EepAdrs = 0x0010
EepWarte:
  sbic EECR,EEPE ; Warte bis die Schreiboperation beendet ist
  rjmp EepWarte
  ldi R16,Low(EepAddrs)
  out EEARL,R16
  .ifdef EEARH
    ldi R16,High(EepAddrs)
    out EEARH,R16
    .endif
  sbi EECR,1<<EERE
  ; Vier Taktzyklen Pause
  in R16,EEDR ; Lese Byte nach R16  

Einlesen des EEPROMs in das SRAM In Fällen, in denen mehr als ein einziges Byte zu lesen ist, brauchen wir etwas mehr Speicher. Bei zwei Bytes z. B. ein zweites Register, bei mehr als nur drei oder vier einen Platz im SRAM. In diesem Fall verwenden wir dann auch Zeiger, um z. B. das gelesene Byte mit ST X+,R16 fortlaufend abzulegen.

Dies hier zeigt, wie wir den kompletten EEPROM-Inhalt in einen SRAM-Bereich ablegen können. Bitte beachten, dass der gesamte Prozess nahezu eine Millisekunde dauert, wegen der Verzögerung beim Lesezugriff, wegen des Checks des Programmierbits, den Doppel-Byte-Check auf die Endadresse und die Zeigeroperationen.

3.6 Schreibzugriffe in das EEPROM

Um zu vermeiden, dass es versehentliche Schreibzugriffe auf das EEPROM gibt, ist der Schreibablauf etwas umständlicher gestaltet:
  1. Zuerst ist zu prüfen, ob vorherige Schreibzyklen beendet sind. Das erkennt man daran, dass das EEPE-Bit im EEPROM-Kontrollregister EECR gelöscht ist. Andernfalls muss man halt weiter warten.
  2. Dann wird die Schreibadresse in die Portregister EEARL/EEARH geschrieben.
  3. Danach wird das zu schreibende Datenbyte in das Portregister EEDR geschrieben.
  4. Dann wird das Master Programming Enable bit EEMPE in EECR gesetzt, zusammen mit den beiden Programming Mode Bits EEPM0 (Nur löschen, Erase only) und EEPM1 (Nur schreiben, Write only), wenn das so gewünscht ist. Das Interrupt Enable Bit EEPIE kann ebenfalls gesetzt werden, wenn Interrupts erwünscht sind.
  5. Innerhalb der folgenden vier Taktzyklen (Interrupts vorher abschalten) wird das Programming Enable Bit EEPE in EECR gesetzt.
Das startet das Programmieren an der eingestellten Adresse nach zwei Taktzyklen. Die Interrupts sollten dann wieder eingeschaltet werden.

Das Programmieren dauert 3,4 ms. Wenn diese Zeit abgelaufen ist, setzt der Controller das EEPE-Bit auf Null und, falls eingeschaltet, löst den EEP-READY-Interrupt aus und verzweigt zum entsprechenden Vektor.

Text, der in das EEPROM kopiert werden soll Ein Beispiel: dieser null-terminierte Text soll aus dem SRAM in das EEPROM kopiert werden. Wir können das entweder interrupt-gesteuert erledigen oder in einer Schleife das EEPE-Bit abfragen und dann, wenn es Null ist, das nächste Zeichen in das EEPROM schreiben. Hier mal die Variante mit Interrupts.

Hier zunächst das Initiieren. Es beginnt mit dem Initiieren des Stapelzeigers, der wegen der Interrupts benötigt wird. Zuerst wird dann der Text aus dem Flash in das SRAM kopiert. Dann werden zwei Zeiger eingerichtet: Z zeigt auf die Adresse im EEPROM, X auf den Text im SRAM. Das erste Zeichen wird in das Daten-Portregister geschrieben und das Adress-Portregister wird auf die erste zu beschreibende Adresse gesetzt. Dann wird das erste Zeichen in das EEPROM geschrieben, indem zuerst das Master-Program-Enable-Bit und danach das Program-Enable-Bit Eins gesetzt werden. Der weitere Ablauf erfolgt interrupt-gesteuert.

.dseg
sText:
;
.cseg
;
; ************************************
;  H A U P T P R O G R A M M  I N I T
; ************************************
;
Main:
.ifdef SPH ; Wenn RAMEND groesser als 255 
  ldi R16,High(RAMEND) ; Stapelzeiger MSB
  out SPH,R16 ; setzen
  .endif
	ldi R16,Low(RAMEND) ; Stapelzeiger LSB
	out SPL,R16 ; setzen
; Zeiger Z auf Flash-Tabelle mit Text
  ldi ZH,High(2*Text) ; MSB
  ldi ZL,Low(2*Text) ; LSB
; Zeiger X auf SRAM-Bereich
  ldi XH,High(sText) ; MSB
  ldi XL,Low(sText) ; LSB
Kopie:
  lpm R16,Z+ ; Lese aus Flash
  st X+,R16 ; Schreibe in das SRAM
  tst R16 ; Ende des Textes?
  brne Kopie ; Nein, weiter
;
WarteEep:
  sbic EECR,EEPE ; EEPE-Bit in EECR gleich Null?
  rjmp WarteEep ; Nein, weiter warten
  ldi XH,High(sText) ; X auf SRAM-Bereich, MSB
  ldi XL,Low(sText) ; dto., LSB
  clr ZH ; EEPROM-Adresse, MSB
  clr ZL ; dto., LSB
.ifdef EEARH  
  out EEARH,ZH ; EEPROM-Adresse in Portregister, MSB
  .endif
  out EEARL,ZL ; dto., LSB
  ld R16,X ; Lese erstes Zeichen
  out EEDR,R16 ; und schreibe in Daten-Portregister
  ldi R16,(1<<EERIE)|(1<<EEMPE) ; Interrupt Enable und Master Programming Enable
  out EECR,R16 ; in Kontroll-Portregister
  sbi EECR,EEPE ; Setze Progamming Enable Bit
; Interrupts
	sei ; Enable interrupts
;
; **********************************
;    P R O G R A M   L O O P
; **********************************
;
Loop:
	rjmp loop

Die Loeschphase ist absolviert Das Schreiben ist mit dem ersten Byte, dem 'D' an die Adresse 0 gestartet, die beiden Schreib-Bits sind aktiv und der Interrupt ist scharf geschaltet. Die erste Phase des Schreibens, das Löschen der EEPROM-Zelle, ist absolviert, nun hat das Schreiben begonnen.

Erster Interrupt Wenn das zu Ende ist, wird der erste Interrupt ausgelöst. Die erste Speicherzelle hat ihr 'D' und die Interrupt-Service-Routine EERDY wird angesprungen. Die sieht so aus:

.cseg
.org 000000
;
; **************************************
; R E S E T  &  I N T - V E K T O R E N
; **************************************
	rjmp Main ; Reset vector
	reti ; EXT_INT0
	reti ; PCI0
	reti ; PCI1
	reti ; WATCHDOG
	reti ; ICP1
	reti ; OC1A
	reti ; OC1B
	reti ; OVF1
	reti ; OC0A
	reti ; OC0B
	reti ; OVF0
	reti ; ACI
	reti ; ADCC
	rjmp EerdyIsr ; ERDY
	reti ; USI_STR
	reti ; USI_OVF
;
; **********************************
;  I N T - S E R V I C E   R O U T .
; **********************************
;
EerdyIsr:
  ld R16,X+ ; Lese das letzte geschiebene Zeichen noch einmal
  tst R16 ; War es ASCII-Null?
  brne EerdyIsr1 ; Nein, weiter schreiben
  cbi EECR,EERIE ; Loesche Interrupt-Enable-Bit
  reti ; Fertig
EerdyIsr1:
  ld R16,X ; Lese das naechste Zeichen aus dem SRAM
  out EEDR,R16 ; Schreibe es in das Datenregister
  inc ZL ; Erhoehe LSB der EEPROM Adresse
  out EEARL,ZL ; und schreibe diese in das Adress-Portregister
  brne EerdyIsr2 ; Kein Ueberlauf
  inc ZH ; Erhoehe MSB der EEPROM-Adresse
  out EEARH,ZH ; und schreibe in das Adress-Portregister
EerdyIsr2:
  sbi EECR,EEMPE ; Setze Master Programming Enable-Bit
  sbi EECR,EEPE ; Setze Programming Enable-Bit
  reti ; Fertig

Diese Routine prüft zu Beginn, ob das letzte geschriebene Zeichen eine ASCII-Null war. Wenn das der Fall ist, wird einfach das Interrupt-Enable-Bit gelöscht. Wer da jetzt noch etwas Weiteres tun will, kann es ab hier einfügen. Wenn nicht, wird die Adresse im EEPROM erhöht, das nächste Zeichen gelesen und der Schreibvorgang neu gestartet.

Zweiter Interrupt beim Schreiben des Textes in das EEPROM So nudeln sich nun die Interrupts Stück für Stück durch den Text. Hier der zweite Interrupt.

Alle Zeichen des Textes im EEPROM Bis dann auch das letzte Zeichen, die ASCII-Null fertig geschrieben ist und das Programm keine Interrupts mehr produziert. Das Ganze dauerte nun etwa 110 ms.

Die Loeschphase ist abgeschlossen Hier noch der Abschluss der Löschphase bei einem Zeichen, avr_sim modelliert das Löschen auch zeitlich korrekt.

Ein Hinweis zur Interruptprogrammierung: wenn das Interrupt-Enable-Bit EEIE gesetzt ist und keine höherwertigen Interrupts anstehen, stößt ein gelöschtes EEPE-Bit immer wieder den EEP-READY-Interrupt an. Dadurch kann der komplette Programmablauf mit diesem Interrupt blockiert werden, wenn in der Service-Routine nicht entweder das nächste Schreiben ins EEPROM erfolgt oder, wenn alle Schreibvorgänge erledigt sind, das Interrupt-Enable-Bit gelöscht wird.

Zu guter Letzt noch eine Warnung: die Anzahl Schreiboperationen in EEPROM-Zellen ist nur für einige Tausend mal garantiert. Da ein Tag im Leben eines Prozessors ganze 80.000 Sekunden hat, ein Jahr mehr als 31 Millionen Sekunden, kannst Du das EEPROM leicht totschreiben, wenn Dein Programm häufiger als alle 3.100 Sekunden oder 53 Minuten in das EEPROM schreibt. Daher unbedingt unnötiges Schreiben unterbinden und die Schreibzugriffe auf ein Mindestmaß beschränken. Nur wenn es echt sein muss, und z. B. wenn der Anwender eine Taste gedrückt hat, mal wieder einen Schreibvorgang anstoßen. Es ist daher eine gute Idee, eine Kopie des EEPROMs im SRAM anzulegen und nur bei wesentlichen Änderungen, und nur die tatsächlich geänderten Zellen, neu zu beschreiben.

Ein Beispiel hierfür: wenn Du den aktuellen Status eines Schrittmotors im EEPROM festhalten willst, um beim Neustart des Controllers genau bei diesem Zählerstand zu beginnen, dann wäre es keine gute Idee, das Schreiben bei jedem Einzelschritt anzustoßen. Wir könnten uns darauf beschänken, das Schreiben nur dann auszuführen, wenn der Schrittmotor seine Endstellung erreicht hat und wenn er mindestens zwei oder drei Einzelschritte vollführt hat. Und wenn der Schrittzähler aus vier Bytes besteht: schreibe nur diejenigen Bytes neu, die sich auch tatsächlich geändert haben. Und hoffe halt, dass die LSB-Zelle lange genug durchhält.

Wer ganz unbedingt ganz viele Schreibvorgänge machen muss, muss sich einen Adresswechsel-Wechsel überlegen und programmieren. Der kann z. B. daraus bestehen, dass ein und diesselbe Zelle immer auf korrekte Inhalte geprüft wird. Passieren dabei Fehler wird die Zelle adressmäßig verlegt. Die aktuelle Stelle, zu der die defekte Zelle verlegt wurde, kommt in eine eigene Zelle. Auf diese Weise kann man solange Ersatzzellen nehmen, wie man sie reserviert hat und die Lebensdauer des EEPROMs so verlängern. Schlussfolgerung:

Lesezugriffe auf das EEPROM sind sehr schnell, aber langsamer als SRAM-Zugriffe oder Zugriffe auf Register. Daher vorzugsweise das EEPROM in Register oder das SRAM kopieren und dort damit operieren.

Schreibzugriffe auf das EEPROM benötigen längere Zeiten und sind über die Lebensdauer des Prozessors hin limitiert. Es bedarf daher sorgfältiger Überlegung, wann genau was zu schreiben ist. Abhängig von der restlichen Organisation Deines Programmes solltest Du interrupt-getriebenes Schreiben bevorzugen, um zeitliche Konflikte mit anderen Vorgängen zu vermeiden.

Zum Seitenanfang

©2021 by http://avr-asm-tutorial.net