Saving a block of initialized variables to EEPROM
altosack
Posts: 132
This is a generic C question, but I'm using it on a Prop, and I couldn't think of a nicer bunch of guys to ask ...
I have a large block of variables that I want to occasionally save back to the EEPROM so the updated values will be available on the next launch. They need to be initialized in the code, and are a variety of types and lengths, so I thought that the best way to keep them contiguous and consistently defined in memory and EEPROM would be to declare and initialize them within a struct, as follows:
This doesn't work; apparently you can't initialize variables inside a struct this way. I know I could do the following, but then the initialized values will not be paired with the definition, so it will be very inconvenient to write and maintain, since I have about 80 variables, not the (3) I have listed here:
Any ideas on how to do what I want, i.e., pair the initialization value with the variable definition, while making sure that the variables are contiguous as defined in memory and EEPROM ?
I have a large block of variables that I want to occasionally save back to the EEPROM so the updated values will be available on the next launch. They need to be initialized in the code, and are a variety of types and lengths, so I thought that the best way to keep them contiguous and consistently defined in memory and EEPROM would be to declare and initialize them within a struct, as follows:
struct { uint32_t var1 = VALUE1; float var2 = VALUE2; uint16_t var3 = VALUE3; }
This doesn't work; apparently you can't initialize variables inside a struct this way. I know I could do the following, but then the initialized values will not be paired with the definition, so it will be very inconvenient to write and maintain, since I have about 80 variables, not the (3) I have listed here:
struct { uint32_t var1; float var2; uint16_t var3; } = { VALUE1, VALUE2, VALUE3 }
Any ideas on how to do what I want, i.e., pair the initialization value with the variable definition, while making sure that the variables are contiguous as defined in memory and EEPROM ?
Comments
Maybe someone will offer a good solution while I'm in China Town.
I think the simplest solution is to do something like this:
At runtime, the_database is initialized from initialvalues before main is called, and you can't change initialvalues at runtime, but you can manipulate the_database in any way you like. If you want to write the values of the_database to the eeprom is get a pointer to the initial values (&initialvalues) and use it as EEPROM address. Then you read the current values (from the_database) and write them to the EEPROM at the address calculated from &initialvalues). When the propeller restarts, it will initialize the_database from the new values.
Sorry, I don't have code to write the data to the EEPROM but I think this should get you started.
===Jac
Actually, your way takes twice the memory and uses startup code, but you made me realize I can do it as follows:
While it's not pretty and the variable names are listed twice (3 times with the #define to make the rest of the program more readable), at least it's maintainable code, since the initialization values are paired with the variable name.
I would like to see what you come up with (including the code to save the data to EEPROM). I would say this (i.e. storing data in the EEPROM to update initialization values) is a need that many developers may have, and it's worth writing an article on the Learn website.
===Jac
It will use exactly 10 bytes in a contiguous portion of EEPROM, in the order in which they're defined, so I can use the following to access it in C:
There are two problems with this:
- I don't really want to use assembly language (*I* don't care that much, but I want my code to be friendly)
- I don't want to define all the variables twice; that's a code maintenance headache.
So, I guess my questions are as follows:
- Is my assumption above about the way C will handle my initialized struct from my previous post, correct ?
- Is there any way to define in C what I did in assembly, and have it do it just as efficiently ?
- What is the offset between EEPROM and memory, if any, in propgcc ?
Define a struct for your data. Then declare a struct pointer with the start address.
Theoretically*, once you copy and write data to that address, the program should startup with the data that you saved.
I know that this isn't exactly what you are looking for, but barring using a linker script, it's the best we can do.
*I haven't tested this idea.
The generated Assembly code contains the following for the declaration of the_database:
That means that the_database is allocated in the ".data" segment (where initialized data lives) and the data is stored right there where the "_the_database" label is. There is no runtime copying: the image really does have the data at that location.
According to my map file, _the_database is located at memory location 0x18BC. I used propeller-load -s a.out to generate a binary file and confirmed that the initialization data is stored at offset 0x18BC. Unless I'm very much mistaken, the binary format is a 1-on-1 dump of what goes into the EEPROM and there is no need to add or subtract $10; The $10-byte header with the initial clock frequency and other fun stuff is part of the binary file, so what you see is what goes in the EEPROM and is also what comes out into the Hub at boot time.
So, to override your defaults with new values, you would do:
There is no Assembly code required, and the single ee_putStr is pretty easy to read, even for beginners (if not, you could write a macro, say #define WRITE_DEFAULTS_TO_EEPROM(x) ee_putStr(&x, sizeof(x), (int)&x) ). All the initialization data is in one place, and is solid (i.e. if someone changes the organization of the struct type, or if you change the order of the initialization lines in the struct, it will still be initialized correctly). Also, Jazzed's idea of putting the data in high memory and somehow not overwriting it, and using linker script magic, isn't necessary. You can do this with just straightforward C and a little help from ee_putStr, but obviously this only works with variables that are in the ".data" segment, not with data in the .bss segment. And to force data into ".data", you simply have to specify (non-zero) initialization values.
I strongly recommend against this sort of dead-reckoning: you're assuming here that variables will end up in memory in adjacent locations which is definitely not going to be the case if some of those variables are initialized to 0 (which will store them in .bss where the initial value cannot be modified because .bss is initialized at runtime) and other variables are stored in .data because they have nonzero initialization values.
Even if it happens to work now, it may not work if in some future version, the GCC compiler changes the way it stores variables (for example, what if the GCC developers decide it's better to store them from the top of memory and going back, instead of the bottom, going up). If you really don't want to put the data in a struct, you should use one ee_putStr() call for each variable to keep the code safe. Depending on how often the default values need to be changed, it could potentially generate some extra wear on your EEPROM(*). So the struct really is the best idea, if you ask me :-)
Hope this helps!
===Jac
(*) You could argue that if there is some data that needs to be changed in the EEPROM much more often than other data, it's better to only write the data that changed, instead of writing the entire struct every time one item changes. But with a struct, you can do either: it's not that hard to call ee_putStr for just a field in the database struct instead of writing the entire struct. I'll leave that to you ;-)
The database start address can change because of program modifications though.
One could use a linker script is used to force a given address for a symbol.
Ah, now I see why you mentioned the linker script: retention of the database across software updates.
I don't know if database retention across software updates is a requirement for altosack's application, but regardless of whether you use a linker script or not, you would still need some code on the Propeller to either (a) read the values to the PC before the updated program gets stored in the EEPROM, and then update the database of the new software with the old values, or (b) use a custom download application that writes the new application to the areas that don't belong to the database. And in both cases, this kinda assumes that the format of the database doesn't change across software updates, which may be dangerous.
It sounds like maintainability is a high priority for altosack's project, so as a developer I would simply state that when the software gets updated, the database gets reset to the default values. Or I would so something like print the values of the database to the serial port in ASCII when the program starts, so that in the future, an application on the PC can use the output to patch a binary file before it gets downloaded. This avoids linker scripts (which may not be easy to maintain by beginner programmers) and it avoids problems with incompatibilities between database versions.
===Jac