ESP8266-01 EEPROM - Empty value after EEPROM.get();

Hello,

i have been looking for an answer for a while to the following problem:

First the example code:

#include <EEPROM.h>
#include <ESP8266WiFi.h>

struct WiFiConf{
  String ssid;
  String password;
};

WiFiConf wifi = {
  "test",
  "test1"
};

WiFiConf wificonf = {
  "",
  ""
};


void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  Serial.println("Start");

  writeConfig(wifi);

  delay(1000);

  wificonf = getConfig();



}

void loop() {
  // put your main code here, to run repeatedly:
  //EMPTY LOOP
}

void writeConfig(WiFiConf wifi){
  EEPROM.put(0, wifi);
  EEPROM.commit();
}

WiFiConf getConfig(){
  WiFiConf wifi = {
    "",
    ""
  };
  EEPROM.get(0, wifi);
  
  delay(1000);
  Serial.println();
  Serial.print("SSID size: ");
  Serial.print(wifi.ssid.length());
  Serial.print(" || SSID: ");
  Serial.println(wifi.ssid);
  Serial.print("Password size: ");
  Serial.print(wifi.password.length());
  Serial.print(" || Password: ");
  Serial.println(wifi.password);
  return wifi;
}

I am creating a struct object with two Strings. Creating two objects of that struct. One is going to be written to the EEPROM while the other is trying to read data from the EEPROOM. Writing the object with EEPROM.put(), wait for a second and then trying to read the data with EEPROM.get();

As I am using the ESP8266 I have to use EEPROM.commit() after writing the data.

My Problem: Both strings are empty when I try to print them, also the size is 0. I check that right after reading the data.
BTW: EEPROM.begin(size) is always ending in an error while runtime. Something with Fatal exception. But I kind of dont need it:

One weird thing to add here, when I change the code like this (use the ssid and password from EEPROM.get()), it is connecting to the network. But the serial.print still show empty strings. I dont get what is wrong with all of that. Am I missing something really hard here??

#include <EEPROM.h>
#include <ESP8266WiFi.h>

struct WiFiConf{
  String ssid;
  String password;
};

WiFiConf wifi = {
  "Sibby Wlan",
  "********"
};

WiFiConf wificonf = {
  "",
  ""
};


void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  Serial.println("Start");

  writeConfig(wifi);

  delay(1000);

  wificonf = getConfig();


  char ssidChar[64];
  char passwordChar[64];
  wificonf.ssid.toCharArray(ssidChar, sizeof(ssidChar)/sizeof(char));
  wificonf.password.toCharArray(passwordChar, sizeof(passwordChar)/sizeof(char));
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssidChar, passwordChar);

  // Connect to Wi-Fi network with SSID and password
  Serial.println();
  Serial.print("Connecting to ");
  Serial.print(ssidChar);

    
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("Connected!");


}

void loop() {
  // put your main code here, to run repeatedly:
  //EMPTY LOOP
}

void writeConfig(WiFiConf wifi){
  EEPROM.put(0, wifi);
  EEPROM.commit();
}

WiFiConf getConfig(){
  WiFiConf wifi = {
    "",
    ""
  };
  EEPROM.get(0, wifi);
  
  delay(1000);
  Serial.println();
  Serial.print("SSID size: ");
  Serial.print(wifi.ssid.length());
  Serial.print(" || SSID: ");
  Serial.println(wifi.ssid);
  Serial.print("Password size: ");
  Serial.print(wifi.password.length());
  Serial.print(" || Password: ");
  Serial.println(wifi.password);
  return wifi;
}

Here is the output of the serial monitor:

Start

SSID size: 0 || SSID:
Password size: 0 || Password:

Connecting to ...Connected!

I feel like I am missing something or just stupid. Google is not helping with that kind of problem. I mean the string is there, but I cant see it :o

1 Like

You write string and String interchangeable. They are not the same! From the top of my head, the EEPROM library (at least on AVR) uses the sizeof the type to write it to EEPROM. String (with a upper case) is an object which dynamically claims extra memory. Aka, can't be used with the sizeof() operator.

Solution would be simple, switch to strings (lower case).

struct WiFiConf{
  char ssid[20];
  char password[20];
};

You need to call EEPROM.begin(size) before you start reading or writing, size being the number of bytes you want to use. Size can be anywhere between 4 and 4096 bytes.

As @septillion says, you can not store a String object.

@septillion
Thanks for the reply. I was thinking about the same thing. BUT when I use WiFi.begin(ssid, password) it is able to connect to the wifi. The strings must be there....
But I tried changed the code like this:

#include <EEPROM.h>
#include <ESP8266WiFi.h>

struct WiFiConf{
  char ssid[32];
  char password[32];
};

WiFiConf wifi = {
  "Connect and die!",
  "********"
};

WiFiConf wificonf = {
  "",
  ""
};


void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  Serial.println("Start");

  writeConfig(wifi);

  delay(1000);

  wificonf = getConfig();


  
  WiFi.mode(WIFI_STA);
  WiFi.begin(wificonf.ssid, wificonf.password);

  // Connect to Wi-Fi network with SSID and password
  Serial.println();
  Serial.print("Connecting to ");
  Serial.print(wificonf.ssid);

    
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("Connected!");


}

void loop() {
  // put your main code here, to run repeatedly:
  //EMPTY LOOP
}

void writeConfig(WiFiConf wifi){
  EEPROM.put(0, wifi);
  EEPROM.commit();
}

WiFiConf getConfig(){
  WiFiConf wifi = {
    "",
    ""
  };
  EEPROM.get(0, wifi);
  
  delay(1000);
  Serial.println();
  Serial.print("SSID size: ");
  Serial.print(sizeof(wifi.ssid)/sizeof(wifi.ssid[0]));
  Serial.print(" || SSID: ");
  Serial.println(wifi.ssid);
  Serial.print("Password size: ");
  Serial.print(sizeof(wifi.password)/sizeof(wifi.password[0]));
  Serial.print(" || Password: ");
  Serial.println(wifi.password);
  return wifi;
}

With the following output:

Start

SSID size: 32 || SSID:
Password size: 32 || Password:

Connecting to ...Connected!

I still cant see the char arrays, size is there. I get the printed size. I mean its a static array. But still no output for the char arrays...

@cattledog
Thanks for the reply. But in the first post I already said that EEPROM.being(size) always ending in a fatal exception... I tried EEPROM.begin(512); and EEPROM.begin(4096);
And in addition, WiFi.begin(ssid, password) still works with the read data from EEPROM. But no output in the serial...

Although the String class on an ESP is not nearly a big a risk as for memory corruption as on an AVR, this

struct WiFiConf{
  String ssid;
  String password;
};

i'd say is pushing it a little.
and then using EEPROM put & get would be more of the same.
I'd use a manual way of storing the data on the (flash)EEPROM, writing to it as if it is a null terminated string writing and reading 1 byte at a time. How you store the data in RAM doesn't really matter, though the functions that use the data in this case take char* as arguments and that would therefore be a logical choice. If on the other hand you'd want to store many ssid's & passwords, then Strings could also be an option.

@Deva_Rishi

Thanks. My next step would have been using EEPROM.write() and EEPROM.read() with byte by byte...

The weird behaviour is making me crazy. It would be easy to use EEPROM.get() and EEPROM.put().
I just want to save the Wifi config (input from user on the webserver or android app).

Can you think of something why WiFi.begin still works with the "empty" strings or "empty" char arrays?

BTW: EEPROM.begin(size) is always ending in an error while runtime. Something with Fatal exception. But I kind of dont need it:

I don't think that the EEPROM will work without the begin. You need to figure out why the begin doesn't work before you can use EEPROM. If you post the entire error message maybe someone can help.

Are you using the right core? Choosing the right ESP in the Tools, Boards menu?

Are you using the right core? Choosing the right ESP in the Tools, Boards menu?

When I add EEPROM.begin(1024); as the first line of setup and compile for a Wemos D1 mini every thing works properly, and I see this output

Start

SSID size: 32 || SSID: mySSID //prints correct SSID
Password size: 32 || Password: mypassword //prints correct password

Connecting to mySSID.Connected!

Part of the problem is passing the structure to a function:

void writeConfig(WiFiConf wifi){
  EEPROM.put(0, wifi);
  EEPROM.commit();
}

When structures, objects, and arrays are passed to a function, they are passed by reference. The only thing passed is a pointer to the object. That pointer has a size of 2 so EEPROM.put() will only store the two bytes that contain the pointer. This causes a problem when you read the value into a local struct.

johnwasser:
When structures, objects, and arrays are passed to a function, they are passed by reference.

I think you are confusing with other languages. In C++, everything is passed by value by default, even array.

arduino_new:
I think you are confusing with other languages. In C++, everything is passed by value by default, even array.

So my 50 years of programming experience have left me uninformed?!? Oh my!

So perhaps you can explain why I get the following results when I test your assertion:

void setup()
{
  char Array[] = " This is an array, passed by value.";


  Serial.begin(115200);


  Serial.print(sizeof Array);
  Serial.println(Array);
  PassedByValue(Array);
  Serial.print(sizeof Array);
  Serial.println(Array);
}


void loop()
{}


void PassedByValue(char array[])
{
  // Since arrays are passed by value, there is no way to damage the original array.
  // In fact, all of these statements will be optimized away since the local copy of
  // the array is not used anywhere else.
  Serial.println(sizeof array);


  array[6] = '@';
  array[7] = '

When I upload that to an Arduino UNO and run Serial Monitor I get:

36 This is an array, passed by value.
2
36 This @$%*& array, passed by value.

I also get a handy compiler warning:

sketch_jul26a.ino: In function 'void PassedByValue(char*)':
sketch_jul26a.ino:22:25: warning: 'sizeof' on array function parameter 'array' will return size of 'char*' [-Wsizeof-array-argument]
   Serial.println(sizeof array);
                         ^
sketch_jul26a.ino:17:31: note: declared here
 void PassedByValue(char array[])
                               ^
Sketch uses 1856 bytes (5%) of program storage space. Maximum is 32256 bytes.
Global variables use 224 bytes (10%) of dynamic memory, leaving 1824 bytes for local variables. Maximum is 2048 bytes.

;
 array[8] = '%';
 array[9] = '*';
 array[10] = '&';
}


When I upload that to an Arduino UNO and run Serial Monitor I get:

> 36 This is an array, passed by value.
> 2
> 36 This @$%*& array, passed by value.

I also get a handy compiler warning:

§DISCOURSE_HOISTED_CODE_1§

Yes, when pass to a function, the array decays to a pointer. That's why you get that nice warning telling you that sizeof doesn't work correctly. The value of the pointer itself is passed by value, in that you cannot modify it to point to something else. You could still, however using the pointer to change the value it is currently pointing to.

If it's passing a pointer* it's passing it by reference, that's the whole definition of passing it by reference...

Although a pointer and a reference are two different things in C :wink: But it serves the same purpose as a constant pointer without the need of the dereference operator.

Part of the problem is passing the structure to a function:
Code: [Select]
void writeConfig(WiFiConf wifi){
EEPROM.put(0, wifi);
EEPROM.commit();
}
When structures, objects, and arrays are passed to a function, they are passed by reference. The only thing passed is a pointer to the object. That pointer has a size of 2 so EEPROM.put() will only store the two bytes that contain the pointer. This causes a problem when you read the value into a local struct.

I'm not so certain that only the pointer is stored in the eeprom.
As I said, I have been running the program of post #3 sucessfully when I add EEPROM.begin(1024).

I have unplugged my wemos, commented out those parts of the code which set up a struct and write it to eeprom. I upload this new code which only reads from eeprom, and all the SSID and password data is there, gets printed out, and the wifi connects using these values.

#include <EEPROM.h>
#include <ESP8266WiFi.h>

struct WiFiConf{
  char ssid[32];
  char password[32];
};

//WiFiConf wifi = {
// "Connect and die!",
//"********"
//};

WiFiConf wificonf = {
  "",
  ""
};


void setup() {
  // put your setup code here, to run once:
  EEPROM.begin(1024);
  Serial.begin(115200);
  Serial.println("Start");

  //writeConfig(wifi);

  delay(1000);

  wificonf = getConfig();


  
  WiFi.mode(WIFI_STA);
  WiFi.begin(wificonf.ssid, wificonf.password);

  // Connect to Wi-Fi network with SSID and password
  Serial.println();
  Serial.print("Connecting to ");
  Serial.print(wificonf.ssid);

    
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("Connected!");


}

void loop() {
  // put your main code here, to run repeatedly:
  //EMPTY LOOP
}

//void writeConfig(WiFiConf wifi){
//  EEPROM.put(0, wifi);
//  EEPROM.commit();
//}

WiFiConf getConfig(){
  WiFiConf wifi = {
    "",
    ""
  };
  EEPROM.get(0, wifi);
  
  delay(1000);
  Serial.println();
  Serial.print("SSID size: ");
  Serial.print(sizeof(wifi.ssid)/sizeof(wifi.ssid[0]));
  Serial.print(" || SSID: ");
  Serial.println(wifi.ssid);
  Serial.print("Password size: ");
  Serial.print(sizeof(wifi.password)/sizeof(wifi.password[0]));
  Serial.print(" || Password: ");
  Serial.println(wifi.password);
  return wifi;
}

cattledog:
I'm not so certain that only the pointer is stored in the eeprom.
As I said, I have been running the program of post #3 sucessfully when I add EEPROM.begin(1024).

without EEPROM.begin(1024); it really won't work, mind you i have had experiences where eventhough i declare an eeprom size to small it still does not clear the data that is already stored in the flash-eeprom but that is another matter.

cattledog:
I have unplugged my wemos, commented out those parts of the code which set up a struct and write it to eeprom. I upload this new code which only reads from eeprom, and all the SSID and password data is there, gets printed out,

now that is actual proof, that eeprom.get works with structs of char*, though still i would do it manually myself.

cattledog:
and the wifi connects using these values.

That does not mean much, if no valid data is provided for wifi.begin() (iaw an empty string for ssid) then it will just use that last valid data instead and try and connect as far as i know.

groundFungus:
I don't think that the EEPROM will work without the begin. You need to figure out why the begin doesn't work before you can use EEPROM. If you post the entire error message maybe someone can help.

Are you using the right core? Choosing the right ESP in the Tools, Boards menu?

So, I added the EEPROM.begin(size) to my example code and it did work. But I wanted to know why I was getting the fatal exception and went some steps back. I changed the char arrays which are containing the wifi config back to strings and there it is. I was able to reproduce the fatal exception:

The ESP8266 is able to write and read data from the EEPROM So, the fatal exception must be happening while WiFi.begin(). I guess the strings which were read from the EEPROM are invalid data for WiFi.begin()

Thank you guys for helping me out. The solution is pretty easy and simple but I just want to add here to close the topic:

Change the Strings in the struct object to char arrays and use EEPROM.begin(size)!

arduino_new:
In C++, everything is passed by value by default, even array.

Agreed, with the exception of the C++ "Reference" Type (eg uint8_t &ref). For example, passing a struct BY VALUE:

struct Structure {
  float value;
};

void passStruct(Structure x);

void setup() {
  Structure myStruct = {3.14159};
  Serial.begin(115200);
  delay(1000);

  Serial.print("Before Function Call: ");
  Serial.println(myStruct.value, 5);
  passStruct(myStruct);
  Serial.print("After Function Call: ");
  Serial.println(myStruct.value, 5);
}

void loop() {
}

void passStruct(Structure x) {
  x.value = 2.71828;
  Serial.print("Inside Function Call: ");
  Serial.println(x.value, 5);
}

Serial Mon Output:

Before Function Call: 3.14159
Inside Function Call: 2.71828
After Function Call: 3.14159

Now, you can pretend that you're passing a struct by reference by passing a pointer to it. But, that's really passing the VALUE of the pointer:

struct Structure {
  float value;
};

void passStruct(Structure *x);

void setup() {
  Structure myStruct = {3.14159};
  Serial.begin(115200);
  delay(1000);

  Serial.print("Before Function Call: ");
  Serial.println(myStruct.value, 5);
  passStruct(&myStruct);
  Serial.print("After Function Call: ");
  Serial.println(myStruct.value, 5);
}

void loop() {
}

void passStruct(Structure *x) {
  x->value = 2.71828;
  Serial.print("Inside Function Call: ");
  Serial.println(x->value, 5);
}

Serial Mon Output:

Before Function Call: 3.14159
Inside Function Call: 2.71828
After Function Call: 2.71828

septillion:
If it's passing a pointer* it's passing it by reference, that's the whole definition of passing it by reference...

Although a pointer and a reference are two different things in C :wink: But it serves the same purpose as a constant pointer without the need of the dereference operator.

Sure they do the same thing, but passing by value and passing by reference are different by definition and we must keep them being understood differently.

gfvalvo:
Agreed, with the exception of the C++ "Reference" Type (eg uint8_t &ref). For example, passing a struct BY VALUE:

Again passing by referent is not the same as passing a pointer to the function.

In C, everything is passed by value; there is no passing by reference.
In C++, they add passing by reference.
But if you want to dig deeper, passing by reference is implemented internally by passing around a pointer by the compiler. So really, there is NO passing by reference; just passing by value.

arduino_new:
Again passing by referent is not the same as passing a pointer to the function.

I never said it was. I should have started a new paragraph to separate the thoughts.

Yes, in C, EVERYTHING is passed by value.

C++ adds the concept of passing by reference:

void myFunction(uint8_t &dog) {

}

Using references is more restrictive but "safer" than using pointers. Although, as pointed out, the code generated by the compiler will probably end up using pointers.

As an Old Time 'C Guy' my view is that references were added to the C++ language for very specific purposes that could not be accomplished with pointers -- Operator Overloading, Method Chaining, etc. So, if a good old-fashioned pointer will do the job, I'll usually use it.