2

In my limited testing it appears that the EEPROM in the Wemos D1 is not initialized to 255 like the specs say is the case for AtMega based Arduinos. In fact, the values look to be more-or-less random. Presumably this is because the ESP8266 doesn't actually contain EEPROM, but instead emulates it using flash. Is there a way to initialize the EEPROM region when flashing?

(This is pretty much the same question as here, which has not been answered.) Since I need only a few bytes of data, I'd prefer not to have to use the SPIFS.

Update:

Use case: I'm using the WiFiManager library to select the network to connect a Wemos D1 mini to the web to upload data to Thingspeak. I'd like to use WiFiManager to request the api-key and channel id from the user. However, once the user has entered these, I'd prefer not to make them enter the values again. (The api key is 16 random looking chars and difficult to enter correctly.) This means that the device has to store the values persistently between boots - the main use case for EEPROM. But how do you tell that the values in EEPROM (actually flash on an ESP device) are uninitialized crap, not the previous value? As suggested in the comments, setting flag values in a number of vars make it statistically unlikely to the random values from uninitialized memory. On an AtMega device, unless previously used, the values will be 255 in each byte.

11
  • You have to solve this in your sketch. That's how it should be in the first place. Even with an atmega it is dangerous to rely on the eeprom being all 0xff. When you use an atmega for testing, and forgot about it, the eeprom might no longer be 0xff. This question is the same, according to the rules of stackexchange, I think this question will be closed.
    – Jot
    Dec 29, 2018 at 10:25
  • 1
    Just buy I2C EEPROM.. Much better solution
    – Pararera
    Dec 29, 2018 at 11:12
  • @Jot - Barring the use of persistent memory like EEPROM, a program (sketch) is the same every time you run it. How do you propose to "solve this in your sketch" if the goal is to determine if it has every been run since the device was flashed?
    – Llaves
    Dec 29, 2018 at 14:36
  • 1
    @Gerben - the ESP8266 doesn't contain actual EEPROM, it simulates the behavior using flash. Clearing the "EEPROM" with a separate program would fail in this case as the flash memory would be over-written when a new program is flashed.
    – Llaves
    Dec 29, 2018 at 16:25
  • 2
    Flash and EEPROM are the same thing in the case of the ESP. I think they always use the same location of the Flash/EEPROM to store the "fake" EEPROM data.
    – Gerben
    Dec 29, 2018 at 16:41

3 Answers 3

2

Calculate a checksum for the data and store as part of the data. Recalculate the checksum when you read the (questionable) data back again. If the checksum fails, it's invalid. If it passes, the data is valid to some statistical level of confidence.

A 16-bit checksum would make false acceptance highly unlikely, but make it long enough for your own comfort level. What would be the worst consequence of a false acceptance: inconvenience (user has to re-enter a number)? Loss of money or property? Loss of life? Choose the complexity of your solution accordingly.

1
  • This is the best, most reasonable answer to this question. Other answers include "don't use EEPROM", and "rely on a magic value being there". I definitely like your strategy the best, and as you can say it can be expanded on to increase confidence (a better type of hash maybe instead of just a checksum). I'd add that I would have some kind of ability to do "migrations", that is, store a version of the EEPROM and if values are added/changed, offer the ability to upgrade to the new format. Jan 12, 2021 at 4:46
1

Rather than use EEPROM, which is a real pain considering that it's shared between all stored data and you need to remember offsets and lengths, use SPIFFS.

By including FS.h in your program (or the include file that needs to store the data, if you are trying to be modular), you can store several megabytes of data as strings in a named file (much like fprintf() or open() would let you do in C++).

Call SPIFFS.begin(); in setup(), and then use the following sections of code to read or write data to the file:

(assuming you want to store start and end in SPIFFS, but this works for any datatype known to Arduino, and probably anything with the printable attribute)

Read saved config:

Serial.println(F("Loading config"));
File f = SPIFFS.open("/ACTIVE_TIMES.cnf", "r");
if (!f) {
  //File does not exist -- first run or someone called format()
  //Will not create file; run save code to actually do so (no need here since 
  //it's not changed)
  Serial.println(F("Failed to open config file"));
  start = 8;
  end = 20; // defaults
  return;
}
while (f.available()) {
  String key = f.readStringUntil('=');
  String value = f.readStringUntil('\r');
  f.read();
  Serial.println(key + F(" = [") + value + ']');
  Serial.println(key.length());
  if (key == F("START_TIME")) {
    start = value.toInt();
  }
  if (key == F("END_TIME")) {
    end = value.toInt();
  }
}
f.close();

Save configuration:

Serial.println(F("Saving config"));
File f = SPIFFS.open("/ACTIVE_TIMES.cnf", "w+");
if (!f) {
  //It's cases like this when calling SPIFFS.format() might be a good idea, 
  //but you would not want to do so here, since if it's an error for a 
  //different reason it would delete all your data. Make it manual or a prompt 
  //instead.
  Serial.println(F("Failed to open config file"));
  return;
}
f.print(F("START_TIME="));
f.println(start);
f.print(F("END_TIME="));
f.println(end);
f.flush();
f.close();
Serial.println(F("Saved values"));

You may also call SPIFFS.format() to reset the filesystem (say, for a factory reset), but this will cause issues on attempting to read any stored data, since the file will not exist. Thus, have a section that sets reasonable defaults (or just prompts the user to set the values).

Note: Since this is for storing an API key and such, just have it fail, since the API key not working is a good notice the user needs to configure it, and you do NOT want to ship a product with a working API key that belongs to you rather than the user.

Final warning: Note carefully the use of F() around certain items in that code. This macro, specific to Arduino, allows one to store data in Flash ONLY, where strings normally consume RAM as well (once the system boots, it loads to RAM, since most code expects it there). The benefit of F(), rather than a progmem directive, is that many functions native to Arduino can read it directly thanks to overloads, or you can wrap String() around the F() to convert it to a string at runtime if the function does not allow that overload. The end result is that, as I've written it, this code should consume RAM only when it is actually running, as good code should (otherwise, it's really easy to use up all your RAM on such embedded devices if you add enough strings).

I have not bothered to do the latter with the filename passed to open() since it is used only once per load or save operation, does not support F() (thus I'd need to use the String() trick), and I'm fairly sure the compiler then shares the memory location, reducing the RAM used to only one edition of that filename.

0

I've done this in the past by simply checking for a string (array of characters if you prefer) at a specific memory location. I used "INITIALIZED". It would be very unlikely for the ROM to have this sequence of characters by accident. If I find the string I read the variables from ROM. If I don't, I write the string and set default values for the variables.

I found a slightly different example that uses the same technique:

// EEPROM library.
#include <EEPROM.h>
// Persistent Shoe State.
byte shoeState = 0;
// Number of Shoe States.
#define SHOE_STATE_MAX 3 

// setup() runs once at reset.
void setup() {     
  // EEPROM Init Code Base Address
  #define EEPROM_INIT_CODE_ADDR 0x00
  // Check EEPROM Init Code
  byte shoeStateInit[] = {0xde, 0xad, 0xbe, 0xaf, 0xaa};
  for(int b = EEPROM_INIT_CODE_ADDR; b < EEPROM_INIT_CODE_ADDR + sizeof(shoeStateInit); b++) {
    // EEPROM = Init Code?
    if(EEPROM.read(b) != shoeStateInit[b]) {
      // No, Write Init Code to EEPROM.
      EEPROM.write(b, shoeStateInit[b]);
      // Set Not Initialized Flag.
      shoeState = -1;
    }
  }
  // EEPROM Init Code?
  if(shoeState == 0) {
    // Yes, Read Shoe State from EEPROM.
    shoeState = EEPROM.read(sizeof(shoeStateInit));
  }
}

The only variable being saved to ROM is shoeState.

1
  • While testing you'll want another sketch or some way to clear your flag memory.
    – linhartr22
    Jan 3, 2019 at 19:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.