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?
3 Answers 3
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/…2018年08月09日 20:03:25 +00:00Commented Aug 9, 2018 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 changefrarugi87– frarugi872018年08月11日 14:26:22 +00:00Commented Aug 11, 2018 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 codefrarugi87– frarugi872018年08月11日 14:28:39 +00:00Commented Aug 11, 2018 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.2018年08月11日 15:46:44 +00:00Commented Aug 11, 2018 at 15:46
-
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– frarugi872018年08月12日 08:47:18 +00:00Commented Aug 12, 2018 at 8:47
-
I replaced strcpy with strncpy. It is a test sketch for EEPROM, not a user friendly Serial input handling demo.2018年08月12日 09:18:32 +00:00Commented Aug 12, 2018 at 9:18
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?