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
lastThing
is neiher a global nor a local variable. It is the name of a member in theconfig_t
structure. What do you want to do in the loop? – Maximilian Gerhardt Aug 7 '18 at 19:33