Zum Beispiel: Das Datenblatt für ATtiny2313 (wie die meisten Atmel AVR-Datenblätter) besagt:
128 Bytes im System programmierbares 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 beunruhigt ist, dass regelmäßige Updates der beiden Konfigurationsbytes das EEPROM des Geräts verschleißen und unbrauchbar machen können. Das ganze Gerät würde unzuverlässig werden, weil man zu einem bestimmten Zeitpunkt einfach nicht mehr verfolgen kann, welche Bytes im EEPROM unzuverlässig sind.
Gibt es eine intelligente Möglichkeit, Wear Leveling auf dem EEPROM eines Mikrocontrollers durchzuführen, wenn Sie effektiv nur ein oder zwei Bytes von verfügbaren 128 verwenden?
Die Technik, die ich normalerweise verwende, besteht darin, den Daten eine fortlaufende 4-Byte-Sequenznummer voranzustellen, wobei die größte Zahl den letzten / aktuellen Wert darstellt. Im Fall der Speicherung von 2 Bytes tatsächlicher Daten, die insgesamt 6 Bytes ergeben würden, würde ich dann eine kreisförmige Warteschlangenanordnung bilden, sodass 128 Bytes EEPROM 21 Einträge enthalten und die Lebensdauer um das 21-fache erhöhen würden.
Dann kann beim Booten 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 demonstriert, dies setzt voraus, dass der EEPROM-Bereich bei der Erstprogrammierung auf Werte von 0xFF gelöscht wurde, sodass ich eine Sequenznummer von 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, würde jedoch ein wenig Bit-Slicing erfordern, anstatt Standarddatentypen zu verwenden.
Es folgt eine Methode, die Buckets und etwa ein Overhead-Byte pro Bucket verwendet. Die Bucket-Bytes und Overhead-Bytes werden etwa gleich stark beansprucht. Im vorliegenden Beispiel weist dieses Verfahren bei gegebenen 128 EEPROM-Bytes 42 2-Byte-Buckets und 44 Statusbytes zu, was die Verschleißfähigkeit um das 42-fache erhöht.
Methode:
Unterteilen Sie den EEPROM-Adressraum in k Buckets, wobei k =⌊ E /( n +1)⌋, mit n = Setup-Data-Array-Größe = Bucket-Größe und E = EEPROM-Größe (oder allgemeiner die Anzahl der EEPROMs). Zellen, die dieser Datenstruktur gewidmet werden sollen).
Initialisiere ein Verzeichnis, ein Array von m Bytes, die alle auf k gesetzt sind , mit m = En·k . Wenn Ihr Gerät hochfährt, liest es das Verzeichnis durch, bis es den aktuellen Eintrag findet, der ein Byte ungleich k ist . [Wenn alle Verzeichniseinträge gleich k sind, initialisiere den ersten Verzeichniseintrag auf 0 und mache von dort aus weiter.]
Wenn der aktuelle Verzeichniseintrag j enthält , enthält Bucket j aktuelle Daten. Wenn Sie einen neuen Setup-Dateneintrag schreiben müssen, speichern Sie j +1 im aktuellen Verzeichniseintrag; Wenn es dadurch gleich k wird, initialisiere den nächsten Verzeichniseintrag mit 0 und mache von dort aus weiter.
Beachten Sie, dass Verzeichnis-Bytes ungefähr den gleichen Verschleiß erfahren wie Bucket-Bytes, weil 2 · k > m ≥ k .
(Ich habe das Obige aus meiner Antwort auf die Arduino SE-Frage 34189 angepasst , Wie kann die Lebensdauer des EEPROM verlängert werden? .)
Es gibt ein paar Optionen, abhängig von der Art des EEPROMs, das Sie haben, und der Größe Ihrer Daten.
Wenn Ihr EEPROM einzeln löschbare Seiten hat und Sie 1 Seite (oder mehr) verwenden, lassen Sie einfach alle Seiten außer den verwendeten gelöscht und verwenden Sie die Seiten im Kreis wieder.
Wenn Sie nur einen Bruchteil einer Seite verwenden, der sofort gelöscht werden muss, teilen Sie diese Seite in Dateneinträge auf. Verwenden Sie jedes Mal einen sauberen Eintrag, wenn Sie schreiben, und löschen Sie ihn, wenn Ihnen die sauberen Einträge ausgehen.
Verwenden Sie bei Bedarf ein "dirty"-Bit, um zwischen sauberen und schmutzigen Einträgen zu unterscheiden (normalerweise haben Sie mindestens ein Byte, das sich garantiert von 0xFF unterscheidet, 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 netter Trick für Algorithmus Nr. 2: Da Ihr erster EEPROM-Eintrag immer verwendet wird, können Sie den Wert des "schmutzigen" Bits bestimmen, indem Sie ihn lesen. Sobald Ihnen die sauberen Einträge ausgehen, fangen Sie einfach wieder beim ersten Eintrag an, invertieren das "Dirty"-Bit, und der Rest Ihrer Einträge wird automatisch als "sauber" markiert.
Sequenznummern und Kataloge sind Platzverschwendung, es sei denn, Sie möchten fehlerhafte Seiten verfolgen oder verschiedene Teile Ihrer EEPROM-Daten unabhängig voneinander aktualisieren.
Ich habe dafür eine fortlaufende Sequenznummer verwendet (ähnlich wie Peters Antwort). Die Sequenznummer kann tatsächlich nur 1 Bit betragen, vorausgesetzt, die Anzahl der Elemente im Cue ist ungerade. Kopf und Schwanz werden dann durch die 2 aufeinanderfolgenden Einsen oder Nullen gekennzeichnet
Wenn Sie beispielsweise 5 Elemente durchlaufen möchten, wären die Sequenznummern:
{01010} (auf 0 schreiben) {11010} (auf 1 schreiben) {10010} (auf 2 schreiben) {10110} (auf 3 schreiben) {10100} (auf 4 schreiben) {10101} (auf 5 schreiben)
Ich glaube, es gibt eine einfache Möglichkeit, nur n Zellen und ein einziges dediziertes Bitmuster zu verwenden, um eine n/2-Erhöhung der Haltbarkeit zu erreichen.
Das dedizierte Bitmuster (z. B. 0xffff) ist ein Marker, der eine ungenutzte Zelle anzeigt, und im Normalbetrieb hat höchstens eine Zelle gleichzeitig andere Werte als diesen Markerwert:
uint16_t readWord (const uint16_t *address);
void writeWord (uint16_t *address, uint16_t value);
#define CELL_COUNT 42
#define BASE_ADDRESS 12
#define MARKER 0xffff // Best to use EEPROM default for this
static uint8_t curCell = 0;
static void wlInit (void)
{
for ( uint8_t ii = 0 ; ii < CELL_COUNT ; ii++ ) {
if ( readWord (BASE_ADDRESS + ii) != MARKER ) {
curCell = ii;
// No early return/break here to keep init() execution time
// constant. Could also check for corruption here by checking for
// > 1 cells without MARKER (see below).
}
}
// If we didn't find a non-marker cell we'll end up with curCell = 0
// per the initial value of curCell
}
static uint16_t wlRead (void)
{
return readWord (BASE_ADDRESS + curCell);
}
static void wlWrite (uint16_t value)
{
assert (value != MARKER); // Storing marker value is big no-no
uint8_t nextCell = (curCell + 1) % CELL_COUNT;
writeWord (BASE_ADDRESS + nextCell, value);
writeWord (BASE_ADDRESS + curCell, MARKER);
curCell = nextCell;
}
Derselbe Ansatz kann mit strukturierten Daten verwendet werden, vorausgesetzt, dass ein Bitmuster reserviert werden kann (entweder in einem dedizierten Byte oder von einem anderen Feld, das ein Ersatzmuster hat).
Dieser Ansatz hat auch eine nützliche Eigenschaft in Bezug auf unterbrochene (z. B. Stromausfall) Schreibvorgänge: Wenn jemals zwei Zellen ohne den MARKER-Wert vorhanden sind, ist ein unvollständiger (dh beschädigter) Schreibvorgang aufgetreten. Dies erscheint sinnvoll, da z. B. das ATmega328P-Datenblatt den folgenden unklaren und nicht besonders beruhigenden Absatz über die Verwendung der Brown-Out-Erkennung zur Vermeidung von Korruption enthält:
Halten Sie AVR RESET aktiv (low) während Perioden mit unzureichender Versorgungsspannung. Dies kann durch Aktivieren des internen Brown-Out-Detektors (BOD) erfolgen. Wenn der Erkennungspegel des internen BOD nicht mit dem erforderlichen Erkennungspegel übereinstimmt, kann eine externe Schutzschaltung zum Zurücksetzen auf niedrige V CC verwendet werden. Wenn ein Reset auftritt, während ein Schreibvorgang abläuft, wird der Schreibvorgang abgeschlossen, vorausgesetzt, dass die Versorgungsspannung ausreichend ist.
Anindo Ghosh
Jippie
m.Alin
JimmyB
geometrisch