I'm trying to make a programm for Arduino Bluno Beetle, that saves a string in the loop. Here is my code:

#include <EEPROM.h>
#include <EEPROMAnything.h>
struct config_t{
   String lastThing;
    } savedValues;
void setup(){
   EEPROM_readAnything(ADDR, savedValues);
   Serial.println(lastThing);
}
String readString="asd";
void loop() {
 //...
 lastThing=readString;
 EEPROM_writeAnything(ADDR, savedValues);
}

(The EEPROMAnything.h can be found here: https://playground.arduino.cc/Code/EEPROMWriteAnything?action=sourceblock&num=1)

The code works until the 11th line. It gives there the following error:

'lastThing' was not declared in this scope

Does anybody know how to fix this problem?

  • 1
    lastThing is neiher a global nor a local variable. It is the name of a member in the config_t structure. What do you want to do in the loop? – Maximilian Gerhardt Aug 7 at 19:33
  • My program receives data from a mobile phone with bluetooth, and writes it to an OLED screen. I need the EEPROM to save the last sent thing, and at a reboot to write it on the screen. – András Ujvári Aug 7 at 19:43
  • 2
    EEPROMs have limited writes, if you write to it in a loop you will exhaust all your writes very quickly! – esoterik Aug 8 at 0:10
  • Lol. Putting that write in the loop is a quick way to exhaust your eeprom. – PhillyNJ Aug 8 at 0:38
  • The 'current' version of the EEPROM library (which should be included with the IDE) has EEPROM.put, which is designed to write any variable type. As a bonus, it only writes if the data is different, so can use it repeatedly without wearing out your EEPROM (as much). – user85471 Aug 8 at 3:13

The proper syntax for referring to a struct member is [structName].[memberName]. Changing lastThing in lines 8 & 13 to savedValues.lastThing fixes the "undeclared" error.

Very huge disclaimer: the code in this answer is untested, and moreover I never played with String objects (I don't like dynamic allocation on arduino). Consequently bugs are expected in this code. If you find any (for instance, the relocation does not work how I suppose, or the parameters should be passed in another way in the read function) please write in the comment so I can fix this

EDIT: Since there were complaints about this, I tested it and.. It works. The read and write functions work so, in the end, it appears that all the researches I did for this answer were complete and allowed me to correctly implement it


As pointed out, lastThing is not a variable, it is a field in the savedValues variable. So the correct syntax would be

lastThing=readString;

This said, your code won't work and has performance issues. First of all it uses the EEPROM.write method, which overwrites the value in eeprom thus lowering its life, and then you will save a pointer to a memory area (4 bytes), which is not the thing you want to save.

In my opinion, it is better to save it "manually":

// Write i in EEPROM, starting from address addr.
// Return the number of bytes written (2 if everything is ok)
uint16_t writeUInt16ToEeprom(uint16_t addr, uint16_t i)
{
    uint16_t currAddr = addr;

    // Only if there is enough space
    if ((currAddr + 2) <= E2END)
    {
        // Write the 2 bytes in eeprom
        EEPROM.update(currAddr++, i >> 8);
        EEPROM.update(currAddr++, i & 0xFF);
    }
    return (currAddr - addr);
}

// Write s in EEPROM, starting from address addr.
// Return the number of bytes written
uint16_t writeStringToEeprom(uint16_t addr, String s)
{
    uint16_t currAddr = addr;

    uint16_t len = s.length();

    // Only if there is enough space
    if ((currAddr + len + 2) <= E2END)
    {
        // Write the string length
        currAddr += writeUInt16ToEeprom(currAddr, len);

        // Write the string bytes
        for (uint16_t i = 0; i < len; i++)
            EEPROM.update(currAddr++, s.charAt(i));
    }

    return (currAddr - addr);
}

// Read i from EEPROM, starting from address addr.
// Return the number of bytes read (2 if everything is ok)
uint16_t readUInt16FromEeprom(uint16_t addr, uint16_t & i)
{
    uint16_t currAddr = addr;

    // Only if there is enough space
    if ((currAddr + 2) <= E2END)
    {
        i = EEPROM.read(currAddr++);
        i = i << 8 | EEPROM.read(currAddr++);
    }
    return (currAddr - addr);
}

// Read s from EEPROM, starting from address addr.
// Return the number of bytes read
// S should be already allocated when entering the function
uint16_t readStringFromEeprom(uint16_t addr, String & s)
{
    uint16_t currAddr = addr;

    uint16_t len = 0;
    s.remove(0);

    // Check if there is enough space for the length
    if ((currAddr + 2) <= E2END)
        currAddr += readUInt16FromEeprom(currAddr, len);

    // Check if there is enough space left for all the chars
    if ((currAddr + len) <= E2END)
    {
        s.reserve(len); // Should allocate only once, thus speeding up

        for (uint16_t i = 0; i < len; i++)
            s.concat((char)(EEPROM.read(currAddr++)));
    }
    else
        currAddr = addr;

    return (currAddr - addr);
}


String lastThing;

void setup(){
    readStringFromEeprom(ADDR, lastThing);
    Serial.println(lastThing);
}

String readString="asd";
void loop() {
    //...
    lastThing=readString;
    writeStringToEeprom(ADDR, lastThing);
}

Note: you should also check the values returned from the functions to check whether the writing or reading was successful.

In this case I did not use the struct, since in your example the struct is not used. If you really need it, because for instance you want to save also an int, you can do something like this:

struct config_t{
    String lastThing;
    uint16_t myIdx;
} savedValues;

uint16_t writeUInt16ToEeprom(uint16_t addr, uint16_t i)
...

uint16_t writeStringToEeprom(uint16_t addr, String s)
...

uint16_t readUInt16FromEeprom(uint16_t addr, uint16_t & i)
...

uint16_t readStringFromEeprom(uint16_t addr, String & s)
...

// Write s in EEPROM, starting from address addr.
// Return the number of bytes written
uint16_t writeStructToEeprom(uint16_t addr, struct config_t s)
{
    uint16_t currAddr = addr;
    currAddr += writeStringToEeprom(currAddr, s.lastThing);
    currAddr += writeUInt16ToEeprom(currAddr, s.myIdx);
    return (currAddr - addr);
}

// Read s from EEPROM, starting from address addr.
// Return the number of bytes read
// S should be already allocated when entering the function
uint16_t readStructFromEeprom(uint16_t addr, struct config_t s)
{
    uint16_t currAddr = addr;

    uint16_t readVal = readStringFromEeprom(currAddr, s.lastThing);
    if (readVal == 0)
        return 0; 
    currAddr += readVal;

    uint16_t readVal = readUInt16FromEeprom(currAddr, s.myIdx);
    if (readVal == 0)
        return 0; 
    currAddr += readVal;

    return (currAddr - addr);
}

void setup(){
    readStructFromEeprom(ADDR, savedValues);
    Serial.println(lastThing);
}

String readString="asd";
void loop() {
    //...
    savedValues.lastThing = readString;
    savedValues.myIdx = 22;
    writeStructToEeprom(ADDR, savedValues);
}

I think that the idea is pretty easy to understand. Basically you manually write a function for each type you want to write (e.g. writeUInt16ToEeprom), then use these "building blocks" to create complex functions (e.g. writeStructToEeprom). For items which do not have a fixed width (Strings) you first save the length, then the bytes. What I mean with "fixed width" is that you know at compile time the number of childs it has For instance, the struct has two "childs" (the string and the integer), while the string has an unknown number of childs (n characters).

You can write a template function for all the integers like this:

// Write i in EEPROM, starting from address addr.
// Return the number of bytes written
template <class T> uint16_t writeIntegerToEeprom(uint16_t addr, T i)
{
    uint16_t currAddr = addr;

    // Only if there is enough space
    if ((currAddr + sizeof(T)) <= E2END)
    {
        // Write the bytes in eeprom
        for (uint8_t idx = 0; idx < sizeof(T); idx++)
        {
            EEPROM.update(currAddr++, (8*(i >> (sizeof(T) - 1 - idx))) & 0xFF);
        }
    }
    return (currAddr - addr);
}

// Read i from EEPROM, starting from address addr.
// Return the number of bytes read
template <class T> uint16_t readUInt16FromEeprom(uint16_t addr, T & i)
{
    uint16_t currAddr = addr;

    // Only if there is enough space
    if ((currAddr + sizeof(T)) <= E2END)
    {
        i = 0;
        for (uint8_t idx = 0; idx < sizeof(T); idx++)
        {
            i = i << 8 | EEPROM.read(currAddr++);
        }
    }
    return (currAddr - addr);
}

You can use it for all the integer types, using it like the UInt16 variant above. Avoid using it for classes or structs, since in some cases (e.g. the struct with string example above) it may not save what you want

  • but that is in the EEPROIM library as put() and get() for any reference. github.com/arduino/ArduinoCore-avr/blob/master/libraries/EEPROM/… – Juraj Aug 9 at 20:03
  • @Juraj are you saying that EEPROM put and get will allow to put and get the struct with the String? I doubt that. You can use "put" instead of the very last template implementation, but I don't think the rest of the answer must change – frarugi87 Aug 11 at 14:26
  • @downvoter: can you please explain why the -1? I think that the answer is on topic with the question (not declaration of the item), and moreover addresses the main problem with the OP implementation, which is the fact that he wants to save a string but he isn't actually doing so with his code – frarugi87 Aug 11 at 14:28
  • I am the downvoter. No, you can't write String with put(), but your solution is not elegant and not tested. And we should discourage use of String. – Juraj Aug 11 at 15:46
  • I posted my answer – Juraj Aug 12 at 6:28

it is better not to use String type at all and it can't be saved to EEPROM. You can save a copy of the internal c-string of the String with strcpy(savedValues.lastThing, readString.c_str());

#include <EEPROM.h>

#define ADDR 512

struct config_t {
  char lastThing[32];
  int lastNum;
} savedValues;

void setup() {
  Serial.begin(115200);
  Serial.setTimeout(10);

  EEPROM.get(ADDR, savedValues);
  Serial.println(savedValues.lastThing);
  Serial.println(savedValues.lastNum);
}

void loop() {
  if (Serial.available()) {
    String readString = Serial.readStringUntil('|');
    int n = Serial.parseInt();

    strncpy(savedValues.lastThing, readString.c_str(), sizeof(savedValues.lastThing));
    savedValues.lastNum = n;
    EEPROM.put(ADDR, savedValues);

    //clean rest of the input (crlf)
    byte b;
    while (Serial.readBytes(&b, 1) > 0);
  }
}
  • There are cases where dynamic allocation must be used. Personally I try to avoid it as hell, since these are microcontrollers and do not have plenty of memory, but in some cases you can't. Then you added a lot of assumptions to the code without highlighting. Do you know this project so well that you can assume that strings are separated by a pipe mark? Do you know that strings will never exceed 32 chars and you are so confident that you even don't check it? You are aware that if the user writes "This is a really wonderful day :)" you will have all sort of bad behaviors, right? – frarugi87 Aug 12 at 8:47
  • I replaced strcpy with strncpy. It is a test sketch for EEPROM, not a user friendly Serial input handling demo. – Juraj Aug 12 at 9:18

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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