Wear Leveling im EEPROM eines Mikrocontrollers

15

Zum Beispiel: Das Datenblatt für ATtiny2313 (wie die meisten Atmel AVR-Datenblätter) lautet:

128 Bytes Systemintern programmierbare EEPROM-Lebensdauer: 100.000 Schreib- / Löschzyklen

Stellen Sie sich vor, ein Programm benötigt nur zwei Bytes, um eine Konfiguration zu speichern. Die anderen 126 Bytes werden effektiv verschwendet. Was mich betrifft, ist, dass regelmäßige Aktualisierungen der beiden Konfigurationsbytes das EEPROM des Geräts verschleißen und es unbrauchbar machen können. Das gesamte Gerät würde unzuverlässig, da Sie zu einem bestimmten Zeitpunkt einfach nicht verfolgen können, welche Bytes im EEPROM unzuverlässig sind.

Gibt es eine clevere Möglichkeit, den Verschleiß im EEPROM eines Mikrocontrollers auszugleichen, wenn Sie nur ein oder zwei der 128 verfügbaren Bytes effektiv nutzen?

jippie
quelle
1
Wenn 100.000 Schreibzyklen eine Einschränkung wären, wäre es sinnvoll, stattdessen eine andere Technologie zu verwenden? Entweder ein Mechanismus, der eine interne Nivellierung beinhaltet, oder etwas mit einer Größenordnung oder mehr Ausdauer?
Anindo Ghosh
1
@AnindoGhosh Ich möchte meinen kleinen Vorrat an Mikrocontrollern nicht verschwenden, nur weil das EEPROM durch das Testen eines Proof of Concept abgenutzt ist. Ich möchte mich nicht darum kümmern, welches Byte ich in einem früheren Projekt verwendet habe, wenn ich den Controller wieder verwende. Und es fühlt sich einfach gut an zu wissen, dass ich die verfügbare Hardware optimal nutze.
Jippie
3
Dies könnte helfen: AVR101: Hochleistungs-EEPROM-Speicher
m.Alin
1
Vielleicht werfen Sie einen Blick auf meine Antwort auf stackoverflow .
JimmyB
Schauen Sie sich die MSP430 FRAM-Serie von TI an ... 10 ^ 13 schreibt !!!
geometrikal

Antworten:

19

Die Technik, die ich normalerweise verwende, besteht darin, den Daten eine 4-Byte-fortlaufende Folgenummer voranzustellen, wobei die größte Zahl den neuesten / aktuellen Wert darstellt. Im Fall des Speicherns von 2 Bytes tatsächlicher Daten, die insgesamt 6 Bytes ergeben würden, und dann bilde ich eine kreisförmige Warteschlangenanordnung, so dass sie für 128 Bytes EEPROM 21 Einträge enthält und die Lebensdauer 21-mal erhöht.

Beim Booten kann dann die größte Sequenznummer verwendet werden, um sowohl die nächste zu verwendende Sequenznummer als auch das aktuelle Ende der Warteschlange zu bestimmen. Der folgende C-Pseudocode zeigt, dass bei der Erstprogrammierung der EEPROM-Bereich auf die Werte 0xFF gelöscht wurde, sodass ich die Folgenummer 0xFFFF ignoriere:

struct
{
  uint32_t sequence_no;
  uint16_t my_data;
} QUEUE_ENTRY;

#define EEPROM_SIZE 128
#define QUEUE_ENTRIES (EEPROM_SIZE / sizeof(QUEUE_ENTRY))

uint32_t last_sequence_no;
uint8_t queue_tail;
uint16_t current_value;

// Called at startup
void load_queue()
{
  int i;

  last_sequence_no = 0;
  queue_tail = 0;
  current_value = 0;
  for (i=0; i < QUEUE_ENTRIES; i++)
  {
    // Following assumes you've written a function where the parameters
    // are address, pointer to data, bytes to read
    read_EEPROM(i * sizeof(QUEUE_ENTRY), &QUEUE_ENTRY, sizeof(QUEUE_ENTRY));
    if ((QUEUE_ENTRY.sequence_no > last_sequence_no) && (QUEUE_ENTRY.sequence_no != 0xFFFF))
    {
      queue_tail = i;
      last_sequence_no = QUEUE_ENTRY.sequence_no;
      current_value = QUEUE_ENTRY.my_data;
    }
  }
}

void write_value(uint16_t v)
{
  queue_tail++;
  if (queue_tail >= QUEUE_ENTRIES)
    queue_tail = 0;
  last_sequence_no++;
  QUEUE_ENTRY.sequence_no = last_sequence_no;
  QUEUE_ENTRY.my_data = v;
  // Following assumes you've written a function where the parameters
  // are address, pointer to data, bytes to write
  write_EEPROM(queue_tail * sizeof(QUEUE_ENTRY), &QUEUE_ENTRY, sizeof(QUEUE_ENTRY));
  current_value = v;
}

Für ein kleineres EEPROM wäre eine 3-Byte-Sequenz effizienter, obwohl ein bisschen Bit-Slicing erforderlich wäre, anstatt Standarddatentypen zu verwenden.

PeterJ
quelle
+1, Netter Ansatz. Kann der Speicher ein wenig optimiert werden, indem weniger "Tag" -Bytes verwendet werden, und möglicherweise abhängig von einer Art Hash-Bucket-Mechanismus, um eine zusätzliche Verteilung bereitzustellen? Ein Hybrid zwischen No Leveling und Ihrem Ansatz?
Anindo Ghosh
@AnindoGhosh, ja ich glaube es könnte. Ich habe diesen Ansatz normalerweise für kleine Mikros verwendet, um den Code zu vereinfachen, und ich persönlich habe ihn hauptsächlich für größere Geräte wie DataFLASH verwendet. Eine andere einfache Idee, die in den Sinn kommt, ist, dass die Folgenummern periodisch gesenkt werden könnten, um sie auf kleineren Werten zu halten.
PeterJ
Der von @ m.Alin erwähnte Atmel-Anwendungshinweis hat eine clevere Vereinfachung: Nach einem RESET ist es dann möglich, durch den [...] Puffer zu blättern und das letzte [...] Pufferelement zu finden, das geändert wurde, indem die Stelle gesucht wird, an der sich das befindet Differenz zwischen einem Pufferelement und dem nächsten Pufferelement ist größer als 1 .
Jippie
Sollte write_value () den Eintrag nicht auf queue_tail * sizeof (QUEUE_ENTRY) setzen? Ich werde das erste Mal korrekt sein, aber sollte es nicht weiter voranschreiten, wenn es mehrere Schreibvorgänge gibt? i wird außerhalb von load_queue () nicht inkrementiert.
Marshall Eubanks
2
@ DWORD32: Ja, das ist technisch korrekt, aber in der Praxis irrelevant. Bis dahin ist die Verschleißgrenze am EEPROM um den Faktor 2000 überschritten!
Dave Tweed
5

Das Folgende ist eine Methode, die Buckets und ungefähr ein Overhead-Byte pro Bucket verwendet. Die Bucket-Bytes und Overhead-Bytes sind ungefähr gleich stark beansprucht. In dem vorliegenden Beispiel werden bei 128 EEPROM-Bytes 42 2-Byte-Buckets und 44 Status-Bytes zugewiesen, wodurch die Verschleißfestigkeit um das 42-fache erhöht wird.

Methode:

Teilen Sie den EEPROM-Adressraum in k Buckets auf, wobei k = ⌊ E / ( n +1) ⌋ ist, wobei n = Größe des Setup-Daten-Arrays = Bucket-Größe und E = EEPROM-Größe (oder allgemeiner die Anzahl der EEPROMs) Zellen, die dieser Datenstruktur gewidmet werden sollen).

Initialisieren Sie ein Verzeichnis, ein Array von m Bytes, die alle auf k gesetzt sind , mit m = En · k . Wenn Ihr Gerät startet, liest es das Verzeichnis durch, bis es den aktuellen Eintrag findet, der ein Byte ungleich k ist . [Wenn alle Verzeichniseinträge gleich k sind , initialisieren Sie den ersten Verzeichniseintrag mit 0 und fahren Sie von dort fort.]

Wenn der aktuelle Verzeichniseintrag enthält j , Eimer j enthält aktuelle Daten. Wenn Sie einen neuen Setup-Dateneintrag schreiben müssen, speichern Sie j +1 im aktuellen Verzeichniseintrag. Wenn dies k entspricht , initialisieren Sie den nächsten Verzeichniseintrag auf 0 und fahren Sie von dort aus fort.

Beachten Sie, dass Verzeichnis-Bytes ungefähr so ​​stark ausgelastet sind wie Bucket-Bytes, da 2 · k > mk .

(Ich habe das oben Gesagte aus meiner Antwort auf die Frage 34189 von Arduino SE übernommen , wie man die Lebensdauer von EEPROM verlängert .)

James Waldby - jwpat7
quelle
2

Ich habe dafür eine fortlaufende Sequenznummer verwendet (ähnlich wie bei Peters Antwort). Die Sequenznummer kann tatsächlich nur 1 Bit betragen, vorausgesetzt, die Anzahl der Elemente im Cue ist ungerade. Der Kopf und der Schwanz werden dann durch die 2 aufeinanderfolgenden Einsen oder Nullen markiert

Wenn Sie beispielsweise 5 Elemente durchgehen möchten, sind die Folgenummern:

{01010} (Schreiben an 0) {11010} (Schreiben an 1) {10010} (Schreiben an 2) {10110} (Schreiben an 3) {10100} (Schreiben an 4) {10101} (Schreiben an 5)

Mick Clift
quelle
1

Abhängig von der Art Ihres EEPROM und der Größe Ihrer Daten gibt es eine Reihe von Optionen.

  1. Wenn Ihr EEPROM individuell löschbare Seiten hat und Sie 1 Seite (oder mehr) verwenden, lassen Sie einfach alle Seiten mit Ausnahme der verwendeten Seiten gelöscht und verwenden Sie die Seiten zirkulär wieder.

  2. Wenn Sie nur einen Bruchteil einer Seite verwenden, die sofort gelöscht werden muss, unterteilen Sie diese Seite in Dateneinträge. Verwenden Sie jedes Mal, wenn Sie schreiben, einen bereinigten Eintrag, und löschen Sie ihn, wenn keine bereinigten Einträge mehr vorhanden sind.

Verwenden Sie ein "Dirty" -Bit, um bei Bedarf zwischen sauberen und schmutzigen Einträgen zu unterscheiden (normalerweise haben Sie mindestens ein Byte, das sich garantiert von 0xFF unterscheidet und das zum Verfolgen von schmutzigen Einträgen verwendet werden kann).

Wenn Ihre EEPROM-Bibliothek die Löschfunktion nicht verfügbar macht (wie Arduino), ist hier ein guter Trick für Algorithmus 2: Da Ihr erster EEPROM-Eintrag immer verwendet wird, können Sie den Wert des "schmutzigen" Bits durch Lesen ermitteln. Sobald Sie keine sauberen Einträge mehr haben, beginnen Sie einfach wieder mit dem ersten Eintrag, indem Sie das "schmutzige" Bit invertieren, und der Rest Ihrer Einträge wird automatisch als "sauber" markiert.

Sequenznummern und Kataloge sind Platzverschwendung, es sei denn, Sie möchten fehlerhafte Seiten nachverfolgen oder verschiedene Teile Ihrer EEPROM-Daten unabhängig voneinander aktualisieren können.

Dmitry Grigoryev
quelle