Help needed to adding Strings to String array and save it into EEPROM

Greetings Everyone,
I would like to store my String array into EEPROM, but every time user pushes a button it should add another String into the array (For this example it is just apple) and then save it into EEPROM again. Here is what I got so far :

#include <EEPROM.h>
String x = "apple";
int i = 0;
String comb[5];
const int buttonPin = 2;     
int buttonState = 0;         
void writeStringToEEPROM(int addrOffset, const String &strToWrite)
{
  byte len = strToWrite.length();
    EEPROM.write(addrOffset, len);
      for (int j = 0; j < len ; j++)
  {
        EEPROM.write(addrOffset + 1 + j, strToWrite[j]);
  }
}
String readStringFromEEPROM(int addrOffset)
{
  int newStrLen = EEPROM.read(addrOffset);
    char data[newStrLen + 1];
      for (int j = 0; j < newStrLen; j++)
  {
        data[j] = EEPROM.read(addrOffset + 1 + j);
  }
        data[newStrLen] = '\0';
          return String(data);
}
void setup() 
{
 Serial.begin(9600); 
 pinMode(buttonPin, INPUT); 

}

void loop() {
  buttonState = digitalRead(buttonPin);
 
  if (buttonState == HIGH) {
    comb [i] = x + "/";
      writeStringToEEPROM(0, comb[i-2] + comb[i-1] + comb[i]);
        delay (1000);
          String retrievedString = readStringFromEEPROM(0);  
            Serial.print(retrievedString); 
              i++;
  
  } 
  else;
  }

This is my first post to the forum, sorry if I am breaking any rules and thanks for the help.

1 Like

You have done well by using code tags and (almost) indented code on your first post. Congrats and :heart:︎ given :wink:

The challenge is that your String array holds String objects but the memory allocated to represent the text ("apple") is somewhere else. The call to write thus will not store your data (and write will only copy 1 byte so your code is not going to work this way).

the easiest fix if you know the max size of an authorised string is to declare comb as an array of cStrings (see string - Arduino Reference)

const byte maxTextLength = 20;
const byte maxTextRecord = 10;
char comb[maxTextRecord][maxTextLength+1]; // +1 to allow a null termination for the text.

then all the data is encompassed in a fixed size array and writing it to EEPROM is just a matter of doing EEPROM.put(0, comb); and the whole array will be updated in EEPROM. When you want to get the array, you can just do also an EEPROM.get(0, comb);

PS/ of course you need to be diligent and never write more than maxTextLength bytes into any given entry.

1 Like

strlcpy / strlcat will help with that.
If you follow @J-M-L suggestion, then you can pass (maxTextLength + 1) to strlcpy / strlcat and they will ensure you don't overwrite the bounds for that char array.
see the sketch strlcpy_strlcat.ino for examples.
NOTE strlcpy / strlcat take the whole size (including the space for the '\0') so maxTextLength +1.

Don't try to save a String directly to EEPROM.
However you can save the chars in the String i.e. comb[i].c_str() to EEPROM.
Write this number of chars, comb[i].length() + 1, from comb[i].c_str(); so that the '\0' at the end is also written.
When reading back, clear all the Strings first and then use

while (i < 5) {
  char c = (char)EEPROM.read(addrOffset + j);
  j++;
  if (c == '\0') { 
   i++;
   continue;  // finished that String DO NOT add the '\0' move on to the next one
  }
  comb[i] += c;
}

and increment i when you find the terminating '\0' in EEPROM
(that code needs some more work but you get the idea )
This will read back all the Strings, but reading back just one String is more complicated.

@J-M-L suggestion has fixed sizes for each so you know where the next string will start in EEPROM. The downside, of course, is that you need to know before hand the longest string you will ever need to save.

1 Like

This is correct indeed

The downside of variable length and

you can save the chars in the String i.e. comb[i].c_str() to EEPROM.
Write this number of chars, comb[i].length() + 1, from comb[i].c_str(); so that the '\0' at the end is also written.

given OP has an array of strings to save, is that you can't just save 1 String in EEPROM as you risk overlapping with the next one if your String has grown, so you need to write the full array, not just the String that has changed. And adding just 1 char somewhere results in ALL the following chars in EEPROM to be changed, thus eating away a bit of your 100,000 write capability.

The fixed size gives you the benefit of speed, EEPROM capability protection (using update) and also knowing directly and exactly where each string is stored if you are looking for one and don't want to bring the full array in SRAM.

that being said, both options work for sure.

1 Like

Thank you for all replies, it seems I fixed the issue

#include <EEPROM.h>
String x = "apple";
double w = 2.75;
int i = 0;
String comb[20];
const int buttonPin = 2;
int buttonState = 0;
void writeStringToEEPROM(int addrOffset, const String &strToWrite)
{
  byte len = strToWrite.length();
  EEPROM.write(addrOffset, len);
  for (int j = 0; j < len ; j++)
  {
    EEPROM.write(addrOffset + 1 + j, strToWrite[j]);
  }
}
String readStringFromEEPROM(int addrOffset)
{
  int newStrLen = EEPROM.read(addrOffset);
  char data[newStrLen + 1];
  for (int j = 0; j < newStrLen; j++)
  {
    data[j] = EEPROM.read(addrOffset + 1 + j);
  }
  data[newStrLen] = '\0';
  return String(data);
}
void setup()
{
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
}
void loop()
{
  buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH && i < 20) {
    comb [i] = x + "=" + String(w) + "/";
    String temp = "";
    for ( int k = 0 ; k < 20 ; k++) {
      if ( comb [k] != NULL) {
        temp += comb[k];
      }
    }
    writeStringToEEPROM(0, temp);
    delay (1000);
    String retrievedString = readStringFromEEPROM(0);
    Serial.print(retrievedString);
    i++;
  }
}

This version seems working just fine

you'll have to define "fine". :slight_smile:

You said

I would like to store my String array into EEPROM,

here you are only storing 1 String in EEPROM (the last one) and because you are missing a break in the for loop, your first string is filling up all the array and the next ones (entry is not NULL) are never stored in the array...

I'll let @drmpf tell you all about the bad things you get from calling String() or using + etc.

Thanks @J-M-L, @deniz3457 take a look at Taming Arduino Strings

This is mashing all the strings together into one and writing them into EEPROM as one string. Is that OK? You won't be able to separate the parts when you read the string back.

I'm not sure that comparing with a null pointer will work. I would use:
if ( comb [k] != "") {
or
if ( comb [k].length != 0) {

Yes mashing them is OK.

#include <LinkedList.h>
#include <EEPROM.h>
LinkedList<String> Data; 
String waste = "apple"; 
double weight = 2.75; 
int i = 0;
const int buttonPin = 2; //Button pin
int buttonState = 0; //Initial state of the button

void writeStringToEEPROM(int addrOffset, const String &strToWrite)
{
  byte len = strToWrite.length(); // takes the byte vise lenght of the String that is to be saved.
  EEPROM.write(addrOffset, len); // First saves the length
  for (int j = 0; j < len ; j++) // Then saves the String.
  {
    EEPROM.write(addrOffset + 1 + j, strToWrite[j]);
  }
}
String readStringFromEEPROM(int addrOffset) 
{
  int newStrLen = EEPROM.read(addrOffset); 
  char data[newStrLen + 1]; //Boundary of the data.
  for (int j = 0; j < newStrLen; j++)
  {
    data[j] = EEPROM.read(addrOffset + 1 + j);
  }
  data[newStrLen] = '\0'; //Terminates the Read operation.
  return String(data);
}
void setup()
{
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
}
void loop()
{
  buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH && i < 20)
  {
    Data.add(i, waste + "=" + String(weight) + "/" ); 
    String temp = "";
    for ( int k = 0 ; k < Data.size(); k++) 
    {
      temp += Data.get(k);
    }
    writeStringToEEPROM(0, temp); 
    delay (500);
    String retrievedData = readStringFromEEPROM(0); 
    Serial.print(retrievedData);
    i++;
  }
}

Using LinkedList library allowed me to get rid of the if statement part. Thank you again for all the replies.

Returning a String from a method is advised against as it can lead to memory fragmentation.
The prefered way (and simplier code) is

void readStringFromEEPROM(int addrOffset, String &rtnStr) 
{
  rtnStr = ""; // clear the return
  int newStrLen = EEPROM.read(addrOffset); 
  for (int j = 0; j < newStrLen; j++)
  {
    rtnStr += ((char)EEPROM.read(addrOffset + 1 + j));
  }
}