Shop OBEX P1 Docs P2 Docs Learn Events
How to Save variables to onboard EEPROM on Proto Board — Parallax Forums

How to Save variables to onboard EEPROM on Proto Board

eagletalontimeagletalontim Posts: 1,399
edited 2012-12-02 16:32 in Propeller 1
This is one area I get completely lost at and I hope to understand this a little better. I have one of the Prop Proto Boards with the standard EEPROM on it. I don't know what size it is, but I would like to use it to store roughly 20 to 30 numerical values all less than 255. The thing I don't understand is how EEPROMs work beside's their name (Electrically Erasable Programmable Read Only Memory). How can I tell where at on the EEPROM I am writing to or reading from? I have read about starting writing / reading values from $8000. What does that mean? Is the next writable area $8001? Thanks for any help!

Comments

  • JonnyMacJonnyMac Posts: 9,108
    edited 2012-12-01 18:12
    The EEPROM on your Propeller board uses I2C so have a look in the Object Exchange for I2C objects -- most of them have built-in features for writing to and reading from an EEPROM. If you have a 64 EEPROM it's a good idea to use $8000 or higher as these values will be preserved when you reprogram the board.
  • Clive WakehamClive Wakeham Posts: 152
    edited 2012-12-01 18:25
    The EEPROM's used with the Propeller uses the I2C interface, which is a two wire communications standard.

    In regard to the location on the EEPROM you are reading from/ or writing to, then your program needs to keep that location.

    The first 32K of the EEPROM is transferred to the main RAM of the Prop chip after Boot-up so any location after $8000 is usable.

    There are several objects in the OBEX for communicating via the I2C standard.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-01 18:36
    I do have the Basic_I2C_Driver included in my program and I had it working at one point just by copying and pasting code from others. I had no idea what I was doing when it came to the eepromAddress so before I messed up something or ran out of space, I removed that function. In order to save values to the EEPROM, I need to know where they are being saved. If I start at $8000, how do I store the next value to the next "spot" on the EEPROM?

    Basically what I am trying to do is take my menu system "ID" numbers :

    MenuB]0[/B := string("Menu Item 1")
    MenuB]1[/B := string("Menu Item 2")

    And use them to store values on the EEPROM in specific spots for easy reading at a future time.

    Example :

    Save menu item 0 at address $8000
    Save menu item 1 at address "whatever is after $8000"

    Or :

    Write Menu Item Value to EEPROM at address $8000 + Menu Item ID
  • Clive WakehamClive Wakeham Posts: 152
    edited 2012-12-01 19:18
    Are you trying to save the different strings to the EEPROM or just individual byte values?
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-01 19:27
    The values that will be stored to the eeprom will be numbers that will be less than 255. I know if they are over 255, it would require more than one space on the eeprom.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2012-12-01 19:44
    There's an "EEPROM Datalogging" thread listed in the PEK sticky in the Propeller forum.

    Prop Proto Boards have 64K EEPROMs.

    Some early QuickStart boards only have 32K EEPROMs. I wrote a program that will tell you if your EEPROM is 64K or 32K.
  • msrobotsmsrobots Posts: 3,709
    edited 2012-12-01 19:44
    I try to keep things simple.

    Most P1 boards have a 64K byte eeprom. the first 32K byte are used to boot the chip and overwritten by programming. The second 32K are usually unused and not overwritten by programming the prop.

    Now the adress $8000 (=32768 decimal or 32 X 1024 aka 32K) is the adress of the first byte of the second 32K.

    so $8000 is the first byte, $8001 the next.

    Now - it depense as usual what you want to archive.

    When you want to save some settings AND KEEP them even if you reprogram your prop then you shoud use the top half and start with adresses over $7FFF (32767).

    On the other hand, if you want some settings saved and not care about beeing lost after reprogramming then You can use the lower part as well. But keep in mind this is basically what you load after reeboot. And the content of the lower 32K gets copyed into hub.

    Now things get funny.

    Imagine some program wher you have some variables in a DAT section.

    Say - for example BackgroundColor of your screen. And you want the enduser to change that color. So somwhere in your code you give the user a choice between - say - 5 different colors. And save this into your long BGcolor (in HUB).

    Everything is nice but after reboot the setting is gone and the old backgroundColor is used.

    Here comes the use of writing the lower part of the eeprom. Since the HUB-ram contains a copy of the lower 32K of your eeprom the adresses used are the same in eeprom and HUB-ram.

    so with a I2C.writeLong(@BGcolor,BGcolor) you can write the content of your long BGcolor into the eeprom at the adress of BGcolor. HUB and eeprom adress of BGcolor are the same!

    after reboot prop reads the eeprom into hub and your long BGcolor has the new value, no the one you save with the prop-tool into it while programming, but the one choosen by the enduser.

    As for your Menu-Items ... they are strings, aka usually more then a byte long and ending with a byte 0 (zero).

    You will need to track the adresses by counting bytes.

    Enjoy!

    Mike
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-01 19:51
    So since I am saving Byte values, can I use $8000 through $8030?
  • msrobotsmsrobots Posts: 3,709
    edited 2012-12-01 20:09
    yes you can!

    Enjoy!

    Mike
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-01 20:12
    Where does the $7FFF come from when addressing an EEPROM address? I see that value when I press F8 in the Prop Tool.
  • msrobotsmsrobots Posts: 3,709
    edited 2012-12-01 20:15
    it is hexadecimal 32767 or $8000 - 1

    Enjoy!

    Mike
  • msrobotsmsrobots Posts: 3,709
    edited 2012-12-01 20:17
    So be aware after $8009 the next number is $800A not $8010 ... the 'numbers are going from 0 to F not 0 to 9

    Enjoy!

    Mike
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-01 20:43
    oh wow. this got even more confusing. How can I associate lets say menu item 11 with it's stored eeprom value by calling it through a function something like this :

    i2c.ReadPage(i2c#BootPin, i2c#EEPROM, $8000 + value of Menu Item ID number, @buffer, 32)

    I need to make this as "flexible" as I can incase I add more menu options at a later date.
  • msrobotsmsrobots Posts: 3,709
    edited 2012-12-01 21:07
    Dont get confused with those hex-numbers. The way you wrote it is just fine.

    You simply add a offset in decimal for each string

    Since you have enough space in the eeprom build a Table with a common lengh for all strings.

    in your example your buffer is 32 bytes long.

    so start the first string at $8000 + 0
    the next one at $8000 + 32
    the next at $8000 + 64 and so on

    then i2c.ReadPage(i2c#BootPin, i2c#EEPROM, $8000 + (value of Menu Item ID number) * 32, @buffer, 32)

    will give you the right adress

    Enjoy!

    Mike
  • JonnyMacJonnyMac Posts: 9,108
    edited 2012-12-01 22:30
    This stuff is in fact a little harder than you want it to be, but it can be concurred. Read an EEPROM data sheet so you know how it works -- this will be important as you're storing strings of bytes.

    From a practical standpoint, you may want to limit your strings to 15 characters. With the 0 terminator this is 16 bytes which works nicely with EEPROM page sizes (read the docs). Knowing that each string will occupy no more than 16 bytes you can store them in memory with an index -- just multply the index by 16 and add this to the base (probably $8000). With the offset address you write the number of bytes in the string plus one (you must save the terminator!). To read back, simply read from the indexed address until you hit zero. I've done this and it does work. Again, the sticking point can be the page size; you don't want to write past a page boundary, this will cause big problems and corrupted data.
  • Mike GMike G Posts: 2,702
    edited 2012-12-01 23:15
    A DAT section writes to EEPROM (F11) and has the added benefit of encapsulating menu strings in an object. The following is a simple indexed abstract structure where the string length can vary.
    CON
      _clkmode = xtal1 + pll16x     
      _xinfreq = 5_000_000
    
    VAR
    
    DAT
      mi1   byte  "Star Wars",0
      mi2   byte  "The Hangover",0
      mi3   byte  "Christmas Vacation",0
      mi4   byte  "One Flew Over the Cuckoo's Nest",0
      mi5   byte  "The Good, the Bad and the Ugly",0
      mis   long  @mi1, @mi2, @mi3, @mi4, @mi5
    
    OBJ
      pst           : "Parallax Serial Terminal"
    
      
    PUB Main | i
    
      pst.Start(115_200)
      pause(500)
    
      repeat i from 0 to 4
        pst.str(@@mis[i])
        pst.char(13)
    
    
    PRI pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return  
    

    Managing menu items during run time (state) is a different story and takes a fair amount of planning. If the menu is inside an object the object itself can save the current state of the menu as well.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-02 09:41
    I have 2 functions I am stuck on that seem to hang everything. I can't seem to read from the EEPROM on boot. The whole code of my program cannot be posted for certain reasons
    CON
      _CLKMODE = XTAL1 + PLL16X
      _XINFREQ = 5_000_000
      upbutton = 0
      downbutton = 1
      menuItems = 11
    
    VAR
      LONG cogon, cog
      LONG LCDstack[100]
      LONG variableStack[200]
      LONG lastWritten
      LONG adcsample
      LONG Menu[255]
      BYTE Stored[255]
      BYTE EVal1
    
      BYTE restartMain
    
    OBJ
      adc : "ADC"
      i2c : "Basic_I2C_Driver"
      lcd : "Serial_Lcd"
      num : "simple_numbers"
    
    PUB Main | btnpresstime, iloop, eloaded, evaltotal
      btnpresstime := 0
      restartMain := 0
      eloaded := 0
      evaltotal := 0
    
      repeat iloop from 0 to menuItems Step 1
        EVal1 := ReadEEPROM(iloop)  ' HANGS HERE ***************************
        Stored[iloop] := EVal1
        evaltotal := evaltotal + EVal1
    
    PUB saveval(value, address) | startTime, addr
      addr := 32767 + (address * 32)
      if i2c.WritePage(i2c#BootPin, i2c#EEPROM, @addr, @value, 32)
        abort ' an error occured during the write
      startTime := cnt ' prepare to check for a timeout
      repeat while i2c.WriteWait(i2c#BootPin, i2c#EEPROM, @value)
        if cnt - startTime > clkfreq / 10
          abort ' waited more than a 1/10 second for the write to finish
      return
    
    PUB ReadEEPROM(address) | tmp, addr
      addr := 32767 + (address * 32)
      tmp := i2c.ReadPage(i2c#BootPin, i2c#EEPROM, @addr, @tmp, 32)
      return tmp
    
  • Mike GreenMike Green Posts: 23,101
    edited 2012-12-02 10:10
    I don't understand why you've written your calls to .WritePage and .ReadPage the way you have. They're all wrong. For one, you've specified a length of 32, yet your variables are longs (4 bytes). You're also passing the address of the address variable. The calls are:

    i2c.ReadPage( i2c#BootPin, i2c#EEPROM, <EEPROM address>, <HUB address>, <# bytes to read>)
    i2c.WritePage( i2c#BootPin, i2c#EEPROM, <EEPROM address>, <HUB address>, <# bytes to write>)
    i2c.WriteWait( i2c#BootPin, i2c#EEPROM, <EEPROM address>)

    Instead of using ReadEEPROM(iloop), try using i2c.ReadByte( i2c#BootPin, i2c#EEPROM, (iloop * 32) + 32768)

    Instead of using i2c.WritePage( i2c#BootPin, i2c#EEPROM, @addr, @value, 32), try using i2c.WriteByte( i2c#BootPin, i2c#EEPROM, addr, value)
    Also change i2c.WriteWait( i2c#BootPin, i2c#EEPROM, @value) to i2c.WriteWait( i2c#BootPin, i2c#EEPROM, addr)
    You'll have to change addr := 32767 + (address * 32) to addr := (address * 32) + 32768

    Note that this will write the saved value to the EEPROM one byte at a time ... about 5ms per byte. You could go back to using .WritePage to take advantage of paged writes, but you'd have to pass in the address of the whole array of bytes to be written and the count of the number of bytes.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-02 10:30
    Ok, i just changed it to this :
      repeat iloop from 0 to menuItems Step 1
        EVal1 := i2c.ReadByte(i2c#BootPin, i2c#EEPROM, (iloop * 32) + 32768)
        Stored[iloop] := EVal1
        evaltotal := evaltotal + EVal1
    

    It still hangs for some reason while pulling 11 different variables from the EEPROM. It does appear to run code past this area as it loads, but nothing else works until the EEPROM is done loading. Is there a way to show a "Loading...." while the EEPROM reads everything?
  • Mike GreenMike Green Posts: 23,101
    edited 2012-12-02 10:58
    There's nothing in .ReadByte that can hang. It doesn't wait for anything ... just goes through the necessary steps. Each call reads one byte from the EEPROM or returns a -1 to indicate that the read failed. I haven't done any speed tests, but it should take under 1ms per byte.

    I'd attach an LED or two to spare I/O pins to use as test indicators (like when the data is being read from the EEPROM). You may not be able to see a flash because the EEPROM read time is so short.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-02 11:06
    I tried the LED read display and it does work correctly. I think it has to do with my program and how it works. I use a Cog to update the screen when in "Active Mode" and I also have a "Program Mode" which has to stop the "Active Mode" cog and start the Program Mode cog. I think I am making this more complicated than it is.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2012-12-02 11:23
    What size is the "Stored" array. It should be at least one larger than the value "menuItems".
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-02 11:39
    Both are [0] through [11] to make calling for each item easier in the program.
  • Mike GreenMike Green Posts: 23,101
    edited 2012-12-02 11:40
    Unfortunately it's often the case that, when someone can't show parts of their code for proprietary reasons, that's often the place where problems hide. I'm a big proponent of simplicity in coding. Use constant names for constants other than trivial ones. Declare arrays using named constants or expressions using them for sizes. Use meaningful but not long variable names. Write loops and conditional statements so their scope fits on a page moving inner pieces into functional subroutines so you can take in the meaning and function of the whole thing at once. Make sure the boundary conditions are dealt with. When you're debugging, you start with trying to verify that your assumptions are correct, particularly at the lowest levels, so you can trust that the low level routines work as specified, particularly at the boundaries.
  • Mike GMike G Posts: 2,702
    edited 2012-12-02 13:15
    Why do want to read/write menu items to EEPROM? Are you trying to save the state of a menu when the power is off?

    I understand the title of this thread is How to Save variables to onboard EEPROM on Proto Board but I think that you are using the wrong approach to solve a problem. The whole EEPROM thing is only confusing the issue.
  • eagletalontimeagletalontim Posts: 1,399
    edited 2012-12-02 13:39
    I am not storing the actual menu descriptions in the eeprom, i am just storing the value that relates to that menu item. Basically like this :

    MenuB]0[/B := string("What is less than 2") ' This will be shown on line 1 of the display

    StoredB]0[/B := 1 ' This is the value for Menu[0] which will be stored in the eeprom at addr := (Menu ID number * 32) + 32768
    EDIT * : This variable is changed by the user and stored in the eeprom when they exit the menu or switch to the next menu item.

    Same would go for Menu[1]. the value for Menu [1] would be stored in the variable Stored[1] which would be stored on the eeprom in the next corresponding address number.
  • Mike GMike G Posts: 2,702
    edited 2012-12-02 16:32
    That's what I thought you were doing. It not how I would deal with menu items. There are too many moving parts and the relationship between EEPROM and logic is too loose.

    I suggest encapsulating the logic in a spin file (object). The following code snippet can display any of 5 menu items and keep track of the currently selected menu item.
    CON
      _clkmode = xtal1 + pll16x     
      _xinfreq = 5_000_000
    
      MAX_MOVIE_MENU_ITMES          = 5
    VAR
    
    DAT
      miSel   byte  $FF
      mi1     byte  "Star Wars",0
      mi2     byte  "The Hangover",0
      mi3     byte  "Christmas Vacation",0
      mi4     byte  "One Flew Over the Cuckoo's Nest",0
      mi5     byte  "The Good, the Bad and the Ugly",0
      mis     long  @mi1, @mi2, @mi3, @mi4, @mi5
    
      null  long  $00
    
    OBJ
      pst           : "Parallax Serial Terminal"
    
      
    PUB Main | i, ptr
    
      pst.Start(115_200)
      pause(500)
    
      repeat i from 0 to MAX_MOVIE_MENU_ITMES-1
        pst.str(SelectMenuByIndex(i))
        pst.str(string(" | Selected Index = "))
        pst.dec(GetMenuItemSelected)
        pst.char(13)
    
      'Detect index out of bounds
      ptr :=  SelectMenuByIndex(10)
      if(ptr == @null)
        pst.str(string(13, "Index is out of bounds", 13))
    
      'Last slected index
      pst.str(string("Last Selected Index = "))
      pst.dec(GetMenuItemSelected)
      pst.char(13)
    
    
    PUB SelectMenuByIndex(idx)
      if(idx > MAX_MOVIE_MENU_ITMES-1)
        return @null
        
      miSel := idx  
      return @@mis[idx]
    
    PUB GetMenuItemSelected
      return miSel
    
    PRI pause(Duration)  
      waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
      return
    
Sign In or Register to comment.