Wird ein SPI-Flash-Speicherchip die gleichen Probleme mit nicht-atomaren Schreibvorgängen haben wie das interne EEPROM eines dsPIC?

Vor einiger Zeit hatte ich sporadisch Probleme mit dem internen EEPROM eines dsPIC. Hin und wieder wurde ein Wert im EEPROM beim Einschalten auf Null gesetzt. Ich habe das Problem bis zu dem Zeitpunkt verfolgt, an dem der Chip nach dem Löschschritt des Schreibzyklus, aber bevor der Schreibvorgang abgeschlossen war, die Stromversorgung verlor. Es drehte sich alles um das Timing des Herunterfahrens relativ zur Firmware-Ausführung, die (im Normalbetrieb) zufällig war. Ich löste dies, indem ich meinem EEPROM einen Pufferabschnitt hinzufügte, um sicherzustellen, dass ein unvollständiger Schreibzyklus bei Wiederherstellung der Stromversorgung abgeschlossen werden konnte. Ich musste EEPROM-Schreibvorgänge in eine atomare Operation umwandeln.

Jetzt verwende ich einen anderen dsPIC ohne internes EEPROM und versuche, einen externen Flash-Speicherchip zum Speichern persistenter Daten zu verwenden. Ich frage mich, ob ich ähnliche Bedenken haben sollte. Sollte ich mir Sorgen machen, dass mein externer Flash-Chip während des Schreibens abschaltet und Daten verliert, und einen Fix dafür in meine Firmware schreiben, wie ich es für das interne EEPROM getan habe? Oder garantiert der Chip selbst atomare Schreibvorgänge?

Für weitere Einzelheiten definiert meine Puffertechnik einen Bereich des dauerhaften Speichers, der aus drei Feldern besteht: Adresse zum Schreiben, zu schreibende Daten und ein READY-Flag. Ein "Schreiben" besteht aus vier Schritten: in den Puffer schreiben, READY-Flag setzen, aus dem Puffer schreiben, READY-Flag löschen. Beim Einschalten überprüfen Sie das READY-Flag. Wenn es gesetzt ist, führen Sie alles aus, was sich im Puffer befindet. Dies funktionierte gut im EEPROM, aber ich bin mir nicht sicher, ob es im Flash gut funktionieren wird.

Flash-Speicher ist eigentlich etwas schlechter als EEPROM, denn je nachdem, was Sie verwenden (nackter Flash oder Flash mit Controller - Ihrer hat einen internen Controller), müssen Sie größere Blöcke löschen und schreiben als auf EEPROM (das normalerweise nach Wörtern organisiert ist). Dies dauert normalerweise viel länger und gibt Ihnen die Möglichkeit, Schreibvorgänge zu beschädigen. Wie Sie im Datenblatt lesen können, kann das Löschen eines Sektors bis zu einer Sekunde (!) dauern, während frische Schreibvorgänge 15 ms dauern (auf Ihrem Chip). Ich würde also sagen, verwenden Sie, was zuvor empfohlen wurde: Führen Sie einen Brownout-Check durch, bevor Sie in Flash schreiben.
FRAM vermeidet dieses Problem auf Kosten der Kosten/Verfügbarkeit.
Leider sind Kosten und Verfügbarkeit meine Hauptanliegen!
@ user36129: Wenn auf den von mir verwendeten EEPROM-Geräten ein Byte oder eine größere Region gelöscht wird, die ein leeres Bit enthält, bleibt das Bit während des gesamten Löschvorgangs leer. Auf Flash-Geräten müssen Bits im Allgemeinen programmiert werden, bevor sie gelöscht werden. Dies ist nicht beobachtbar, wenn die Löschung vollständig ausgeführt wird, kann jedoch beobachtbar sein, wenn sie unterbrochen wird.

Antworten (4)

Ich habe noch nie von einem Flash-Speicherchip (oder Prozessor mit internem Flash) gehört, der intern über ausreichend Energiespeicher verfügt, um einen Schreib- (oder Lösch-) Zyklus abzuschließen, wenn die externe Stromversorgung entfernt werden sollte. Mit anderen Worten, wenn Sie keine Kontrolle darüber haben, wann Ihr System heruntergefahren wird, müssen Sie immer ein Protokoll erstellen, das jeden einzelnen Flash-Update-Vorgang erkennen und behandeln kann, der möglicherweise unterbrochen wurde.

Eine Möglichkeit, dies zu umgehen, besteht darin, den erforderlichen Energiespeicher (z. B. einen Elektrolytkondensator) auf Ihrem Board bereitzustellen, damit Sie einen externen Stromausfall erkennen und dennoch einen möglicherweise bereits gestarteten Schreib- / Löschvorgang abschließen können.

BEARBEITEN: Ihr Schreibpufferkonzept könnte mit dem externen Flash verwendet werden, es muss jedoch geändert werden, um die größere Löschgranularität zu berücksichtigen. Laut Datenblatt ist die minimale Löschgröße ein "Sektor" (4K Bytes).

Sie müssen drei Sektoren für Ihren Schreibpuffer reservieren. Eine davon hält Ihr READY-Flag (nennen Sie dies den WB_R-Vektor). Der zweite enthält die Sektoradresse des Sektors, der aktualisiert wird (nennen Sie dies den WB_A-Sektor). Der dritte enthält die aktualisierten Daten für diesen Sektor (nennen Sie dies den WB_D-Sektor).

Führen Sie die folgenden Schritte aus, um ein bestimmtes Byte (oder eine Gruppe von Bytes in einem einzelnen Sektor) zu aktualisieren. Wir gehen davon aus, dass WB_R bereits gelöscht ist.

  1. Löschen Sie WB_A.
  2. Suchen Sie den Flash-Sektor, der das Byte enthält, das Sie ändern möchten (nennen Sie dies den DEST-Sektor).
  3. Schreiben Sie die Sektoradresse von DEST in WB_A.
  4. Löschen Sie WB_D.
  5. Kopieren Sie den Inhalt von DEST nach WB_D, aber wenn Sie zu den Bytes gelangen, die Sie ändern, schreiben Sie die neuen Werte anstelle der alten Werte nach WB_D.
  6. Setzen Sie das READY-Flag in WB_R. Beachten Sie, dass dies bedeutet, dass Sie es in seinen nicht gelöschten Zustand ändern. Da der gelöschte Zustand 0xFF ist, bedeutet dies, dass Sie 0x00 schreiben.
  7. Lösche DEST (erhalte die Sektoradresse von WB_A).
  8. Kopieren Sie den Inhalt von WB_D nach DEST.
  9. Löschen Sie WB_R.

Überprüfen Sie beim Einschalten das READY-Flag, und wenn es gesetzt ist (etwas anderes als 0xFF – es wurde möglicherweise nur teilweise geschrieben oder teilweise gelöscht), springen Sie direkt zu Schritt 7.

Beachten Sie, dass bei diesem Algorithmus jeder der Schreibpuffersektoren mindestens einmal für jede von Ihnen durchgeführte Schreiboperation geschrieben und gelöscht wird. Dies kann zu einem Problem werden, wenn Sie während der Lebensdauer des Produkts viele (mehr als 100.000) Schreibvorgänge ausführen. In diesem Fall benötigen Sie einen ausgefeilteren Wear-Leveling-Algorithmus.

Meine gepufferte Schreibtechnik sollte auch funktionieren, oder?
Keine Ahnung, das müsstest du genauer beschreiben. Es hört sich so an, als würden Sie sich darauf verlassen, dass Ihr Board zufällig über genügend Energiespeicher verfügt, um bestimmte Arten von Schreibzyklen abzuschließen.
Ich habe der Frage eine Beschreibung meiner Puffertechnik hinzugefügt. Ich glaube nicht, dass die Energiespeicherung einen Einfluss auf das hat, was ich tue, aber ich könnte mich irren.
OK, da Einzelbyte-Schreibvorgänge idempotent sind (dh es spielt keine Rolle, ob sie mehr als einmal ausgeführt werden), sieht es so aus, als ob Ihr Schreibpuffer funktionieren könnte. Der einzige Fehlermodus, den ich sehe, ist, wenn die Stromversorgung während des Schreibens der Adresse oder der Daten in den Schreibpuffer oder des Setzens des READY-Flags ausfällt, wird dieser bestimmte Schreibvorgang niemals stattfinden - aber zumindest wird die Zieladresse nicht gelöscht. Diese Technik könnte auch mit externem Flash funktionieren, aber Sie müssen die größere Löschgranularität berücksichtigen, da sie darauf angewiesen ist, dass der Schreibpuffer, das READY-Flag und der Zielspeicherort einzeln gelöscht werden können.
Mir ist die Löschgranularität nicht 100% klar. Verstehe ich richtig, dass Flash von Natur aus große Blöcke löscht und alle Teile neu schreibt, die sich nicht geändert haben? Wenn ja, wird meine Puffer-Idee definitiv nicht funktionieren ...
Nein, bei den meisten externen Flash-Geräten (es gibt Ausnahmen) muss das Löschen und Schreiben ausdrücklich in zwei getrennten Schritten erfolgen, und Sie sind für die vorübergehende Speicherung von Daten verantwortlich, die sich nicht in einer Lese-Änderungs-Schreib-Operation ändern. Das Schreiben kann normalerweise byteweise erfolgen, aber das Löschen funktioniert jeweils auf einer größeren "Seite" oder einem größeren Block von Bytes. Siehe meine Bearbeitung oben.
Ich habe offensichtlich den falschen Speicher verwendet. EEPROM ist eindeutig die bessere Wahl für meine Anwendung. Ich hätte mit dieser Frage beginnen sollen: electronic.stackexchange.com/questions/65503/… Schätzen Sie die Hilfe!
@StephenCollings: Oder ein nvSRAM, das automatisch mit Ladung von einer dedizierten Obergrenze speichert, wenn die Primärversorgung abfällt.

Gepuffertes Schreiben ist nicht ausreichend. Sie müssen sich hier von den Dateisystem- und Datenbanktypen abheben: Sie benötigen eine Datenstruktur in Flash, die in einen "guten" Zustand zurückkehren kann, wenn ein Block beschädigt ist.

Ein typischer Weg, dies zu tun, ist das Ping-Pong zwischen zwei Blöcken. Machen Sie die letzten zwei oder vier Bytes der Blöcke zur "Seriennummer" des Blocks und den Rest Ihrer Daten im Rest des Blocks. Wenn Sie einen neuen Block schreiben, erhöhen Sie die Seriennummer des vorherigen Blocks um eins, überspringen Sie den Löschwert „0“ (der je nach Flash-Typ 0xff sein kann) und schreiben Sie den neuen Block mit dieser Seriennummer.

Lesen Sie beim Einschalten beide Blöcke und sehen Sie, welcher die spätere Seriennummer hat (unter Berücksichtigung des Umbruchs von 0xffff-> 0 und Ignorieren der übersprungenen Löschwerte). Verwenden Sie diesen Block. Möglicherweise möchten Sie auch einen CRC Ihrer Daten hinzufügen, um sicherzustellen, dass sie in der Mitte nicht beschädigt wurden (obwohl, wenn Sie die Seriennummer an das Ende setzen, das "kein Problem" sein sollte).

Wenn Sie über komplexe Daten verfügen, können Sie dies dahingehend erweitern, dass eine Datenbank oder ein Dateisystem einen Baum auf der Festplatte aktualisiert oder sogar eine Schreibprotokollierung implementiert.

Ich habe diesen Ansatz in der Vergangenheit verwendet, bin aber seitdem auf Flash-Geräte mit Blöcken gestoßen, die sich sehr böse verhielten, nachdem sie (vermutlich) teilweise gelöscht wurden. Seitdem habe ich begonnen, mindestens drei Blöcke zu verwenden, damit man zu jedem Zeitpunkt immer den zuletzt gelöschten Block identifizieren kann, indem man nur die Informationen in den anderen beiden Blöcken verwendet]. Zwei Blöcke sind nicht genug, weil ein Block, der gerade gelöscht wird, willkürlich das Bitmuster erlangen könnte, das notwendig ist, um zu sagen, dass der andere Block der letzte gelöschte ist. Die Verwendung von drei Blöcken vermeidet dieses Problem ...
... denn wenn sich zwei Blöcke gegenseitig "beschuldigen", dann muss einer von ihnen der letzte sein, der gelöscht wurde, und der verbleibende Block weiß, welcher es war.

Dies ist ein Bereich, in dem Sie sich hinsetzen und die Dinge sorgfältig strategisch planen müssen. Einige Details aus dem Datenblatt sind:

  1. 4k-Sektor löschen: 400 m s schlimmsten Fall
  2. 32k-Block löschen: 800 m s schlimmsten Fall
  3. 64k-Block löschen: 1000 m s schlimmsten Fall
  4. Seite schreiben: 3 m s schlimmsten Fall
  5. erstes Byte schreiben: 40 μ s schlimmsten Fall
  6. nächstes Byte schreiben: 12 μ s schlimmsten Fall

Wenn Sie die Kontrolle über dieses Detail haben, ist es wahrscheinlich eine gute Idee, eine lokale Stromversorgung (über die gespeicherte Ladung eines Kondensators) zu arrangieren, die sich innerhalb einer von Ihnen festgelegten "Droop-Marge" hält, während kritische Schreibvorgänge stattfinden. Dies muss keine volle Sekunde sein, wenn Sie das Schreibtiming für das erste Byte klug nutzen (vergessen Sie nicht, zusätzliche Kommunikations-/Einrichtungszeiten damit einzubeziehen). Sie könnten nur ein oder zwei Bytes in einer speziellen Seite aktualisieren, die bedeutet B. dass ein Block- oder Sektorlöschen gestartet wird. Auf diese Weise können Sie bei einem Brownout oder Reset feststellen, wo Sie zuletzt waren, um den Vorgang abzuschließen. Möglicherweise benötigen Sie auch mehr als eine "Sonderseite". Aber in jedem Fall müssen Sie alle Fälle gründlich prüfen!

Wenn die Stromversorgung unterbrochen wird, während ein Flash-Chip den Block löscht, sollte robuste Software davon ausgehen, dass sich der Inhalt des Blocks jederzeit willkürlich ändern kann, es sei denn, der Block wird erneut gelöscht und ein Löschzyklus wird vollständig ausgeführt. Selbst wenn der Block immer noch alte Daten zu enthalten scheint, gibt es keine Garantie dafür, dass er dies noch für längere Zeit tun wird. Selbst wenn der Block gelöscht zu sein scheint, gibt es keine Garantie dafür, dass programmierte Bits nicht spontan "erscheinen". Ich habe ein paar Prozessoren mit internem Flash gesehen, die eine Fähigkeit beinhalteten, zu überprüfen, ob Bits "wirklich" ausgeblendet oder "gründlich" programmiert waren, aber ich habe noch nie gesehen, dass eine solche Funktionalität von einem externen Flash-Gerät bereitgestellt wurde.

Wenn man in der Lage sein möchte, periodisch Daten im Flash zu speichern und sicherzustellen, dass im Falle eines Stromausfalls jedes Update entweder vollständig oder gar nicht gelingt, muss man mindestens drei Flash-Blöcke haben und ein Protokoll so definieren, dass immer ein Block gelöscht wird, kann man dies nur anhand des Inhalts der anderen beiden Blöcke feststellen. Es gibt eine Vielzahl von Protokollen, um dies zu implementieren; Ich schlage hier einen einfachen vor, unter der Annahme, dass die zu speichernde Informationsmenge ein voller Block minus einer programmierbaren Einheit mit minimaler Größe ist und drei Blöcke verfügbar sind, die ich X, Y und Z nennen werde.

Jeder Block enthält ein "Steuer"-Bit, das für die Verfolgung des Gültigkeits-/Löschstatus reserviert ist; Ich nenne diese Bits x, y und z. Während des Betriebs behält das System die Invariante bei, dass das Steuerbit des Blocks, der korrekte Daten enthält, leer ist; der "vorangehende" Block (X wird von Z vorangestellt) wird sein Steuerbit programmiert haben. Die Steuerbits für den verbleibenden Block (der auf denjenigen mit gültigen Daten "folgt") sind irrelevant. Wenn alle Steuerbits leer sind, wurde noch nie etwas richtig geschrieben; Wenn alle Steuerbits programmiert sind, ist etwas ernsthaft beschädigt worden.

Um neue Daten zu schreiben, löschen Sie den Block, der auf den Block folgt, der die korrekten Daten enthält, und speichern Sie dann neue Daten in diesem Block. Programmieren Sie schließlich als letzten Schritt das Steuerbit des ehemals aktuellen Blocks. Bis dieses Steuerbit programmiert ist, kümmert sich nichts um den Inhalt des gerade programmierten Blocks. Sobald dieses Bit programmiert ist, kümmert sich nichts um den Inhalt des Blocks, der dem neuen Block folgt. Vorausgesetzt, dass das System über genügend Energie verfügt, um sicherzustellen, dass die Programmierung dieses einen Bits entweder erfolgreich ist oder sauber fehlschlägt, ist ein zuverlässiger Betrieb in allen Stromausfallszenarien gewährleistet.

Angenommen, x ist programmiert, y ist leer und z ist irgendetwas. Da der gültige Datenblock sein eigenes Flag-Leerzeichen haben muss und das Flag des vorherigen Blocks programmiert werden muss, kann X kein gültiger Block sein (Flag x ist programmiert) und Z kann kein gültiger Block sein (weil Flag y programmiert ist). Folglich ist Y der einzige Block, der gültige Daten enthalten kann. Block X enthält die vorherige Version der Daten, und es kann nicht darauf vertraut werden, dass Z irgendetwas enthält. Wenn es notwendig ist, neue Daten zu speichern, sollte der Code damit beginnen, Z zu löschen (unabhängig davon, ob es bereits leer erscheint) und alle Daten zu programmieren, die darin enthalten sein sollten. Wenn die Stromversorgung zu irgendeinem Zeitpunkt während dieses Vorgangs unterbrochen wird, ist der Systemzustand derselbe wie vor Beginn (basierend auf den Flags wird angenommen, dass der Inhalt von Z bedeutungslos ist, sodass sein Inhalt den Systemzustand überhaupt nicht beeinflusst).

Erst nachdem alle Schreibvorgänge in Z abgeschlossen sind und es gültige Daten enthält, sollte Flag y programmiert werden. Sobald dieses Flag geschrieben ist, ist Z als der Block erkennbar, der gültige Daten enthält, da sein eigenes Flag leer ist, während das Flag (y) des vorangehenden Blocks programmiert ist; die Tatsache, dass y jetzt programmiert ist, bedeutet, dass Y nicht mehr gültig ist.

Wenn es das nächste Mal notwendig ist, neue Daten zu speichern, sollte Block X gelöscht und dort Daten gespeichert werden; Der Abschluss sollte durch das Programmierflag z angezeigt werden. Die Zeit danach sollte Y gelöscht werden und dort Daten gespeichert haben, wobei der Abschluss durch das Programmierflag x angezeigt wird. Es ist wichtig, dass Versuche, die Flags x, y und z zu programmieren, entweder vollständig ausgeführt werden oder keine Wirkung haben, aber dies sind die einzigen Operationen, die auf Hardwareebene "garantiert atomar" sein müssen. Alle anderen Schreibvorgänge in den Speicher werden in einen Block ausgeführt, dessen Inhalt nicht einmal angesehen wird (*), es sei denn, er wird vollständig ausgeführt.

(*) Das System wird im Allgemeinen nicht in der Lage sein, den Zugriff auf den ungültigen Block zu vermeiden, aber das Verhalten des Systems wird durch den gelesenen Wert nicht beeinflusst.

Übrigens, wenn man sich nicht sicher ist, dass Flag-Schreibvorgänge vollständig ausgeführt werden können, gibt es verschiedene Ansätze mit redundanten Flag-Bits, die möglicherweise etwas helfen, aber die Zuverlässigkeit ist nicht mehr gewährleistet. Nehmen wir zum Beispiel an, dass das System Strom verliert, während Bit y teilweise programmiert ist, so dass es manchmal als programmiert, aber manchmal als leer gelesen wird. Wenn beim ersten Einschalten y als leer angezeigt wird, würde die nächste Aktualisierung Z löschen. Wenn während dieses Löschens das System die Stromversorgung verliert und beim nächsten Einschalten y als programmiert angezeigt wird, würde das System annehmen, dass Z das ist gültigen Block. Wenn y beide Male als programmiert gelesen worden wäre, dann wäre Z gewesender gültige Block und der nächste gelöschte Block wären X gewesen. Wenn er beide Male als leer gelesen worden wäre, dann wäre Z beim zweiten Mal korrekt als ungültiger Block erkannt worden. Obwohl man versuchen könnte, sich gegen diese Gefahren zu schützen, indem man redundante Flag-Bits hinzufügt, helfen solche Ansätze nicht viel. Man kann Dinge so entwerfen, dass es "unwahrscheinlich" ist, dass sich teilweise programmierte Flags auf problematische Weise verhalten, aber das unterscheidet sich grundlegend von der Garantie, dass, wenn Flag-Schreibvorgänge atomar funktionieren, der Chip nichts für andere teilweise geschriebene Daten melden könnte würde Ärger machen.