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?
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.
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:
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?
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.
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.
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 ;-)
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?
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.
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.
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
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.
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.
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.
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:
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.
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.
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.
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.
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.
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.
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)
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.
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.
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.