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.
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.
Ü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.
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.
Dies ist ein Bereich, in dem Sie sich hinsetzen und die Dinge sorgfältig strategisch planen müssen. Einige Details aus dem Datenblatt sind:
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.
Benutzer36129
pjc50
Stefan Collings
Superkatze