General > General Technical Chat
Preserving settings over a firmware upgrade
DavidAlfa:
You could keep older settings struct definitions, then detect, check and import them.
All the functions would need to be aware of older versions and how to handle them.
A simple example, might contains some errors and possibly a lot of alignment issues, this is only a simple idea:
--- Code: ---#define SETTINGS_VERSION settings_v2 // Current settings version
typedef enum {
settings_v0 = 0xFF00,
settings_v1 = 0xFE01,
settings_v2 = 0xFD02,
}settings_version;
typedef struct { // First int is always settings version
struct{
uint16_t version;
uint16_t data0;
uint16_t data1;
}data;
uint16_t checksum;
} settings_v0_t;
typedef struct {
struct{
uint16_t version;
uint16_t data0;
uint16_t data1;
uint16_t data2;
}data;
uint16_t checksum;
} settings_v1_t;
typedef struct {
struct{
uint16_t version;
uint16_t data0;
uint16_t data1;
uint16_t data2;
uint16_t data3;
}data;
uint16_t checksum;
} settings_v2_t;
--- End code ---
--- Code: ---void init(void){
void * settings = some_flash_address;
uint16_t version = *(uint16_t*)settings;
if(!check_settings(settings))
if(version != SETTINGS_VERSION)
import_settings(settings);
else
reset_settings();
}
--- End code ---
--- Code: ---// Return 1 if error, 0 if OK.
bool check_settings(void * settings){
uint16_t new_checksum = checksum(settings);
uint16_t checksum=0;
switch (*(uint16_t*)settings){
case settings_v0:
checksum = (settings_v0_t*)settings->checksum;
break;
case settings_v1:
checksum = (settings_v1_t*)settings->checksum;
break;
case settings_v2:
checksum = (settings_v2_t*)settings->checksum;
break;
default:
return 1;
}
if(new_checksum != checksum)
return 1;
return 0;
}
--- End code ---
--- Code: ---// Import different system settings, reset new values to their default state
void import_settings(void * settings){
settings_v2_t s2; // Temporal buffer to import settings into
switch (*(uint16_t*)settings){
case settings_v0:
s2.data.data0 = *(settings_v0_t*)settings->data.data0;
s2.data.data1 = *(settings_v0_t*)settings->data.data1;
s2.data.data2 = DEFAULT_DATA_2;
s2.data.data3 = DEFAULT_DATA_3;
break;
case settings_v1:
s2.data.data0 = *(settings_v0_t*)settings->data.data0;
s2.data.data1 = *(settings_v0_t*)settings->data.data1;
s2.data.data2 = *(settings_v0_t*)settings->data.data2;
s2.data.data3 = DEFAULT_DATA_3;
break;
case settings_v2: // Nothing to do, same settigns version, we shouldn't reach this
break;
default: // Unknown_settings_version !
break;
}
s2.data.version = SETTINGS_VERSION;
s2.checksum = checksum(s2);
write_settings(s2);
reboot();
}
--- End code ---
--- Code: ---// Compute checksum for each settings version
uint16_t checksum(void * settings){
uint16_t sz=0;
uint16_t checksum=0;
uint8_t * data = (uint8_t*)settings;
switch (*(uint16_t*)settings){
case settings_v0:
sz = sizeof(settings_v0_t.data);
break;
case settings_v1:
sz = sizeof(settings_v1_t.data);
break;
case settings_v2:
sz = sizeof(settings_v2_t.data);
break;
default:
return 0;
}
for(uint16_t i=0;i<sz;i++){
checksum += *data++;
}
return checksum;
}
--- End code ---
PlainName:
--- Quote ---I don't think you understand how the flash works. It has to be erased by sector and fully rewritten.
--- End quote ---
I am fully conversant with how to program flash, thank you, and ways to retain existing data whilst changing parts that are smaller than a flash page.
thm_w:
--- Quote from: tom66 on July 25, 2023, 05:13:06 pm ---Build the data in the memory a little like a key-value struct, where each setting has a 'key ID' (this can just be decoded to flash address) and a size.
The key ID can encode the type and size of variable. For instance, the first byte can signal type (int8, uint16, float) and the second byte might encode maximum size. Then the remaining 16 bits (assuming a 32-bit CPU) can effectively encode the address of the value.
Then you write a number of functions that do things like:
...
--- End quote ---
This is a cool example of what you are talking about for EEPROM but sadly not open source: https://www.realtime.bc.ca/articles/nonvol.html
nctnico:
--- Quote from: PlainName on July 26, 2023, 10:59:51 am ---
--- Quote from: Siwastaja on July 26, 2023, 05:57:31 am ---
--- Quote from: nctnico on July 25, 2023, 09:22:33 pm ---Exactly! Conversion between configuration versions basically comes for free when using JSON to store the configuration.
--- End quote ---
It only avoids the fixed memory offset relationships, but does not magically understand functional differences which still need migration code to solve.
...
Migration between two binary formats is actually simpler:
--- Code: ---void migrate_v1_to_v2(struct conf_v1 const * const v1, struct conf_v2 * const v2)
{
v2->thing1 = v1->thing1 * 0.1;
v2->thing2 = v1->thing2;
}
--- End code ---
With json, this is otherwise the same but instead of direct access with variable names and assignment operators, you use the json library to parse and generate the json. No automation nor simplification here, only a complication.
--- End quote ---
Where you have that kind of migration code you're polluting the product for a one-time (possibly never) migration. And next time it won't work so you'll need another lot of migration code to do it all again. Or never again.
--- End quote ---
That is the cost of backward compatibility. For sure everyone will agree that it is better to avoid needing such migrations but either way it is better to prepare the way the data is stored so that software can detect the settings are compatible or not. When the configuration data is incompatible between versions, the next question is whether to use sensible defaults or try to convert.
Shonky:
--- Quote from: thm_w on July 26, 2023, 09:41:47 pm ---
--- Quote from: tom66 on July 25, 2023, 05:13:06 pm ---Build the data in the memory a little like a key-value struct, where each setting has a 'key ID' (this can just be decoded to flash address) and a size.
The key ID can encode the type and size of variable. For instance, the first byte can signal type (int8, uint16, float) and the second byte might encode maximum size. Then the remaining 16 bits (assuming a 32-bit CPU) can effectively encode the address of the value.
Then you write a number of functions that do things like:
...
--- End quote ---
This is a cool example of what you are talking about for EEPROM but sadly not open source: https://www.realtime.bc.ca/articles/nonvol.html
--- End quote ---
Full source linked at the end?
Navigation
[0] Message Index
[#] Next page
[*] Previous page
Go to full version