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
typedefstruct
2
{
3
uint8_tnumberOne;
4
uint8_tnumberTwo;
5
}old_data_t;
zu
1
typedefstruct
2
{
3
uint8_tnumberOne;
4
uint8_tnumberThree;
5
uint8_tnumberTwo;
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;)
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.
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
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
typedefstruct
2
{
3
uint8_tvalue;
4
}device_data_t;
5
6
typedefstruct
7
{
8
uint8_tversion;
9
uint32_tdataStructLength;
10
device_data_tdeviceData;
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.
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.
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.
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
structeeprom_data_extension1;
6
7
typedefstruct
8
{
9
uint8_tnumberOne;
10
uint8_tnumberTwo;
11
structeeprom_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
structeeprom_data_extension2;
8
9
// Place an instance of this extension struct after all old existing
10
// EEPROM contents.
11
typedefstructeeprom_data_extension1{
12
uint8_tnumberThree;
13
structeeprom_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
typedefstruct
19
{
20
uint8_tnumberOne;
21
uint8_tnumberTwo;
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
typedefstruct
6
{
7
uint8_tnumberOne;
8
uint8_tnumberTwo;
9
uint8_tnumberThree;
10
}ram_data_t;
11
12
ram_data_trd;// 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.
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.
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.
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).
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
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.