Forum: Mikrocontroller und Digitale Elektronik Struktur von Konfigurationsdaten im EEPROM, erweiterbar machen


von Marcel B. (cable545)


Lesenswert?

Hallo,

ich habe mal eine Frage zu folgendem Scenario.
Ich habe eine Datenstruktur (struct), welche zur beim Systemstart aus 
einem EEPROM gefüllt wird. Diese kann bei Bedarf zur Laufzeit auch 
wieder im EEPROM gespeichert werden. Gespeichert werden die Daten der 
Struktur Byte für Byte am Anfang des EEPROMS.

Nun kommt meine Frage. Wenn ich die Datenstruktur verändern möchte, also 
zum Beispiel von
1
typedef struct
2
{
3
  uint8_t numberOne;
4
  uint8_t numberTwo;
5
} old_data_t;

zu
1
typedef struct
2
{
3
  uint8_t numberOne;
4
  uint8_t numberThree;
5
  uint8_t numberTwo;
6
} new_data_t;

benötige ich nach der Änderung in dem Programm eine Routine die nach dem 
Systemstart einen Datensatz mit einer alten Datenstruktur aus dem EEPROM 
erkennt und entsprechend behandelt.

Mir geht es nicht um eine Lösung für den Fall der obigen 
Beispielstruktur sondern um einen generellen Ansatz wie man 
Konfigurationsdaten in einen EEPROM packt und dabei der Aufwand bei 
Erweiterungen dieser Struktur gering bleibt.

Ich hoffe mein Anliegen ist Verständlich;)

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Du könntest als ersten Eintrag in der Struktur eine Versionskennung 
unterbringen, und bei Erweiterungen immer nur hinten anhängen.

Zur Sicherheit kannst Du noch in einem weiteren (am besten dem zweiten) 
Strukturelement das Ergebnis eines sizeof der jeweiligen 
Strukturdefinition unterbringen.

Beim Lesen der Struktur musst Du, sofern Du auf später hinzugekommene 
Elemente zugreifen willst, halt die Versionskennung/Strukturgröße 
prüfen.

von U. M. (oeletronika)


Lesenswert?

Marcel B. schrieb:
> Mir geht es um einen generellen Ansatz wie man
> Konfigurationsdaten in einen EEPROM packt und dabei der Aufwand bei
> Erweiterungen dieser Struktur gering bleibt.
idealer weise plant man eine Datenstruktur gleich so, dass sie spätere 
Optionen und Ausbauvarianten schon berücksichtigt.
Das braucht natürlich mehr Platz und eine weit reichende Planung sowie 
meist auch Erfahrung. Manchmal kann man aber trotzdem nicht verhersagen, 
was später noch kommen kann.

Später hinzukommende Parameter irgend wo dazwischen zu quetschen und 
somit Daten von ihrem ursprünglichen Platz zu verschieben, ist sicher 
die schlechteste Variante, weil dann die Parameter nach einem 
Firmwareupdate nix mehr Wert sind und unter Umständen mühselig von Hand 
neu eigetragen werden müssen.
Da ist es dann doch besser, auf Ordnung und Zusammenhang zu verzichten 
und neue Parameter hinten anzuhängen, auch wenn diese im logischen 
Zusammenhang lieber weiter vorne hätte.
Gruß Öletronika

von Marcel B. (cable545)


Lesenswert?

Die Idee mit der Versionskennung am Anfang der Struktur kam mir auch 
schon in den Sinn. Generell zur Erkennung einer Änderung ist das eine 
gute Idee. Allerdings finde ich die Einschränkung die Erweiterung nur am 
Ende der Struktur machen zu dürfen nicht so günstig. Gerade bei 
verschachtelten Strukturen:
1
typedef struct
2
{
3
  uint8_t value;
4
} device_data_t;
5
6
typedef struct
7
{
8
  uint8_t version;
9
  uint32_t dataStructLength;
10
  device_data_t deviceData;
11
} data_t;

Wenn ich nun die Struktur von device_data_t ändere, erkenne ich zwar an 
der Version, dass eine Änderung vorliegt, aber ich benötige eine 
Routine, welche die Daten von der älteren Version auf die neuere Version 
Mapped. Die kann je nach Größe sehr aufwendig werden.

von test (Gast)


Lesenswert?

Du kannst dir auch eine Struktur in Form von Key, Type und Value Paaren 
erstellen. Also so etwas was eine INI oder XML Datei macht, halt nur in 
Form einer optimierten Datenstruktur (und nicht als Text der geparst 
werden muss).

Ist allerdings beim Einlesen/Abspeichern etwas aufwendiger, aber dafür 
hast du den Aufwand nur einmal.

von A. S. (Gast)


Lesenswert?

Wartbar ist nur das hinten anfügen.

Wenn Du 7 Mal alles umgemapped hast und deine letzte Version mit allen 
Vorgängern klar kommen soll, weißt Du, warum.

Alternativ halt die Daten auf Default, wenn die Struktur sich geändert 
hat.

Beim ummappen und gleichzeitig reset ist eh alles in ne fritten.

von Marten Morten (Gast)


Lesenswert?

Marcel B. schrieb:
> Hallo,
>
> ich habe mal eine Frage zu folgendem Scenario.
> Ich habe eine Datenstruktur (struct), welche zur beim Systemstart aus
> einem EEPROM gefüllt wird. Diese kann bei Bedarf zur Laufzeit auch
> wieder im EEPROM gespeichert werden. Gespeichert werden die Daten der
> Struktur Byte für Byte am Anfang des EEPROMS.

Trenn dich von der Idee, dass die Daten im EEPROM und RAM das gleiche 
Bitmuster haben. Dann kannst du im EEPROM Pointer verwenden und musst 
vorhandene Daten nicht umkopieren, mit dem Problem, dass sich der 
gesamte restliche EEPROM-Inhalt auch verschiebt.
1
// First version of data structure in firmware v1. Prepared for an
2
// extension, but doesn't know about the extension. 
3
// Set ext1 = NULL when first writing the data to EEPROM.
4
//
5
struct eeprom_data_extension1;
6
7
typedef struct
8
{
9
   uint8_t numberOne;
10
   uint8_t numberTwo;
11
   struct eeprom_data_extension1 *ext1; // reserved for future use
12
} eeprom_data_t;
1
// Second version of data structure in a new firmware v2. Knows about the 
2
// first extension, prepared for yet another extension, but doesn't know
3
// about the other extension.
4
// Set ext1 to an instance of eeprom_data_extension1_t, located behind
5
// all other existing EEPROM contents.
6
// Set ext2 = NULL when writing to EEPROM.
7
struct eeprom_data_extension2;
8
9
// Place an instance of this extension struct after all old existing
10
// EEPROM contents.
11
typedef struct eeprom_data_extension1 {
12
   uint8_t numberThree;
13
   struct eeprom_data_extension2 *ext2; // reserved for future use
14
} eeprom_data_extension1_t;
15
16
// Place an instance of this struct at the exact same location in 
17
// EEPROM as in the first firmware v1
18
typedef struct
19
{
20
   uint8_t numberOne;
21
   uint8_t numberTwo;
22
   eeprom_data_extension1_t *ext1; // Data continues at ext1
23
} eeprom_data_t;


> benötige ich nach der Änderung in dem Programm eine Routine die nach dem
> Systemstart einen Datensatz mit einer alten Datenstruktur aus dem EEPROM
> erkennt und entsprechend behandelt.
1
// In Firmware v2, the one aware of the extension being present
2
3
// RAM version of the data structure (if it is desired to have a continous
4
// version of the data in RAM
5
typedef struct
6
{
7
   uint8_t numberOne;
8
   uint8_t numberTwo;
9
   uint8_t numberThree;
10
} ram_data_t;
11
12
ram_data_t rd;         // RAM version of the data, continous
13
eeprom_data_t *ed;     // EEPROM version of the data, chained
14
15
rd.numberOne = ed->numberOne;
16
rd.numberTwo = ed->numberTwo;
17
// Use new data if it exists. Otherwise set to default (e.g. 0)
18
rd.numberThree = ed->ext? ed->numberThree : 0;


> Mir geht es nicht um eine Lösung für den Fall der obigen
> Beispielstruktur sondern um einen generellen Ansatz wie man
> Konfigurationsdaten in einen EEPROM packt und dabei der Aufwand bei
> Erweiterungen dieser Struktur gering bleibt.

Einfaches erweitern der Daten vermeiden, weil man dann im schlimmsten 
Fall das ganze EEPROM umbauen muss.

Beitrag #5728662 wurde von einem Moderator gelöscht.
von c-hater (Gast)


Lesenswert?

A. S. schrieb:

> Wartbar ist nur das hinten anfügen.

Jepp. Aber das darf man gerne auch auf intelligent machen.

D.h.: Man schaltet ein Mapping zwischen RAM- und EEPROM-Struktur und 
definiert für jede neue Version der Gesamtstruktur ein "EOF" (welches 
natürlich ebenfalls im EEPROM gespeichert werden muss).

Laden der Daten geht so:
1) Initialisierung der RAM-Strukturen mit Defaultwerten
2) Laden jedes Members über's Mapping sofern der Offset<EOF(EEPROM)

Danach hat man alles im RAM, was von jeder beliebigen Vorgängerstruktur 
brauchbar ist, der Rest ist mit den Defaultwerten besetzt.

Beim Speichern schreibt man einfach alle Werte auf ihre Mappingadressen 
und zum Schluss das neue "EOF". Geht dabei irgendwas schief, ist also 
immer noch die Vorgängerstruktur im EEPROM intakt.

Das Mapping selber liegt natürlich im Flash als schlichtes Array von 
integers (für jeden Member irgendeiner Struktur eins) und ist natürlich 
bei jeder Strukturerweiterung nachzupflegen. Mit ein paar intelligent 
programmierten Macros ist dieser lästige Aufwand sehr gut beherrschbar.

von Gerd E. (robberknight)


Lesenswert?

Ich schlage zusätzlich vor, noch ein Feld für eine Prüfsumme (z.B. CRC32 
oder MD5) von Anfang an mit vorzusehen. Die kann man z.B. gleich am 
Anfang direkt nach der Versionskennung unterbringen. Deren Position kann 
dann über alle Versionen hin konstant bleiben.

Damit kannst Du jederzeit prüfen, ob die Daten im EEPROM in sich 
konsistent sind oder es beim Schreiben zu einem Fehler (z.B. 
Unterbrechung der Stromversorgung) kam, der die Daten durcheinander 
gebracht hat.

von S. R. (svenska)


Lesenswert?

Der Sinn des "Versionskennung als erstes Feld" und "nur hinten 
erweitern" liegt darin, dass alle Versionen erstmal prinzipiell 
abwärtskompatibel sind.

Eine alte Firmware läuft mit dem neuen EEPROM ganz normal weiter, eine 
neue Firmware muss nur beim Einlesen aufpassen, welche Felder mit 
Standardwerten belegt werden müssen und welche im EEPROM gültig sind 
(relativ wenig Code). Jede Firmware muss nur ihr eigenes Format 
schreiben können.

Wenn man inkompatible Änderungen zulassen möchte, dann kann man das 
Versionsfeld als (Major,Minor) definieren, wobei die Major-Version eine 
komplett neue Struktur bezeichnet. Eine neue Firmware muss dann jede 
relevante vorherige Major-Version lesen können (sinnvollerweise eine 
eigene Funktion, sonst wird das unwartbar) und muss möglicherweise auch 
ein paar ältere Formate schreiben können.

Eine Prüfsumme ist nur sinnvoll, wenn die Struktur auch ihre eigene 
Größe enthält (sonst würde eine alte Firmware nur einen Teil der 
Struktur prüfen und die Prüfsumme wäre immer ungültig). Sinnvoll wäre 
vielleicht auch ein Header (z.B. 8 ASCII-Zeichen und ein Schreibzähler), 
falls mehrere Strukturen im EEPROM vorhanden sind (Wear-Levelling oder 
Redundanz).

von Marcel B. (cable545)


Lesenswert?

Vielen Dank für eure Ideen. Ich werde mir die Konzepte mal in einem 
praktischen Beispiel anschauen.

von Rainer V. (a_zip)


Lesenswert?

Marcel B. schrieb:
> ich habe mal eine Frage zu folgendem Scenario.

Hallo, ich würde erst einmal zwei Fälle unterscheiden. 1. du erweiterst 
die Struktur. Es werden mehr Werte gleichen Typs gespeichert. Dann muß 
die Hauptroutine wissen, welche Werte für sie gültig sind. Wenn du alles 
immer "hinten" anfügst, dann muß die Hauptroutine wissen, wie lang ihr 
gültiger Datensatz ist.
2. du veränderst die Struktur. Dann wirst du wohl für jede "Erweiterung" 
einen kompletten Datensatz abspeichern müssen. Andernfalls wird der 
Verwaltungsaufwand riesig!
Versionsnummern, Prüfsummen ect. sind eben Verwaltungsarbeit, wenn 
nötig.
Gruß Rainer

von Vincent H. (vinci)


Lesenswert?

Sofern dein Prozessor mehr als ein paar kB Flash und Ram hat und 
Änderungen in der Datenstruktur häufig antizipiert werden würde ich eine 
Serialisierung in Erwägung ziehen.

Der obige Vorschlag mit den Pointern braucht allein für Erklärung dass 1 
Byte irgendwie angefügt würde 20 Zeilen Kommentare... Das stell ich mir 
recht lustig vor wenn das irgendwer warten muss.

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
Noch kein Account? Hier anmelden.