Forum: Mikrocontroller und Digitale Elektronik Atmega 8; EEPROM bei Reset korrumpiert; alle 0 werden zu 255


von Gabriel (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich schreibe ein Programm in C, bei dem ein Schrittmotor auf Knopfdruck 
nach dem Zufallsprinzip zu einer Seite dreht. Im EEPROM soll die Seite 
gespeichert werden, zu der der Motor dreht. Dabei starte ich mit einem 
EEPROM, in welchem die ersten 256 Bytes auf Null gesetzt sind. Das 
Programm liest nacheinander die Bytes im EEPROM aus, und schreibt den 
Wert in das erste Byte, welches gleich 0 ist. Wenn der Speicher voll 
ist, können wieder alle Bytes auf Null gesetzt werden.

Das Problem ist jetzt, dass bei einem Reset/Stromausfall alle Bytes, die 
vorher Null waren, den Wer 255 zugewiesen bekommen. Und das ohne, dass 
der Reset während eines Schreibzugriffes passiert!

In meinem Programm kommt nur zwei mal der Befehl eeprom_write_byte vor, 
und ich verstehe nicht, wie der das EEPROM in dieser Weise korrumpieren 
sollte.
Weiß jemand einen Ausweg?

von Cyblord -. (cyblord)


Lesenswert?

Gabriel schrieb:
> In meinem Programm kommt nur zwei mal der Befehl eeprom_write_byte vor,
> und ich verstehe nicht, wie der das EEPROM in dieser Weise korrumpieren
> sollte.
> Weiß jemand einen Ausweg?

Ich sage das EEPROM ist und bleibt leer (und hat damit überall 0xff 
(255)) und du schreibst gar nie erfolgreich rein.

von Gabriel (Gast)


Lesenswert?

Doch, denn wenn ich mit dem Tastendruck den Motor auslöse, und dann das 
EEPROM auslese, wurde die Drehrichtung korrekt dokumentiert.

von Einer K. (Gast)


Lesenswert?

Ich finde deine Löschroutine komisch...
Eigentlich ist ein leeres EEPROM mit 0xff geflutet
Du machst dir da schon einen Löschzyklus pro Zelle mit kaputt.

Mal ganz abgesehen von diesem Fehler:
1
E:\Programme\arduino\portable\sketchbook\sketch_jul14b\main.c: In function 'main':
2
E:\Programme\arduino\portable\sketchbook\sketch_jul14b\main.c:226:21: warning: 'i' may be used uninitialized in this function [-Wmaybe-uninitialized]
3
  226 |       for (uint8_t* i; i<(uint8_t*)256; i++)
4
      |                     ^

von Gabriel (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Du machst dir da schon einen Löschzyklus pro Zelle mit kaputt.

Was genau meinst du damit? Da kann ich dir nicht ganz folgen... dass ich 
statt Nullen eine 255 benutzen soll, um zu erkennen, wo der nächste Wert 
geschrieben wird,  weil das ohnehin der default value ist? Dann müsste 
ich meine Routine zur Erkennung der aktuellen EEPROM Adresse anpassen

Arduino Fanboy D. schrieb:
> Mal ganz abgesehen von diesem Fehler:
> E:\Programme\arduino\portable\sketchbook\sketch_jul14b\main.c: In
> function 'main':
> E:\Programme\arduino\portable\sketchbook\sketch_jul14b\main.c:226:21:
> warning: 'i' may be used uninitialized in this function
> [-Wmaybe-uninitialized]
>   226 |       for (uint8_t* i; i<(uint8_t*)256; i++)
>       |                     ^

Was genau ist denn da das Problem? Die Funktion _eeprom_write fragt ja 
nach einem Pointer, und ich kann doch auch einen Pointer einfach 
inkrementieren...oder?

von Stefan S. (chiefeinherjar)


Lesenswert?

Gabriel schrieb:
> Was genau ist denn da das Problem?

i ist nicht initialisiert und kann zu Beginn jeden beliebigen Wert 
haben.
Es kann gut sein, dass i den Wert 138 hat. Das heißt, die Werte von 0 
bis 137 werden übersprungen. Genau so kannst du Glück haben, dass i eben 
zu Beginn der Schleife den Wert 0 hat.

Deswegen initialisiert man eine Variable mit einem Wert, damit man genau 
weiß, wie groß die Zahl zum Beginn der Funktion/Schleife/Abfrage/etc 
ist.

Und i wird auch nie den Wert 256 haben können - es ist nur eine 8 Bit 
große Zahl.

von Stefan F. (Gast)


Lesenswert?

Der Inhalt des EEproms kann verloren gehen, wenn während des Zugriffes 
die Stromversorgung instabil ist. Ich hatte das mal sporadisch. Ursache 
war, dass die Ausgangsspannung des Netzteils direkt nach dem Start noch 
einmal absackte.

Benutze den Brown-Out Detektor und baue in dein Programm am Anfang eine 
1s Warteschleife ein... ach ich sehe, du hast schon 2s, gut.

Auf mögliche Fehler im Quelltext habe ich nicht geachtet, das kann 
zusätzlich noch sein.

von Christopher B. (chrimbo) Benutzerseite


Lesenswert?

Stefan S. schrieb:
> Und i wird auch nie den Wert 256 haben können - es ist nur eine 8 Bit
> große Zahl.
Doch, i ist bei ihm ein Pointer auf einen uint8_t, der kann schon größer 
als 255 werden ;-)

von Gabriel (Gast)


Lesenswert?

Ach, besten Dank, Stefan! Ich werde das ausprobieren, wenn ich wieder 
daheim bin. Das klingt sehr plausibel.
Wenn ich eine Variable vom Typ uint8_t* erstelle, gleicht diese aber 
grundsätzlich einer "normalen" uint8_t, richtig? Wie greift man denn 
dann auf Werte im Speicher mit der Adresse > 255 zu?
Und weitere Verständnisfrage: wenn ich eine Variable vom Typ uint8_t in 
dem Argument einer Funktion typecaste, um etwa einen Zeiger zu bekommen, 
hat die ursprüngliche Variable dann noch ihren alten Typ und erfährt nur 
als Argument der Funktion eine Typumwandlung, oder wird die Variable 
dann grundsätzlich gecastet?

von Stefan S. (chiefeinherjar)


Lesenswert?

Christopher B. schrieb:
> Stefan S. schrieb:
>
>> Und i wird auch nie den Wert 256 haben können - es ist nur eine 8 Bit
>> große Zahl.
>
> Doch, i ist bei ihm ein Pointer auf einen uint8_t, der kann schon größer
> als 255 werden ;-)

Gnarf - da hast du natürlich recht. Danke für die Korrektur!
Ich habe nur "uint8_t" gelesen.

von Christopher B. (chrimbo) Benutzerseite


Lesenswert?

Stefan S. schrieb:
> Gnarf - da hast du natürlich recht. Danke für die Korrektur!
> Ich habe nur "uint8_t" gelesen.
kein Problem. Der TO hat es glaube ich selber nicht verstanden was er da 
tut.

Gabriel schrieb:
> Wenn ich eine Variable vom Typ uint8_t* erstelle, gleicht diese aber
> grundsätzlich einer "normalen" uint8_t, richtig? Wie greift man denn
> dann auf Werte im Speicher mit der Adresse > 255 zu?
Nein. Ein uint8_t* zeigt auf eine normale uint8_t Variable. Der 
Pointer selbst hat (wahrscheinlich) eine andere Bitbreite.
Lasst dir doch mal über Serial oder wie auch immer sizeof(uint8_t) und 
sizeof(uint8_t*) ausgeben.

von Oliver S. (oliverso)


Lesenswert?

Stefan ⛄ F. schrieb:
> Der Inhalt des EEproms kann verloren gehen, wenn während des Zugriffes
> die Stromversorgung instabil ist.

Das trifft aber (i.d.R.) nur die zuletzt aktive Zelle, nicht das ganze 
eeprom.

Oliver

von MWS (Gast)


Lesenswert?

Das hier:
1
uint8_t *eeprom_adr = 0;
2
//
3
eeprom_adr++;
4
if (eeprom_adr>(uint8_t*)255)
ist auch Unsinn.

von Stefan F. (Gast)


Lesenswert?

Oliver S. schrieb:
> Das trifft aber (i.d.R.) nur die zuletzt aktive Zelle, nicht das ganze
> eeprom.

Ja

von (prx) A. K. (prx)


Lesenswert?

Gabriel schrieb:
> Was genau ist denn da das Problem?

Der Name. In Programmen ist eine Variable "i" in alter Tradition ein 
ganzzahliger Zähler, kein Pointer-Typ, während "p" in C ein Pointer 
wäre, keine Ganzzahl. Programmiert man entgegen der Erwartung, kann 
jemand schon mal drüber stolpern.

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

Gabriel schrieb:
> Was genau meinst du damit? Da kann ich dir nicht ganz folgen... dass ich
> statt Nullen eine 255 benutzen soll, um zu erkennen, wo der nächste Wert
> geschrieben wird,  weil das ohnehin der default value ist? Dann müsste
> ich meine Routine zur Erkennung der aktuellen EEPROM Adresse anpassen

Das Konzept ist sowieso fragwürdig. Arbeitet man mit einer variablen 
Anzahl Datensätzen in einem EEPROM ohne Dateisystem o.ä. dann braucht 
man wenigstens einen abgespeckten Ersatz. Also z.B. können die ersten X 
Bytes die Adresse des letzten gültigen Blocks beinhalten. Möglichkeiten 
gibts da viele. Aber jedes mal durchlaufen bis man vermeintlich eine 
leere Stelle erkannt hat ist aufwändig und Fehlerbehaftet. Kann die 0 
als "leer" Marker wirklich nie in gültigen Daten vorkommen?
Dann ist 0 eben in einem EEPROM schlecht, weil leer ist eine Zelle wenn 
sie 255 enthält.
Und erstmal beim Startup den EEPROM beackern ist total beknackt.

Das Konzept benötigt eine Überarbeitung.

Zum Problem: Der gesamte EEPROM kann nicht mal schnell gelöscht werden 
nur weil da eine Spannungsschwankung auftritt. Nicht alles und nicht 
jedes mal reproduzierbar.

: Bearbeitet durch User
von Gabriel (Gast)


Lesenswert?

Cyblord -. schrieb:
> Das Konzept ist sowieso fragwürdig. Arbeitet man mit einer variablen
> Anzahl Datensätzen in einem EEPROM ohne Dateisystem o.ä. dann braucht
> man wenigstens einen abgespeckten Ersatz. Also z.B. können die ersten X
> Bytes die Adresse des letzten gültigen Blocks beinhalten. Möglichkeiten
> gibts da viele. Aber jedes mal durchlaufen bis man vermeintlich eine
> leere Stelle erkannt hat ist aufwändig und Fehlerbehaftet. Kann die 0
> als "leer" Marker wirklich nie in gültigen Daten vorkommen?
> Dann ist 0 eben in einem EEPROM schlecht, weil leer ist eine Zelle wenn
> sie 255 enthält.
> Und erstmal beim Startup den EEPROM beackern ist total beknackt.

Naja, ich habe auch erst die Adresse des aktuellen Wertes in den Eeprom 
schreiben wollen, aber dann wären an dieser Stelle viel häufiger 
Schreibzugriffe erfolgr als an den anderen, ergo schnellerer Verschleiß. 
Mit der jetzigen Lösung lese ich das EEPROM lediglich, um die aktuelle 
Adresse zu finden.
Es kann nicht vorkommen, dass in einem beschriebenen Byte eine 0 steht, 
denn einer Seite ist die Zahl 1, der anderen 2 zugewiesen.
Es wird mitnichten bei jedem Reset das EEPROM vollständig gelöscht; erst 
wenn nach Wochen oder Monaten das EEPROM seriell ausgelesen wurde, wird 
es per seriellen Befehl wieder gelöscht.

von Einer K. (Gast)


Lesenswert?

Gabriel schrieb:
> Was genau ist denn da das Problem? Die Funktion _eeprom_write fragt ja
> nach einem Pointer, und ich kann doch auch einen Pointer einfach
> inkrementieren...oder?

Das Problem ist, dass du den Pointer nicht initialisierst.
Somit ist nicht gewährleistet, dass deine heiß geliebten Nullen 
überhaupt da rein geschrieben werden.
Was dann am Ende so aussieht, als wäre der Speicher nach dem Reset 
gelöscht worden.

Klarer: Du hast da so eine Art Zufallsgenerator gebaut.


> uint8_t *eeprom_adr = 0;
Socher Art Benennungen/Definitionen finde ich auch kritisch...
Entweder ist etwas eine Adresse, oder ein Pointer.
Ja, ich weiß, da gibts eine Ähnlichkeit.....

Aber, die Unterschiede machen sich dann hier
>itoa(i+1,buffer_char,10);
bemerkbar!
Denn es erwartet einen int und keinen Pointer. Was es auch lautstark 
bemängelt.


>itoa((int)(i+1),buffer_char,10);
Nagut, der Gcc steckt das auch ohne Cast weg, meckert etwas, aber tut 
das beabsichtigte.

Zudem halte ich die ganze Rechnerei mit den EEPROM Adressen für mehr 
oder weniger überflüssig. Ins Besondere die magischen auf 0 gesetzten 
Pointer

Bei mir würde das eher so aussehen:
uint8_t  datenFeld[feldgroesze]  EEMEM;
Dann muss man sich eigentlich kaum noch um die Adressen kümmern, weil 
man dem Compiler/Linker alles überlässt. Fehlerhafte Pointer 
Berechnungen können dir ganz fix das Wohnzimmer tapezieren. Und das 
wollen wir doch nicht.

In C könnte das dann so aussehen:
1
uint8_t* start = datenFeld;
2
uint8_t* ende  = start+feldgroesze;
3
for(uint8_t* ptr = start; ptr<ende; ptr++) eeprom_write_byte(ptr,0);

In C++ könnte das dann schlanker aussehen, denn es stellt die Iteratoren 
bereit:
1
 for(uint8_t &d:datenFeld) eeprom_write_byte(&d,0);

----

Gabriel schrieb:
> erst
> wenn nach Wochen oder Monaten das EEPROM seriell ausgelesen wurde, wird
> es per seriellen Befehl wieder gelöscht.

Und das habe ich dir hoffentlich verkaufen können, dass es das eben 
nicht unter allen Umständen tut. Du da einen Bock geschossen hast.

von Gabriel (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Das Problem ist, dass du den Pointer nicht initialisierst.
> Somit ist nicht gewährleistet, dass deine heiß geliebten Nullen
> überhaupt da rein geschrieben werden.
> Was dann am Ende so aussieht, als wäre der Speicher nach dem Reset
> gelöscht worden.
> Klarer: Du hast da so eine Art Zufallsgenerator gebaut.

Das ist mir bereits nach den vorigen Antworten klar geworden, vielleicht 
habe ich das noch nicht so deutlich erwähnt.

Arduino Fanboy D. schrieb:
>> uint8_t *eeprom_adr = 0;
>
> Socher Art Benennungen/Definitionen finde ich auch kritisch...
> Entweder ist etwas eine Adresse, oder ein Pointer.
> Ja, ich weiß, da gibts eine Ähnlichkeit.....

Nachvollziehbar

Arduino Fanboy D. schrieb:
> Zudem halte ich die ganze Rechnerei mit den EEPROM Adressen für mehr
> oder weniger überflüssig. Ins Besondere die magischen auf 0 gesetzten
> Pointer
> Bei mir würde das eher so aussehen:
> uint8_t  datenFeld[feldgroesze]  EEMEM;
> Dann muss man sich eigentlich kaum noch um die Adressen kümmern, weil
> man dem Compiler/Linker alles überlässt. Fehlerhafte Pointer
> Berechnungen können dir ganz fix das Wohnzimmer tapezieren. Und das
> wollen wir doch nicht.
> In C könnte das dann so aussehen:
> uint8_t* start = datenFeld;
> uint8_t* ende  = start+feldgroesze;
> for(uint8_t* ptr = start; ptr<ende; ptr++) eeprom_write_byte(ptr,0);

Ok, die "0" kann ich natürlich auch durch 255 ersetzen, und deine Lösung 
ist tatsächlich sehr viel schlanker.
Was ist denn allgemein das Problem dabei, wenn ich nicht an eine Stelle 
im EEPROM die aktuelle Adresse schreibe, sondern stattdessen durch das 
EEPROM iteriere? Wie gesagt war mein Gedanke der, dass ich ein einzelnes 
Byte nicht so oft beschreibe.

von Einer K. (Gast)


Lesenswert?

Gabriel schrieb:
> Ok, die "0" kann ich natürlich auch durch 255 ersetzen,

Ach, nee....
Du hast jetzt schon die magischen Zahlen 0, 255 und 256 mehrfach im Code 
verstreut.
Wobei zwischen der 0, der 255 und der 256 eine unabänderliche, und doch 
unsichtbare, Beziehung herrscht.

Solche Zahlen haben nur einmal irgendwo aufzutauchen, damit man nur an 
einer Stelle ändern muss, wenn es doch mal 512 Arrayzellen geben soll.
Dem Compiler ist das egal.
Aber für den unbedarften Leser, die Wartungsmannschaft, unabdingbar.
Das Prinzip heißt DRY (Don’t repeat yourself), einen Verstoß dagegen 
bezeichne ich gerne mal als "Schlampigkeit".

Gabriel schrieb:
> Was ist denn allgemein das Problem dabei, wenn ich nicht an eine Stelle
> im EEPROM die aktuelle Adresse schreibe, sondern stattdessen durch das
> EEPROM iteriere? Wie gesagt war mein Gedanke der, dass ich ein einzelnes
> Byte nicht so oft beschreibe.

Da kann ich dir wenig zu sagen...
Außer: Es gibt eine Atmel AppNote zum Thema "EEPROM wear leveling"
Die könnte dir einen Ansatz liefern.

von Peter D. (peda)


Lesenswert?

Die Nachfolgetypen (ATmega88) können den EEPROM getrennt löschen und 
schreiben. Das halbiert den Verschleiß. Löschen heißt auf 0xFF setzen.
Man kann auch ohne Löschen beliebig weitere Bits von 1 auf 0 setzen.

von Einer K. (Gast)


Lesenswert?

Peter D. schrieb:
> Man kann auch ohne Löschen beliebig weitere Bits von 1 auf 0 setzen.
Setzen die eeprom_update_xxx Funktionen das um?
Die eeprom_write_xxx  werden das doch sicherlich nicht tun.

von Cyblord -. (cyblord)


Lesenswert?

Peter D. schrieb:
> Man kann auch ohne Löschen beliebig weitere Bits von 1 auf 0 setzen.

So hat VW ursprünglich die ersten digitalen Kilometerzähler 
implementiert. Für jeden gefahrenen Kilometer wird ein Bit von 1 auf 0 
gekippt. So kann man Kilometergenau und jederzeit persistent jahrelang 
die Fahrleistung aufzeichnen ohne den EEPROM zu schrotten.

Arduino Fanboy D. schrieb:
> Setzen die eeprom_update_xxx Funktionen das um?
> Die eeprom_write_xxx  werden das doch sicherlich nicht tun.

Das passiert beim Schreiben eines Bytes automatisch wenn nicht vorher 
gelöscht wurde. Es werden dann nur die Bits verändert die von 1 auf 0 
kippen. Der Rest kann nicht verändert werden.

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

Cyblord -. schrieb:
> Das passiert beim Schreiben eines Bytes automatisch wenn nicht vorher
> gelöscht wurde. Es werden dann nur die Bits verändert die von 1 auf 0
> kippen. Der Rest kann nicht verändert werden.

Ja, nee...
Das ist mir schon klar.....

Die Frage war eigentlich ob die eeprom_update_xxx Lib Funktionen nur 
prüfen ob  geänderte Bytes vorliegen, oder auch das Muster prüfen, um 
eben auf das löschen verzichten zu können.
(aber das Detail kann ich auch selbst untersuchen, wenn das mal wichtig 
wird)

von Cyblord -. (cyblord)


Lesenswert?

Arduino Fanboy D. schrieb:
> Cyblord -. schrieb:
>> Das passiert beim Schreiben eines Bytes automatisch wenn nicht vorher
>> gelöscht wurde. Es werden dann nur die Bits verändert die von 1 auf 0
>> kippen. Der Rest kann nicht verändert werden.
>
> Ja, nee...
> Das ist mir schon klar.....

Ging aus deiner Frage nicht hervor. Du hast den Eindruck erweckt, eine 
Schreibfunktion müsse explizit die Bitkipperei implementiert haben. Was 
nicht stimmt.
> Die eeprom_write_xxx  werden das doch sicherlich nicht tun.

> Die Frage war eigentlich ob die eeprom_update_xxx Lib Funktionen nur
> prüfen ob  geänderte Bytes vorliegen, oder auch das Muster prüfen, um
> eben auf das löschen verzichten zu können.

die update prüft auf Gleichheit und schreibt NUR wenn sich was geändert 
hat.
Jedes andere Byte, welches nicht schon drin steht, wird also geschrieben 
-> geht.

Die write schreibt aber immer und damit kannst du hier diese Methode 
auch anwenden. -> geht.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Arduino Fanboy D. schrieb:
> Die Frage war eigentlich ob die eeprom_update_xxx Lib Funktionen nur
> prüfen ob  geänderte Bytes vorliegen, oder auch das Muster prüfen, um
> eben auf das löschen verzichten zu können.

Getrenntes Löschen und Schreiben wird von der Lib nicht unterstüzt. Das 
muß man selber implementieren.

von Einer K. (Gast)


Lesenswert?

Cyblord -. schrieb:
> die update prüft auf Gleichheit und schreibt NUR wenn sich was geändert
> hat.
Dazu muss sie das Byte aber schon löschen, damit nachher zuverlässig das 
richtige drinsteht.

> Jedes andere Byte, welches nicht schon drin steht, wird also geschrieben
> -> geht.
Ohne Mustervergleich nur mit löschen


> Die write schreibt aber immer und damit kannst du hier diese Methode
> auch anwenden. -> geht.
Aber nicht ohne vorher zu lesen und den Mustervergeleich selber 
durchzuführen.

Ich interpretiere Deine Antwort so, dass die update Funktionen eben 
keinen Mustervergleich machen, also bei Änderungen grundsätzlich 
löschen.
Was dann etwas halbgar wäre.

Peter D. schrieb:
> Getrenntes Löschen und Schreiben wird von der Lib nicht unterstüzt. Das
> muß man selber implementieren.
Ich danke dir, das hatte ich so befürchtet.

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.