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.
Doch, denn wenn ich mit dem Tastendruck den Motor auslöse, und dann das EEPROM auslese, wurde die Drehrichtung korrekt dokumentiert.
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 | | ^ |
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
Das hier:
1 | uint8_t *eeprom_adr = 0; |
2 | //
|
3 | eeprom_adr++; |
4 | if (eeprom_adr>(uint8_t*)255) |
ist auch Unsinn.
Oliver S. schrieb: > Das trifft aber (i.d.R.) nur die zuletzt aktive Zelle, nicht das ganze > eeprom. Ja
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
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
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:
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.
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.
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.