Shop OBEX P1 Docs P2 Docs Learn Events
Saving a block of initialized variables to EEPROM — Parallax Forums

Saving a block of initialized variables to EEPROM

altosackaltosack Posts: 132
edited 2013-09-05 09:22 in Propeller 1
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:
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

  • jazzedjazzed Posts: 11,803
    edited 2013-09-02 10:31
    I wish I had a simple answer for you. It will require more study than I have time for today.

    Maybe someone will offer a good solution while I'm in China Town.
  • jac_goudsmitjac_goudsmit Posts: 418
    edited 2013-09-02 13:13
    An interesting problem!

    I think the simplest solution is to do something like this:
    typedef struct
    {
      uint32_t var1;
      float var2;
      uint16_t var3;
    } database_t;
    
    const HUBDATA database_t initialvalues = 
    {
      // I recommend using field names in struct initialization at all times,
      // to prevent problems when you (or someone else) decides to change
      // anything to the fields in the typedef.
      .var1 = VALUE1,
      .var2 = VALUE2,
      .var3 = VALUE3,
    };
    
    database_t the_database = initialvalues;
    

    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
  • altosackaltosack Posts: 132
    edited 2013-09-02 14:12
    Thanks, Jac.

    Actually, your way takes twice the memory and uses startup code, but you made me realize I can do it as follows:
    struct {
        uint32_t    var1;
        float       var2;
        uint16_t    var3;
    } EEPROM_vars = {
        .var1 = VALUE1;
        .var2 = VALUE2;
        .var3 = VALUE3;
    };
    #define var1 EEPROM_vars.var1
    #define var2 EEPROM_vars.var2
    #define var3 EEPROM_vars.var3
    

    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.
  • jac_goudsmitjac_goudsmit Posts: 418
    edited 2013-09-03 06:43
    Silly me, I didn't think of just initializing the database struct because I was focused on the question of whether it would be possible to get a pointer to the initialization values (so they can be accessed in the EEPROM).

    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
  • altosackaltosack Posts: 132
    edited 2013-09-03 18:49
    Hmm... It seems like this is more complicated than it needs to be. From what you've said, I think that C will allocate the space for the struct, which will be filled with zeros on the EEPROM, and then initialize it in memory from its own section, behind my back, once it starts up. This wastes space and initialization code; with assembly language, I can do the following in a .S file, and I know it will work:
    .section my_var_section
    .global var1, var2, var3
    var1:    .long    VALUE1
    var2:    .float   VALUE2
    var3:    .short   VALUE3
    

    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:
    extern long     var1;
    extern float    var2;
    extern uint16_t var3;
    
    // and Simple Libraries lets me write it back to the EEPROM like this:
    
    ee_putStr(&var1, (int) &var3 + (sizeof) var3 - (int) &var1, (int) &var1);
    
    // If the var_block was defined in C, it would simplify to:
    
    ee_putStr(&var_block, (sizeof) var_block, (int) &var_block);
    
    // If what I remember about spin carries over to propgcc, there might be an offset of, say, 
    // $10 between EEPROM and memory, but this is something fairly trivial that I'll research
    // once I get the block to be defined the way I want it to.
    

    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 ?
  • jazzedjazzed Posts: 11,803
    edited 2013-09-03 19:29
    You need to define a start address in high memory that you promise will never be over-written.
    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.
  • jac_goudsmitjac_goudsmit Posts: 418
    edited 2013-09-04 06:36
    I did a quick test using the following program:
    #include <stdio.h>
    #include <propeller.h>
    
    #define VALUE1 (0x12345678)
    #define VALUE2 (1.1)
    #define VALUE3 (0xABCD)
    
    typedef struct
    {
      uint32_t var1;
      float var2;
      uint16_t var3;
    
    } database_t;
    
    database_t the_database = 
    {
      // I recommend using field names in struct initialization at all times,
      // to prevent problems when you (or someone else) decides to change
      // anything to the fields in the typedef.
      .var1 = VALUE1,
      .var3 = VALUE3,
      .var2 = VALUE2, // Declaring initialization out of sequence as test
    };
    
    int main(void)
    {
        printf("The database is at %p\n", &the_database);
    
        return 0;
    }
    
    

    The generated Assembly code contains the following for the declaration of the_database:
    ...
      37                  .LFE0
      38                      .global    _the_database
      39                      .data
      40 0017 00               .balign    4
      41                  _the_database
      42 0018 78563412         long    305419896
      43 001c CDCC8C3F         long    1066192077
      44 0020 CDAB             word    -21555
      45 0022 0000             .zero    2
      83                  .Letext0
      84                      .file 2 "c:\\propgcc\\bin\\../lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/include/stdint
    

    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.
    If what I remember about spin carries over to propgcc, there might be an offset of, say, $10 between EEPROM and memory, but this is something fairly trivial that I'll research once I get the block to be defined the way I want it to.

    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:
    ee_putStr(&the_database, sizeof(the_database), (int)&the_database);
    

    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.
    ee_putStr(&var1, (int) &var3 + (sizeof) var3 - (int) &var1, (int) &var1);

    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 ;-)
  • jazzedjazzed Posts: 11,803
    edited 2013-09-04 09:06
    Nice code Jac.

    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.
  • David BetzDavid Betz Posts: 14,516
    edited 2013-09-04 14:07
    jazzed wrote: »
    Nice code Jac.

    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.
    Another potential problem is that this won't even come close to working in XMM mode since the EEPROM doesn't contain the program at all, just a small program that loads the XMM kernel and starts the program which resides in flash or SD card or somewhere else.
  • jac_goudsmitjac_goudsmit Posts: 418
    edited 2013-09-05 09:22
    jazzed wrote: »
    Nice code Jac.

    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
Sign In or Register to comment.