Teensy EEPROM location -- is it static regardless of program data?

Status
Not open for further replies.

mdion

Member
Hello all,

I have a small lab instrument based on the Teensy 3.1/3.2, and I store calibration constants in EEPROM. Sometimes I will email .hex files to other people using my instrument in order to update the programming via Teensy.exe. On more than one occasion, I have received reports of calibration data being overwritten. I am trying to figure out if it's something in my code, or if the Teensy assigns the EEPROM location based on program size or other factors. Does anyone know the answer to this?
 
Not sure if this is responsive?

EEPROM data survives normal Teensy reprogramming - it exists in a fixed way and location based on the device, it does not change in response to user programming or other. Unless the sketch actively addresses or changes the values in EEPROM area they should retain their values.
 
Normally EEPROM data is preserved when uploading programs.

However, Teensy 3.5 and 3.6 have a hidden feature where holding the button for 15 seconds (or between 13 to 17 seconds) causes a full chip erase. Eventually this feature will be ported to Teensy 3.2 & LC with an update, but today it's only on 3.5 & 3.6. The full chip erase clears the EEPROM data.
 
Thanks for the quick answers!

So is it correct to say that the Teensy 3.2/3.1 has a dedicated EEPROM hardware module, or is it "simulated" (written to an dedicated portion of the program flash)? I ask because I am also working on a project with a PIC32, and there is no dedicated EEPROM.
 
Both, sort of...

There is a dedicated hardware module, which simulates EEPROM using Flash. The chip has a dedicated 32K bank of flash memory, completely separate from the 256K flash for program storage. There's also a dedicated 2K RAM. The hardware module, which Freescale (now NXP) called "FlexNVM" uses that 32K of Flash plus the 2K of RAM to emulate 2K of EEPROM. Your EEPROM accesses are done to the region of memory where the 2K RAM is located. Reads come directly from the RAM, even if the flash is busy. Writes do to the RAM, and the hardware automatically detects the write and does the work to back it up into the 32K flash, using a wear leveling algorithm implemented completely in the hardware. Because it's all done in hardware and uses a dedicated block of flash, your program keeps running and only has to wait if you do *another* write to EEPROM before the first completes.

This feature exists on Teensy 3.0, 3.1, 3.2, 3.5 & 3.6.

On Teensy LC the EEPROM is emulated by reserving a 2K chunk of the program flash. Software implements the wear leveling and writing. Your program is blocked from running during the write.

The old 8 bit Teensy 2.0 & Teensy++ 2.0 boards have actual EEPROM memory.
 
Fascinating! So then there's no way that the hardware keeping track of the bad-block table loses track of where the right addresses are during reprogramming, right?
 
Correct, more or less like that. The settings actually get changed, but when your code calls the EEPROM functions the FlexNVM gets configured back to EEPROM emulation. Then the first access to the FlexNVM is with all the memory exactly as it had been when your prior program ran.

How exactly it does the wear leveling and address mapping isn't 100% explained by Freescale's manual. But if you read the info, it appears to be using some sort of fancy address translation rather than a table.
 
Since the EEPROM is simulated in Flash, what is the approximate write cycle lifetime for the EEPROM on the 3.6? My application is used in an amateur radio application where I need to store frequency data fairly often, although I can back it off.
 
Figure 11 on page 36 in this datasheet has the best info.

https://www.nxp.com/docs/en/data-sheet/K66P144M180SF5V2.pdf

On Teensy 3.5 & 3.6 we're running running ratio of 64 for the first 1K of eeprom, and a ratio of 21.3 for the remaining 3K.

So from the chart, using Teensyduino's default, the first 1K has endurance of 300,000 bytes writes per location, or 600,000 word writes.

If you want higher endurance, there are tunable parameters in eeprom.c. You can double the endurance by changing EEPARTITION to use all 256K of the dataflash, leaving none left for non-eeprom use. You can edit EEESPLIT if you want all the eeprom to be equal, or a smaller portion to have higher endurance at the expense of the rest. The default split allocates half of the 128K to the first 25% of the eeprom.

You can get MUCH MORE endurance by editing EEPROM_MAX to a smaller number. If you go for only 256 bytes of emulated eeprom and set the split for all created equal, and all 256K flash used, then you're wear leveling 256 bytes over 256K for a ratio of 1024, which the chart says is supposed to give an endurance of 10 million writes.

If you edit eeprom.c, you need to be careful to keep it updated with your changes, as running the Teensyduino installer will overwrite it.

As you can see in the graph, writing 16 or 32 bits is handled twice as efficiently as only 8 bit writes, so that's the other easy way to double your endurance.
 
Paul: I have been tracking a "bug" for three days now (I'm not that good) and it concerns writing/reading EEPROM. I have a union:

Code:
union  {
  byte array[6];
  int num;
  long bigNum;
  float fp;
} myU;
that I used to shove values set either as defaults or by the user into EEPROM for retrieval on startup. I have this code:
Code:
  myU.num = cwSidetone;                           // CENTERFREQCW Default sidetone and center frequency for CW narrow = 700
  EEPROM.write(offset++, myU.array[0]);
  EEPROM.write(offset++, myU.array[1]);
where cwSidetone is an int with the value 700 (0x02BC). At this stage, offset in an int data type with the value 421. On startup, I read the EEPROM map with:

Code:
  myU.array[0] = EEPROM.read(offset++);                   // Default center frequency for narrow CW = 700
  myU.array[1] = EEPROM.read(offset++);
  //myU.array[2] = 0x00;                                  // NOTE: Commented out...
  cwSidetone   = myU.num;
  cwCenterFrequency = myU.num;                            // Set to center freq of filter, too.
  Serial.print("cwSidetone = ");
  Serial.println(cwSidetone, HEX);
  Serial.print("*******************in Read()  myU.num = ");
  Serial.print(myU.num);
  Serial.print("    array[0] = ");
  Serial.print(myU.array[0], HEX);
  Serial.print("    array[1] = ");
  Serial.println(myU.array[1], HEX);
The offset is correct. If I leave the commented-out assignment for element 2 of the array, the value is 0xD902BC (14222012) which is in Hz and, so, is probably higher than most people can hear. If I uncomment the line that zeroes out the 2nd element of the union member array, the correct value is returned. I tried to reproduce this with a short program, but couldn't. My current program compiles to 180476 bytes of flash memory, and 30224 bytes of SRAM on a Teensy 3.6. I do know that the 0xD9 byte is an artifact of an earlier conversion using an unsigned long for a ham radio frequency. Still, I am puzzled why this bug works the way it does. Anyone: Any thoughts.
 
Note with the EEPROM functions, you no longer have to do a single byte read/write. You should be able to read/write the whole union due to the use of templates.
 
Note with the EEPROM functions, you no longer have to do a single byte read/write. You should be able to read/write the whole union due to the use of templates.

Michael,
That is one step too fast, could you give an example?
I only see byte access
Code:
    uint8_t read( int idx )              { return EERef( idx ); }
    void write( int idx, uint8_t val )   { (EERef( idx )) = val; }
    void update( int idx, uint8_t val )  { EERef( idx ).update( val ); }
or miss I something?

Edit:
OK, defragster, it is a couple of lines below the quoted text
But honestly, I do not care about Arduino 'standards'

Michael, sorry about being too quick
 
Michael: Actually, I'm just using the union to abstract from the Endian problem, if any, across platforms. Another discovery today: using this to work with the same union as above:

Code:
  myU.array[0] = EEPROM.read(offset++);                  // Screen calibration constants
  myU.array[1] = EEPROM.read(offset++);
  calibratedXLo = myU.num;

where calibratedXLo is the low X pixel value for a touch screen. I read three more similar coordinates, all of which pertain to a 800x480 display. I'm getting values in the millions. However, if I add one line:


Code:
  memset(myU.array, 0, sizeof(myU.array));
  myU.array[0] = EEPROM.read(offset++);                  // Screen calibration constants
  myU.array[1] = EEPROM.read(offset++);
  calibratedXLo = myU.num;

I get exactly the correct test values. Since no value needs more than two bytes, and the fact that I only do the memset() call once at the top of the four calls, I am perplexed as to what's going on. Again, I cannot duplicate it with a small test program.
 
says above 'offset is correct' - but no posted code shows where that is set to a known initial or controlled value.
 
I have a list of symbolic constants for all of the variables that are kept in EEPROM. The "factory settings" start at offset = 200 while the "user settings" are offset = 400. The screen calibration offsets begin at offset + 31, so for the factory values they would be at 231 while it would be 431 for the user settings. (The user has the options to do a "factory reset" if they choose.) I did not show the code as it's pretty lengthy, as you might expect. The radio has two VFO's and the artifact for the sidetone problem is left over from that. Still, given the way a union works and that there is an int defined in the union, earlier operations on the union shouldn't matter as long as I don't overflow the memory requirements for the largest item in the union, which I do not. I get a tad bit uncomfortable when I find a work-around, but don't understand the bug that caused it in the first place.
 
Always comforting to know you got to the source of the problem. The memset() will make some minor difference in compilation - but shouldn't affect that variable or the stuff around it - unless something got optimized away?

Is offset updated in or used in an interrupt? Again so many things could explain this that can't be seen in part. If interrupts are involved then 'offset' needs to be declared as volatile. Otherwise perhaps a change to 'debug' or alternate level of optimization might give a clue.

if you can reduce it to a simple sample showing the issue that would allow others to view. If a simple sample can't be broken … something removed was the problem.

Something may be trashing the memory for 'offset' - make a copy before use and then compare after use to see it equal to expected offset++ value, and make sure it is in the expected range of values. Doing that could change the behavior or show the nature of the error if it involves offset.
 
defragster: Thanks for noodling my problem. I do have one interrupt in the code which works in conjunction with a rotary encoder, but that interrupt does not come into play with anything in the EEPROM memory space. Also, the relatively short ISR only has one global variable and that is defined as volatile. I have used "scaffolding" code for my debug statements, like:

Code:
#ifdef DEBUG
Serial.print("calibratedXLo = ");
Serial.prinln(calibratedXLo);
#endif

so when I comment out the #define for DEBUG, the debug statements are not compiled into the executable. No change in performance, however. I have inspected offset and it is consistently correct.

Alas, I am not able to reproduce the behavior in a short "postable" sketch. As I mentioned earlier, this is a fairly large program--the largest I've worked on for a µC--that controls this 5" TFT touch-screen display:

FullButtonScreen.jpg

I'm starting to think that either the stack or the instruction pointer is getting trashed at some point, but I don't have the tools or talent to determine what the root cause is. I think your idea of checking offset going into the method (which I've done) but also coming out of the method is a good idea. That is, I'll add a debug print on offset at the end of the EEPROM list to see if it's what I expect it to be. If not, that's an important clue...thanks for that idea!
 
Status
Not open for further replies.
Back
Top