Storing strings in EEPROM and data changing after power cycle

Status
Not open for further replies.

JakeNBake

Member
Hello,

As the title says, I have been using EEPROM.put/get to store a string in the EEPROM of a 3.2. When later power cycling, the string completely changes value. Any ideas? I'm speculating that maybe all the values get left/right shifted?

Code:
#include <StreamMessageHandler.h>
#include <EEPROM.h>

StreamMessageHandler USBSERIAL;
char testWordread[10];
String testWord;

void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
USBSERIAL.registerState(ping,"ping");
USBSERIAL.registerState(readName,"readname");


}

void loop() {
  // put your main code here, to run repeatedly:
  USBSERIAL.run();
}

state ping() {
  Serial.println("pong");
  testWord = USBSERIAL.parameter(0);
  EEPROM.put(0,testWord.c_str());
}

state readName() {
  EEPROM.get(0,testWordread);
  testWord = String(testWordread);
  Serial.println(testWord);
}

Serial:
Code:
ping::aaaaaaaa
pong
readname //before power cycle
aaaaaaaa
readname // after power cycle
YzˆÆùT‚b
ping::aaaaaaa
pong
readname //before
aaaaaaa
readname
Yz?ÆyT‚
 
Not sure the String.c_str() is giving proper bytes to write info? :: The number of bytes written is related to the datatype or custom structure of the variable to be written >> www.arduino.cc/en/Tutorial/EEPROMPut

The example there shows a 'c' null term string in a struct that works - but the compiler knows the size of the struct. Any null term string of 'c' style or from .c_str() may not provide the needed sizeof() info for the write if done at compile time.

@mjs513 has written code for FRAM's that may show it working for 'c' style - - but the .c_str() won't have that.

On Read/.get() it is read to 'c' string memory then converted. Try doing that the other way [ strcpy (cstr, str.c_str()); ] on the .put() may allow it to resolve the length to .put()
 
I will give it a try with strcpy, but also felt it's worth adding that I tried to get this to work after having originally done it with String type:
Code:
#include <StreamMessageHandler.h>
#include <EEPROM.h>

StreamMessageHandler USBSERIAL;

String testWord;

void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
USBSERIAL.registerState(ping,"ping");
USBSERIAL.registerState(readName,"readname");


}

void loop() {
  // put your main code here, to run repeatedly:
  USBSERIAL.run();
}

state ping() {
  Serial.println("pong");
  testWord = USBSERIAL.parameter(0);
  EEPROM.put(0,testWord);
}

state readName() {
  EEPROM.get(0,testWord);
  Serial.println(testWord);
}

Serial:
Code:
ping::aaaaaaa
pong
readname
aaaaaaa
readname //after power cycle
Yz€Æùt‚
 
If it were me, I would also try changing the put as well.
Code:
state ping() {
  Serial.println("pong");
  testWord = USBSERIAL.parameter(0);
  EEPROM.put(0,testWord);
}
That is the underlying system will see you passed in a pointer to string and maybe decide it is 4 bytes long...

When I was debugging some of the EEPROM stuff on T4 and T4.1 beta, I did a sketch that dumped out the EEPROM data, but the values and actual underlying data...

Also I would probably add in a little debug code to get an idea of what was really written... What I think was written was the address of your variable...

So again if it were me, I would first try simple test to see what was really output to EEPROM. Like:

Code:
state ping() {
  Serial.println("pong");
  testWord = USBSERIAL.parameter(0);
  EEPROM.put(0,testWord);

  // Output debug information.
  Serial.println(testWord);
  Serial.println((uint32_t)&testWord, HEX);
#define eeprom_size 64  // not the actual real size, but limit to only 64 bytes to start.
  for (uint16_t i = 0; i < eeprom_size; i++) {
    Serial.printf("%x:%x ", i, EEPROM.read(i));

    if ((i & 0xf) == 0xf) Serial.println();
  }
}

And if desired you could also output the ascii with the above prints, something like:
Code:
  for (uint16_t i = 0; i < eeprom_size; i++) {
    uint8_t ch = EEPROM.read(i);
    Serial.printf("%x:%x ", i, (ch >= ' ' && ch <= '~')? ch : '.', ch);

    if ((i & 0xf) == 0xf) Serial.println();
  }
Again typed in on fly but should be reasonably close...

And again I think it will show the actual data written is not what you think it is...
 
Thank you! Now I fully understand what you mean. Using strcpy as defragster suggested and knowing what data was actually stored in a 'String', I've now got it working. Thank you both again!

(Working code if anyone else stumbles upon this issue:
Code:
#include <StreamMessageHandler.h>
#include <EEPROM.h>

StreamMessageHandler USBSERIAL;

char nameArray[15];
String usrString;

void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
USBSERIAL.registerState(ping,"ping");
USBSERIAL.registerState(readName,"readname");


}

void loop() {
  // put your main code here, to run repeatedly:
  USBSERIAL.run();
}

state ping() {
  Serial.println("pong");
  usrString = USBSERIAL.parameter(0);
  strcpy(nameArray,usrString.c_str());
  Serial.println(String(nameArray));
  EEPROM.put(0,nameArray);

}

state readName() {
  EEPROM.get(0,nameArray);
  Serial.println(String(nameArray));
}
 
I wonder if there is C++ template magic which could allow the EEPROM library to treat String objects in a smart/special manner? Would be really nice if this sort of code could "just work". Or alternately, maybe give an error or warning if it can't work?
 
I wonder if there is C++ template magic which could allow the EEPROM library to treat String objects in a smart/special manner? Would be really nice if this sort of code could "just work". Or alternately, maybe give an error or warning if it can't work?

Actually there is not much magic needed. One simply has to specialize the templates for Arduino strings. Here my try. To test it, copy the code after the EEPromClass in EEPROM.h.

Code:
// Specialization for Arduino Strings
template <>
String &EEPROMClass::get(int idx, String &s)
{
    s = ""; // just in case...
    EEPtr e = idx;

    char c = *e; // read in bytes until we find the terminating \0
    while (c != '\0')
    {
        s.append(c);
        c = *(++e);
    }
    return s;
}

// Specialization for Arduino Strings
template <>
const String &EEPROMClass::put(int idx, const String &s)
{
    const uint8_t *ptr = (uint8_t *)s.c_str();

#ifdef __arm__
    eeprom_write_block(ptr, (void *)idx, s.length() + 1); // length() doesn't account for the trailing \0
#else
    EEPtr e = idx;
    for (int count = s.length() + 1; count; --count, ++e)
        (*e).update(*ptr++);
#endif
    return s;
}


Then you can do things like:
Code:
#include "EEPROM.h"

void setup()
{ 
  while (!Serial)  {  }

  // uncomment to write
  /*
  String s1 = "Hello string";    
  EEPROM.put(0, s1);  
  */
  
  String s2;
  EEPROM.get(0,s2);  
  Serial.println(s2);  
}

void loop()
{ 
}

which prints:
Code:
Hello string
The reading into the string can probably be done more efficiently but I doubt if it is worth the effort... If you want, I can do a PR tomorrow but I have no chance to test the AVR part.
 
Status
Not open for further replies.
Back
Top