Forum: Compiler & IDEs EEprom Schreiben und Lesen


von Benjamin Munske (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Leute,

ich habe mich heute das erste Mal an die EEprom-Programmierung
herangewagt, werde allerdings nicht so ganz schlau aus dem Tutorial:
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#EEPROM

Mein Ziel ist es in einem Abstand von einigen Sekunden einen 16-Bit
Wert im EEprom abzulegen.

Diese Werte sollen dann nacheinander (irgendwann später, wenn der
Befehl vom PC kommt) an den Computer übertragen werden. So sehen meine
Gedankenansätze dazu aus (s. Anhang)

Bitte nicht wundern: Das Programm ist extrem gekürzt - habe nur das
relevant stehen gelassen.

Mein Problem ist auch, dass es schon zu lange her ist, dass ich mit
Pointer programmiert habe - in C habe ich das noch gar nicht gemacht!

Könntet ihr mir bitte den Umgang mit Pointern anhand meines Beispiels
erklären und meine Fehler korrigieren, denn so lerne ich das am Besten.

von Rolf Magnus (Gast)


Lesenswert?

Wieso hantierst du manuell mit den Adressen rum? Das sollte der Linker
für dich übernehmen. So zeigt es ja auch das Tutorial.
Dein Code sieht völlig anders aus als der dort beschriebene. Da jetzt
ein Beispiel hinzuschreiben bringt eigentlich nicht viel, da das
Tutorial das meines Erachtens schon ganz gut tut.

von Benjamin M. (bennm)


Lesenswert?

Naja, wie soll ich das sonst machen?

Ich will ja So lange Messwerte im EEprom speichern, bis ich die Messung
abbreche! Die Anzahl ist also variabel. Da ist es doch am einfachsten
mit Adressen zu arbeiten, oder sehe ich das falsch? Wie würdest du das
denn machen? Im Tutorial werden immer nur einzelne Werte gespeichert
und nicht eine ganze Reihe davon.

von Ralf (Gast)


Lesenswert?

Ohne mir das ganze jetzt anzusehen, machs doch so:

1. Du setzt den Pointer auf den Anfang des EEPROMs
2. Du schreibst einen Messwert an die Stelle, auf die der Pointer
zeigt, ins EEPROM hinein
3. Du inkrementierst den Pointer
4. Schritt 2-4 solange wiederholen, bis der Abfrage-Befehl kommt
5. Du sendest den EEPROM-Inhalt ab dem ersten Messwert (= Anfang
EEPROM)
6. Mit Schritt 1 weitermachen

Du musst dir natürlich überlegen, was passieren soll, wenn das EEPROM
voll ist, denn dann hast du mehrere Möglichkeiten:

1. Du schreibst am Anfang des EEPROMs weiter. Das nennt man dann
Ring-Speicher. Allerdings verlierst du dann die ältesten Messwerte. Und
du musst dann beachten, dass die ältesten Messwerte nicht mehr am Anfang
des EEPROMs stehen.
2. Du ignorierst alle weiteren Messungen (= Speicher voll)
3. Du sendest bei vollem EEPROM den Inhalt automatisch zum PC. Der
Vorteil wäre, dass du keine Daten verlierst...

Du brauchst im Prinzip nur zwei Adressen, und das sind die Start- und
die Ende-Adresse des EEPROMs, damit du erkennen kannst, wann das EEPROM
voll ist.
In C übernimmt der Compiler das Aufaddieren der Adresse durch das
Pointer-Inkrementieren automatisch, also wenn du 8-Bit-Werte
speicherst, wird die Adresse immer um 1 Byte erhöht, bei 16-Bit-Werten
um 2 Byte. Das erkennt der Compiler deswegen, weil du bei der
Pointer-Deklaration angeben musst, auf welchen Datentyp der Pointer
zeigt.

HTH

Ralf

von Benjamin M. (bennm)


Lesenswert?

Du bist genau der richtige Ansprechpartner für mich - genau so wollte
ich es machen, nur dass ich noch nicht genau weiß, wie ich das in den
C-Code umsetze. Im Folgenden werde ich mal meinen Quellcode stück für
Stück darstellen und dahinter schreiben, was dieser Code machen sollte
- wäre nett, wenn du meinen Code dann verbessern könntest (ist nicht so
viel), damit ich das lerne und verstehe. WEnn der Speicher voll ist,
dann wird die Messung halt abgebrochen, aber das progge ich später.
Direkt an den PC übertragen geht nicht, da die Daten erst später
abgerufen werden sollen, d.h. zwischendrin besteht keine Verbindung zum
PC.
Los gehts:


(uint16_t *) (EE_ADR = 2); // Damit möchte ich den Pointer auf Adresse
2 setzen, da ich in 0-1 die anzahl der aufgenommenen Messwerte
speichern möchte

uint16_t * EE_ADR = (uint16_t *) 2; // Deklaration des EEprom-Pointers
- Startadresse = 2

// EEprom Speichervorgang
  void Save_EE(uint16_t eingang)
  {
    eeprom_write_word ((uint16_t *) EE_ADR, eingang);
    (uint16_t *) (EE_ADR + 2);
  }

// Diese Funktion  (Save_EE) soll einen Messwert (2Byte) in der
aktuellen Adresse (EE_ADR) ablegen und den Pointer um 2 erhöhen.


// EEprom Datenausgabe an den PC
  void Copy_EE(void)
  {
    uint16_t cnt;
    uint16_t cntmax;
    uint16_t Ausgabe;
    char *Temp;

    cntmax = EE_ADR + 1;

    for (cnt = 2; cnt < cntmax; cnt = cnt + 2)
    {
      Ausgabe = eeprom_read_word ((uint16_t *) cnt);
      Temp = (char *) Ausgabe;
      uart_puts(Temp);
    }
  }
// in cntmax soll die anzahl der abgelegten Messwerte gespeichert
werden. Anschließend soll über eine Schleife von 2 (startwert) bis zu
cntmax ein Datensatz nach dem anderen an den PC übertragen werden. Dazu
werden die EEprom-Adressen angesprochen (eeprom_read_word), in einen
String umgewandelt und über den UART verschickt.


Das waren eigentlich schon alle Stellen, die mir Probleme machen -
bitte nicht über einige Fehler lachen - denke schon, dass dort
teilweise erhebliche Mängel drin sind, aber ich kenn mich halt noch
nicht mit der EEprom-Programmierung aus.

Vielen Dank im Vorraus und ein fröhliches Weihnachtsfest euch allen!

von Fritz G. (fritzg)


Lesenswert?

Ausgabe = eeprom_read_word ((uint16_t *) cnt);
      Temp = (char *) Ausgabe;
      uart_puts(Temp);

Das geht so nicht.

Ich nehme an, dass im EEPROM deine Messwert als 16bit Integer stehen.
Diese kannst du nicht mit uart_puts() ausgeben, da das eine
String-Ausgabefunktion ist (es sei denn, du hast sie geändert), d.h.
das sie bei der ersten 0 abbricht, was bei jedem Messwert <256 der Fall
ist.

Du meinst sicherlich:
1
char str[80];
2
3
sprintf(str,"%d ",Ausgabe);
4
uart_puts(str);

von Walter (Gast)


Lesenswert?

*******die erste Zeile weglassen:
(uint16_t *) (EE_ADR = 2); // Damit möchte ich den Pointer auf Adresse
2 setzen, da ich in 0-1 die anzahl der aufgenommenen Messwerte
speichern möchte

******* diese ist richtig
uint16_t * EE_ADR = (uint16_t *) 2; // Deklaration des EEprom-Pointers
- Startadresse = 2

// EEprom Speichervorgang
  void Save_EE(uint16_t eingang)
  {
*****  das (uint16_t *) kannste dir sparen:
    eeprom_write_word (EE_ADR, eingang);
*** so nicht:    (uint16_t *) (EE_ADR + 2);
*****sondern so:
EE_ADR += 1;

den Rest habe ich mir nicht angeschaut, vielleicht erst mal die Fehler
raus machen

Grüße
Walter

von Stefan Seegel (Gast)


Lesenswert?

> d.h. das sie bei der ersten 0 abbricht, was bei jedem Messwert <256 >
der Fall ist.

Nein, weil Werte little-endian abgespeichert werden, dass heißt also
dann bei 0, 256, ...
Aber egal, geht so oder so nicht :-)

Ich weiß nicht genau um was für ein Gerät es sich handelt, aber wenn es
im Dauerbetrieb ist (z.B. Heizungsregler oder so was) ist es keine gute
Idee den EEPROM als Datenpuffer zu verwenden, weil der EEPROM für solch
einen Zweck doch relativ schnell kaputt geht. Z.B. ATMega 16, 512 Bytes
EEPROM = Platz für 256 Integer Werte, jede Sekunde ein Wert = ein
kompleter Zyklus in 4,2 Minuten, EEPROM 100.000x beschreibbar ->  0.8
Jahre, würde mich nerven von meinem Heizungsregler rund alle 9 Monate
den Controller austauschen zu müssen ;-)

Stefan

von Benjamin M. (bennm)


Lesenswert?

Vielen Dank schon einmal für eure Tipps - werde ich wohl morgen oder
übermorgen ausprobieren, da ich gerade noch Weihnachten feiere! :-)
Es handelt sich bei der ganzen Geschichte um einen Datenlogger für
einen ferngesteuerten Wagen, d.h. ich erreiche die 100000
Schreibzugriffe des EEproms eh nicht, da der Logger längst nicht im
Dauerbetrieb arbeitet, sondern sporadisch genutzt wird.

Wie kann den Sourcecode eigentlich so darstellen, wie Fritz Ganter das
hier im Forum gemacht hat? Gibt es da einen BB-Code?

von Benjamin M. (bennm)


Lesenswert?

So, ich habe die Verbesserungsvorschläge mal eingearbeitet, doch ich
bekomme immernoch Fehler angezeigt:

main.c: In function `Copy_EE':
main.c:109: warning: assignment makes integer from pointer without a
cast
109>> cntmax = EE_ADR + 1;

main.c: In function `__vector_11':
main.c:430: warning: unused variable `EE_ADR'
430>> uint16_t * EE_ADR = (uint16_t *) 2; // Deklaration des
EEprom-Pointers - Startadresse = 2

@Stefan Seegel: Was meinst du mit "Aber egal, geht so oder so nicht
:-)"

Warum wird hier nur 1 addiert (EE_ADR += 1;) wir arbeiten doch mit
Words, da muss ich doch normalerweise 2 addieren!? Für EE_ADR += 1
könnte ich doch auch EE_ADR++ schreiben, oder?

von Timo Birnschein (Gast)


Lesenswert?

Ja, das kannst du auf jeden fall!
Die beiden Ausdrücke sind gleich.

Gruß
Timo

von Timo Birnschein (Gast)


Lesenswert?

"Nein, weil Werte little-endian abgespeichert werden, dass heißt also
dann bei 0, 256, ..."

Wo steht, dass die Werterepräsentation Little Endian ist? Der AVR ist
doch sicherlich Big Endian?! Little Endian kommt aus der Steinzeit,
wenn ich das richtig sehe, und wird heute überwiegend auf so
Sparc-Kisten im medizinischen Bereich verwendet...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Wo steht, dass die Werterepräsentation Little Endian ist? Der AVR
> ist doch sicherlich Big Endian?! Little Endian kommt aus der
> Steinzeit, wenn ich das richtig sehe, und wird heute überwiegend auf
> so Sparc-Kisten im medizinischen Bereich verwendet.

ROTFL

Erstens hast du ganz offensichtlich little endian und big endian
verwechselt.  Sie heißen nicht umsonst Intel und Motorola byte order
(in dieser Reihenfolge), d.h. aller Intel-Krempel ist little endian.
Nun, man kann natürlich mit Fug und Recht sagen, dass der tatsächlich
aus der Steinzeit käme... letztlich schleppt er den Ballast des alten
8086 immer noch mit sich rum, und dieser wiederum schleppte eigentlich
nur den Ballast des 8080 mit sich rum. ;-)

Anyway, bei byte order gibt's keine Steinzeit, sondern zwei
gleichberechtigte Modelle, von denen man für jedes einige Punkte
finden kann.  Auch wenn du eine steinzeitlich-abgeleitete little
endian CPU in deinem PC hast, big endian ist genauso verbreitet.
Erstens ist es die network byte order, d.h. alle nennenswerten
Netzwerkprotokolle benutzen little endian für ihre Zahlendarstellung
(IP-Adressen, Portnummern, Sequenznummern, ...).  Zweitens können wohl
nahezu alle ,,großen'' RISC-Prozessoren beide Varianten (über ein
Steuerregister der CPU definiert), werden aber typisch big endian
betrieben.  Das sind keinesfalls nur die SPARC-CPUs (nun UltraSPARC,
die waren schon 64-bittig lange bevor davon in der Intel-Welt
überhaupt jemand nur geträumt hätte), sondern auch MIPS und POWER.
Letztere gibt's auch in PCs, nur eben nicht von Intel: dem Macintosh.
Ansonsten tauchen viele davon aber auch in Form aller möglicher
Microcontroller auf (ein bis zwei Nummern größer als ein AVR halt) und
sind dadurch auch in deiner Umgebung stärker vertreten als du vermuten
wirst.

Zurück zum AVR: erstens hat er als 8-bit-CPU gar keinen richtigen byte
sex.  Schließlich könnte man denken, dass er ja nur einzelne Bytes
verarbeitet und es daher an der Software liegt, wie sie Zahlen aus
mehreren Bytes verarbeitet.  Letzteres geschieht wohl in der Tat als
little endian, zumindest in den gängigen Compilern.  Zu aller
Verwirrung besitzt aber auch die AVR-CPU zwei Dinge, die ein bisschen
endianess durchgucken lassen, und noch dazu zwei verschiedene: der ROM
ist 16-bittig organisiert, und die Zugriffe auf ihn sind m. W. big
endian.  In neueren CPU-Cores sind nun auch Datenbefehle für 16-bit-
Zahlen hinzugekommen, diese hingegen sind little endian (und stimmen
damit mit den existierenden Compilern und Bibliotheken überein).

Ganz kurz zurück zum Subject: wie man die Werte im EEPROM abspeichert,
bleibt einem natürlich gut und ganz selbst überlassen, da es für
diesen in keiner Hinsicht Wortbefehle gibt.  Sowohl little endian als
auch big endian sind also möglich.  Wenn die eeprom_*_word()-Routinen
der avr-libc nimmt, diese arbeiten little endian.

von Timo Birnschein (Gast)


Lesenswert?

Hallo (mal ganz kleinlaut),

ich habe grade nochmal ein Vorlesungsskript "Modelle und Standarts zur
Bildverarbeitung und Kommunikation" gewälzt und stellte fest, dass dort
nicht direkt gezeigt wird, welche VR auf welchem System heute noch
läuft, bzw. früher lief. Also schaute ich nochmal in wikipedia und muss
sagen, dass ich in der Tat ganz offensichtlich Little Endian und Big
Endian vertauscht habe.

Allerdings wundere ich mich darüber. Nicht, dass ich mich nicht irre,
sondern dass Intel little Endian ist... Ich hatte das anders in
Erinnerung.

Sorry für das verbreiten falscher Tatsachen!

von Benjamin M. (bennm)


Lesenswert?

Das klingt ja sehr interessant.
Um mal zurück zu meinem Problem zu kommen - ich weiß immer noch nicht,
wie ich die Fehler behebe!

von Fritz G. (fritzg)


Lesenswert?

Welche Fehler sind den noch übrig?

von Benjamin M. (bennm)


Lesenswert?

main.c: In function `Copy_EE':
main.c:109: warning: assignment makes integer from pointer without a
cast
109>> cntmax = EE_ADR + 1;

main.c: In function `__vector_11':
main.c:430: warning: unused variable `EE_ADR'
430>> uint16_t * EE_ADR = (uint16_t *) 2; // Deklaration des
EEprom-Pointers - Startadresse = 2

von Timo Birnschein (Gast)


Lesenswert?

du verwendest in deinem Code EE_ADR, bevor es initialisiert wurde. Das
hat Walter ja oben schon behoben.
Dann kann ich nicht erkennen, wieso du den Pointer auf folgende Art
deklarierst:
 uint16_t * EE_ADR = (uint16_t *) 2;

Wenn der erstellte Pointer = 2 werden soll, ist es nicht nötig die 2
erst auf ein 16-Bit Value zu casten. Ein Pointer ist auch nur ein
integer-Wert.

Die erste Warnung kannst du meiner Meinung nach ignorieren, denn es ist
ja gewollt, dass dein cntmax = dem aktuellen Pointer + 1 ist. Du willst
ja den Wert des Pointers haben, also die Adresse, die er in sich
trägt.

Um die zweite Warnung richtig zu verstehen, brauche ich mehr code. Was
sie sagt ist, dass du EE_ADR nicht verwendest. Er wird also die ganze
Zeile wegoptimieren, weil sie für ihn keinen Sinn ergibt...

von Walter (Gast)


Lesenswert?

"Warum wird hier nur 1 addiert (EE_ADR += 1;) wir arbeiten doch mit
Words, da muss ich doch normalerweise 2 addieren!?"

EE_ADR ist ein Pointer auf einen 16Bit Wert, also auf 2 Bytes.
Wenn du in C den Pointer um 1 erhöhst zeigt er auf den nächsten 16Bit
Wert.
Bei einem byteorientierten System wie dem AVR zeigt der Pointer also
jetzt auf die Adresse die 2 Bytes weiter ist.

Grüße
Walter

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.