Shop OBEX P1 Docs P2 Docs Learn Events
Saving Variables to EEPROM — Parallax Forums

Saving Variables to EEPROM

eagletalontimeagletalontim Posts: 1,399
edited 2013-11-17 11:47 in Propeller 1
I am attempting to understand how to write variable values to an eeprom based on user input and retrieve them upon reboot. What I am not quite understanding is how to write these variables reliably whether they be a value of 0 or 15000. I have used this code before :
PUB saveval(value, address) | startTime, addr
  addr := (address * 4) + 32768
  if i2c.WriteByte( i2c#BootPin, i2c#EEPROM, addr, value)
    abort ' an error occured during the write
  startTime := cnt ' prepare to check for a timeout
  repeat while i2c.WriteWait( i2c#BootPin, i2c#EEPROM, addr)
    if cnt - startTime > clkfreq / 10
      abort ' waited more than a 1/10 second for the write to finish
  return

The problem with the above code is I am unsure the address to start at and what address to go to next. The address * 4 part is confusing to me as well. I also believe I had an issue with saving a "0" and retrieving that "0". If I am saving a BUNCH of variables of all sorts of values, I need to make sure I don't mix anything up or overwrite anything accidentally in the eeprom. The string of variables that will be sent from my vb program, parsed, and saved to the eeprom will look something like this:
1:1:1:0:0:0:0:1:15:0:7000:1000:500:200:13000:1:1:0:635:10 'and a bunch more....

The EEPROM I will be using is a 256K and I will be using part of it to hopefully "flash" the first 32K to upgrade firmware. This is a whole other step though. Got to start with the basics.

Comments

  • localrogerlocalroger Posts: 3,451
    edited 2013-11-10 15:44
    I see a few odd things going on here. First, values that can reach 15,000 need words (2 bytes) not byte storage. There are read/write word and long methods available in the i2c object, and in EEPROM they don't need to be aligned like they do in the Hub. You only need to multiply the address by 4 if the address is an index into a Long array or address in Cog RAM. It's important that multibyte writes not cross a page boundary; pages are as small as 32 bytes in 32K EEPROMs (24256 part number as the "256" is bits, not bytes). The largest I2C EEPROMs are 128 kilobytes and have 241024 part numbers, and generally have 256 byte pages.

    Use of writewait is wrong; the intent is that you just call writewait and when it returns, you're safe to do another write. You can also do a simple delay since all available EEPROMs complete a write cycle in less than 5 milliseconds, so you could do WAITCNT(CNT + CLKFREQ / 200) to get past the write cycle.

    If you are using a 32 Kbyte (24256) part there's no high EEPROM. You can, however, permanently save variables by just writing them to their original address, e.g. WRITELONG(device, @var, @var). When the Prop boots the modified value will be loaded by the boot loader. You can do this with both DAT and VAR type variables. But if you reload the program, VARS are reinitialized to zero and DATs to whatever they are declared in the source code.

    I find that the Spin I2C routines are pretty robust and once the system is debugged and proven free of hardware problems you really don't need to check for the error every time you read or write.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-10 16:22
    I was looking at the difference between longs and words and still don't quite understand what is going on there. An EEPROM consists of bits that are either 1 or 0 correct? Eight of these bits makes up a byte? That is about as far as I go. The code I posted above was copied from a thread I found a LONG time ago. It works for my home power monitor system, but this is a whole different project and needs to be right the first time. What I was thinking as a fail safe way to go about this is to save the variables based on location in the variable array... For example :
    variable[0] := 1
    variable[1] := 0
    variable[2] := 1000
    variable[3] := 15000
    variable[4] := 0
    variable[5] := 1
    variable[6] := 623
    .......
    

    Then, the index of each variable would be the "address" for the "addr := (address * 4) + 32768" code above. So, if the variable array coming from the VB program is too short, it will kick back an error, if it matches the length of what is on the prop, it will begin the save to the eeprom. Upon boot, the variables will then be read back from the eeprom and stored in the same sequence as they went in.
  • ozpropdevozpropdev Posts: 2,791
    edited 2013-11-10 18:12
    You can store your variables in the top of the standard 24LC256 Eeprom if your not using it all.
    This requires no code to load them on boot up.

    Use the following arrangement instead.
    CON
        variables = $7F00
    
    PUB something
        long[variables][0] := 0
        long[variables][1] := 10000
        etc...
    

    To save your "variables" just save the block from $7F00 to $7FFF
    Each variable uses 4 bytes(1 long) so 64 variables are available with 256 bytes.
    If you need more variable space lower the address ($7F00) to gain more space.
  • ozpropdevozpropdev Posts: 2,791
    edited 2013-11-10 18:31
    Does the i2c code have WriteLong? If so use it instead of WriteByte.
    CON
    variables = $7F00
    
    PUB saveval(value, address) | startTime, addr
      addr := (address * 4) + varaibles
      if i2c.WriteLong( i2c#BootPin, i2c#EEPROM, addr, value)
        abort ' an error occured during the write
      startTime := cnt ' prepare to check for a timeout
      repeat while i2c.WriteWait( i2c#BootPin, i2c#EEPROM, addr)
        if cnt - startTime > clkfreq / 10
          abort ' waited more than a 1/10 second for the write to finish
      return
    
  • UnsoundcodeUnsoundcode Posts: 1,530
    edited 2013-11-10 19:06
    Hi, the 256 Kb is 256 thousand bits or 32 thousand bytes (32 KB). It looks like you are using Mike Greens iC2 object, if it helps any I have also used Mikes object to write to EEPROM. If I remember correctly there is random write and sequential write and the iC2 objects "writepage" is the method I used. Write page sequentialy writes a page to the EEPROM, I wrote a page of 32 bytes. Anyway when you write a page sequentially you must not go beyond the page byte boundary or you will overwrite the begining of that same byte page. Using write page is extremely fast and the way I used it was to write the whole 32 KB in one transmission one page at a time. Once your data is onboard the EEPROM you can then access each individual address as needed. If you are using a 64 KB EEPROM and you want to use the high 32 KB for data you would offset the write start address by $8000 (32768), using larger EEPROMs requires slightly different addressing methods. The data to be transmitted was contained in a binary file, the larger values split into high and low bytes and placed in consecutive address's. This is the code I used, the serial data was collected in an array (Buffer[32]) 32 bytes at a time in a separate routine.
    eepromAddress:=0
    
         repeat j from 0 to 1023  
              RxArray(100,@Buffer,32) 
              
                i2c.WritePage(i2c#BootPin, i2c#EEPROM,eepromAddress, @buffer, 32)
                eepromAddress:=eepromAddress + 32
    

    Buffer[32] and eepromAddress were declared in the Var section.

    You don't have to write the whole EEPROM but do look at the data sheet for your EEPROM and the various options for writing sequentially or randomly.

    Jeff T.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-10 20:28
    ozpropdev wrote: »
    You can store your variables in the top of the standard 24LC256 Eeprom if your not using it all.
    This requires no code to load them on boot up.

    Use the following arrangement instead.
    CON
        variables = $7F00
    
    PUB something
        long[variables][0] := 0
        long[variables][1] := 10000
        etc...
    

    To save your "variables" just save the block from $7F00 to $7FFF
    Each variable uses 4 bytes(1 long) so 64 variables are available with 256 bytes.
    If you need more variable space lower the address ($7F00) to gain more space.

    If these values were set by the pre programmed code, how would the eeprom data be loaded on boot if the user has changed these settings via the VB program? Also, I am still not quite understanding the "LONG". I thought since the Prop is a 32 bit processor, a WORD would be 4 bytes. 32 bits = 4 sets of 8 bits which is comes out to 4 bytes. Maybe I am thinking about this too hard?

    It would be really nice to be able to save all variables that were named "User_Setting[x]" to the eeprom in one go and the prop automatically boots using the variables saved in the eeprom....not pre-programmed in by default.
  • JonnyMacJonnyMac Posts: 8,926
    edited 2013-11-10 21:47
    Propeller 101:

    Byte = 8 bits
    Word = 2 Bytes (16 bits)
    Long = 2 Words or 4 Bytes (32 bits)

    If you write to the EEPROM at the same address of your variable (you need to know if that variable is one, two, or four bytes), then on the next reset that last saved value will be loaded into RAM. I've done this with many programs.
  • ozpropdevozpropdev Posts: 2,791
    edited 2013-11-10 22:08
    After the eeprom is programmed the first time the variables will need to be loaded with
    your initial default values. Then save all variables to eeprom and from then on the
    the saved data will be restored every time the propeller boots.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-10 22:08
    Ok, makes a bit more sense now. Still not sure how to figure what value would be a Byte, Word, or Long. A "1" could be a Bit, Byte or a Word, and the value of 15000 could be a Word(?) and a long? Having to keep up with every single variable's type (Bit, Byte, Word, Long) seems to be pretty tedious just to save it to the eeprom and read it back.
  • ozpropdevozpropdev Posts: 2,791
    edited 2013-11-10 22:16
    Just make them all LONG's then your covered for any size value.
    If your values are never > 32767(signed) then used all WORD's.
  • JonnyMacJonnyMac Posts: 8,926
    edited 2013-11-10 22:28
    Ok, makes a bit more sense now. Still not sure how to figure what value would be a Byte, Word, or Long. A "1" could be a Bit, Byte or a Word, and the value of 15000 could be a Word(?) and a long? Having to keep up with every single variable's type (Bit, Byte, Word, Long) seems to be pretty tedious just to save it to the eeprom and read it back.

    How could you not figure it out? You have to declare a variable by type (byte, word, or long -- there is no bit type in Spin). A byte will hold values from 0 to 255, a word will hold values from 0 to 65535, a long will hold values between -2.14 billion to +2.14 billion. It's up to you. That said, as the long is the native type, I tend to use longs for simple variables.

    Let's say you have a byte variable called ticks and you want to save its present value to EEPROM. Using my 24xx512 object (which is attached), you'd do something like this:
    eeprom.wr_byte(@ticks, ticks)
    


    The first parameter, @ticks, tells the wr_byte method what address to write to. The method knows to write a single byte. The second parameter is the value to write. There are similar methods for words and longs, and master method (which is called by the others) that can write any value. There are also read methods, though you're not using them in your case.

    I developed my 24xx512 object for the EFX-TEK EZ-8+ controller which uses a 64K EEPROM; the lower 32K holds the program, the upper 32K holds the user sequences.

    And, yes, you can use the 24xx512 object with a 24LC256 EEPROM -- you just have to limit your addresses to $0000..$7FFF.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-16 21:29
    Ok, maybe I am missing something here, but how do I use the "variables" after they are defined?
    CON
      _CLKMODE = XTAL1 + PLL16X
      _XINFREQ = 5_000_000
    
      User_Setting = $7F00
    
    OBJ
      eeprom : "jm_24xx512"
    
    PUB Main
      ' LOAD VARIABLES IF NOT DEFINED
      if User_Setting[0] <> 0
        SetDefaultValues
        Save_To_EEPROM 
    
        ' do stuff with the values.
    
    PUB SetDefaultValues
        LONG [User_Setting][0] := 1
        LONG [User_Setting][1] := 1
        LONG [User_Setting][2] := 40
        LONG [User_Setting][3] := 6
        LONG [User_Setting][4] := 65
        LONG [User_Setting][5] := 20
        LONG [User_Setting][6] := 65
        LONG [User_Setting][7] := 22
        LONG [User_Setting][8] := 65
        LONG [User_Setting][9] := 23
        LONG [User_Setting][10] := 9
        LONG [User_Setting][11] := 40
    
    PUB Save_To_EEPROM
      eeprom.start(%000)
      eeprom.wr_byte(@User_Setting, User_Setting)
      return
    

    When I try to program this, I get an "Expected End of Line" error on this " if User_Setting[0] <> 0 ". I guess I can't simply call each value by "User_Setting[X]"?
  • Mike GMike G Posts: 2,702
    edited 2013-11-17 05:01
    User_Setting = $7F00 is defined as a constant but is used as an array.
  • Mike GMike G Posts: 2,702
    edited 2013-11-17 05:35
    Here's a demo.
    CON
      _CLKMODE = XTAL1 + PLL16X
      _XINFREQ = 5_000_000
    
      USER_SETTINGS_MAX_LEN = 12
    
    VAR
      byte UserSetting[USER_SETTINGS_MAX_LEN]
      byte UserSettingidx
      
    OBJ
      pst           : "Parallax Serial Terminal" 
      eeprom : "jm_24xx512"
    
    PUB Main
      'Init Objects
      pst.Start(115_200)
      pause(1000)
      eeprom.start(%000)
    
    
      UserSettingidx := 0
      SetDefaultValues
    
      pst.str(string("------------------------",  13, "Initialized UserSetting", 13, "------------------------", 13)) 
      PrintArray
      
      ' LOAD VARIABLES IF NOT DEFINED
      if UserSettingidx == USER_SETTINGS_MAX_LEN
        pst.str(string("------------------------",  13, "Write to EEPROM", 13, "------------------------", 13, 13))
        SaveToEEPROM
    
      ClearUserSettings
      pst.str(string("------------------------",  13, "Cleared UserSetting", 13, "------------------------", 13)) 
      PrintClearedArray
    
      LoadFromEEPROM
      pst.str(string("------------------------",  13, "Loaded From EEPROM", 13, "------------------------", 13)) 
      PrintArray
    
      
    
    
    PUB SetDefaultValues
        UserSetting[UserSettingidx++] := 1
        UserSetting[UserSettingidx++] := 1
        UserSetting[UserSettingidx++] := 40
        UserSetting[UserSettingidx++] := 6
        UserSetting[UserSettingidx++] := 65
        UserSetting[UserSettingidx++] := 20
        UserSetting[UserSettingidx++] := 65
        UserSetting[UserSettingidx++] := 22
        UserSetting[UserSettingidx++] := 65
        UserSetting[UserSettingidx++] := 23
        UserSetting[UserSettingidx++] := 9
        UserSetting[UserSettingidx++] := 40
    
    PUB ClearUserSettings | i
      repeat i from 0 to UserSettingidx - 1
        UserSetting[i] := 0
      UserSettingidx := 0  
    
    PUB SaveToEEPROM | i
      repeat i from 0 to UserSettingidx - 1
        eeprom.wr_byte(@UserSetting[i], UserSetting[i])
      return
    
    PUB LoadFromEEPROM 
      repeat UserSettingidx from 0 to USER_SETTINGS_MAX_LEN - 1
        UserSetting[UserSettingidx] := eeprom.rd_byte(@UserSetting[UserSettingidx])
      return
    
    PRI PrintClearedArray  | i
      pst.str(string("Array Length: "))
      pst.dec(UserSettingidx)
      pst.char(13)
    
      repeat i from 0 to USER_SETTINGS_MAX_LEN - 1
        pst.str(string("array["))
        pst.dec(i)
        pst.str(string("] = "))
        pst.dec(UserSetting[i])
        pst.char(13)
    
      pst.char(13)
    
    PRI PrintArray  | i
      pst.str(string("Array Length: "))
      pst.dec(UserSettingidx)
      pst.char(13)
    
      if(UserSettingidx > 0)
        repeat i from 0 to UserSettingidx - 1
          pst.str(string("array["))
          pst.dec(i)
          pst.str(string("] = "))
          pst.dec(UserSetting[i])
          pst.char(13)
    
      pst.char(13)
    
    PRI pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return
    
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-17 08:13
    I thought if I stored values to a specific area ($7F00), no code is required to load these variables on boot? If this is the case, how does this load saved values other than what is "hard coded" in :
    PUB Main
      'Init Objects
      pst.Start(115_200)
      pause(1000)
      eeprom.start(%000)
    
    
      UserSettingidx := 0  ' Sets IDX to 0 on every reboot
      SetDefaultValues ' Loads "Hard Coded" values every reboot
    
      pst.str(string("------------------------",  13, "Initialized UserSetting", 13, "------------------------", 13)) 
      PrintArray
      
      ' LOAD VARIABLES IF NOT DEFINED
      if UserSettingidx == USER_SETTINGS_MAX_LEN  ' Always going to return FALSE so default values will always be written to EEPROM on reboot.
        pst.str(string("------------------------",  13, "Write to EEPROM", 13, "------------------------", 13, 13))
        SaveToEEPROM
    .........
    
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-17 09:08
    Ok, I just ended up using the propeller Eeprom object and parts of the Brownout Detector object to get what I need done. I was hoping to not have to use code to load the initial values, but I guess that is the way I have to go.
  • Mike GreenMike Green Posts: 23,101
    edited 2013-11-17 09:41
    Re: Post #16. If you store the values to the locations in EEPROM where the variables are loaded from, they will be initialized to the saved values (rather than the default of zero). If you're using JonnyMac's object and you have a declaration like

    VAR long variable1

    then you save an initial value to it by doing

    eeprom.wr_long(@variable1,initialValue)
    eeprom.wait

    where initialValue is the value or expression you want to save. If you're using a 16-bit word variable, then you use wr_word. If you have an 8-bit byte variable, then you use wr_byte.
  • JonnyMacJonnyMac Posts: 8,926
    edited 2013-11-17 09:53
    I was hoping to not have to use code to load the initial values, but I guess that is the way I have to go.

    The first run after a download is the only time you have to deal with presetting values. In one project, I created a variable called initialized, and after the objects were loaded I did something like this:
    [B]ifnot [/B](initialized)
        set_program_defaults
        ee.wr_byte(@initialized, true)
    

    How it works: On the first run after startup all global variables will be zero, including initialized which will cause the ifnot clause to be true. I set the program variables to their default values (that method also writes them to EEPROM), and then change the EE value of initialized to true. On the next reset the value of initialized will cause the ifnot clause to fail -- the only time this ifnot clause runs is on the first execution after a download.

    Something that you need to remember: Every time you update a RAM variable you have to update the EEPROM, too. If you don't that change will be lost on a reset/power-down.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-17 09:58
    The variables will only be changed when change via my VB program. I needed those variables to be a default value on first boot, then if the changed in the VB program and saved (through the prop), the next boot(s) will load the new values. I think with the Propeller EEPROM object, I have been able to get this working. I still have to load the eeprom values with 1 line of code, but I think that will work for what I am doing.
  • JonnyMacJonnyMac Posts: 8,926
    edited 2013-11-17 10:04
    I needed those variables to be a default value on first boot

    You cannot default common variable values on the first boot (see post #19). You could setup a DAT table with values, and those can be modified on the fly. Still... you must save the values back to EEPROM when you change them. You were almost there in post #13 -- you made things a little harder for yourself than you needed to.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2013-11-17 10:08
    Oh ok. I did set variables in the DAT and used the same setup as the Brownout Detector backup to eeprom object in the OBEX. The first boot was still 0, but after that, everything has been working perfect. Since I will have to test each board before it will be put into use, I still have to power it up and run it through it's tests. That should load the EEPROM with the values that I need as long as I backup the data before reboot. This whole "Digital" electronics thing is a little tough for me, but I think I am understanding it slowly. Thank you all for all the help so far!
  • JonnyMacJonnyMac Posts: 8,926
    edited 2013-11-17 11:47
    If you're going to be booting and testing each board, don't bother having the code load defaults -- do that from your VB program. Easy-peasy.
Sign In or Register to comment.