I can store and restore my data with avr/eeprom.h if I don't change the sketch.
- I have a sketch that stores the data. After power cycle of the device the data are fine.
- If I change the sketch with Serial.print statements (I want to re-read the data on-screen) the sketch seeks the data in wrong addresses of EEPROM, although I can verify with another sketch that the data are fine.
More detailed: I store around 50 bytes, but I verified the location for only the 4 bytes variable. I can see that this variable is at the start of the EEPROM (address: 0). But the second sketch seeks the data on address 30. Not even close.
To my understanding avr/eeprom.h is more easy to use since you don't need to police the location of data. But it seems that if change (what?) in your sketch it seeks the data on other locations although on eeprom are fine.
In other words, it's like if I change the firmware (not the EEMEM variables / definitions!), that firmware seeks the data on different places.
I understood something wrong?
In my situation is better to use the arduino EEPROM.h with static location of EEPROM addresses?
Fun fact. Google Bard seems it got it completely wrong:
What are the differences of avr/eeprom.h and EEPROM.h from arduino?
(Google Bard Answer)
The avr/eeprom.h and EEPROM.h libraries in Arduino are both used to access the EEPROM memory on AVR microcontrollers. However, there are some key differences between the two libraries. avr/eeprom.h is a lower-level library that provides direct access to the EEPROM registers. This means that it can be used to access any location in the EEPROM, but it also requires more code and can be more error-prone. EEPROM.h is a higher-level library that provides a more user-friendly interface to the EEPROM. It only allows you to access addresses that are within the range of the EEPROM, but it is easier to use and less error-prone. In general, avr/eeprom.h should only be used if you need to access specific locations in the EEPROM that are not within the range of EEPROM.h. Otherwise, EEPROM.h is the preferred library to use.
Edit: avr-objdump of eeprom section
Prints on Sketch | Without prints | Prints both on Library and Sketch |
---|---|---|
00810000 <eeprom_lw_dev_nonce>: | 00810000 <eeprom_lw_f_nwk_s_int_key>: | 00810000 <eeprom_lw_dev_addr>: |
00810002 <eeprom_lw_f_nwk_s_int_key>: | 00810010 <eeprom_lw_s_nwk_s_int_key>: | 00810004 <eeprom_lw_dev_nonce>: |
00810012 <eeprom_lw_s_nwk_s_int_key>: | 00810020 <eeprom_lw_has_joined>: | 00810006 <eeprom_lw_f_nwk_s_int_key>: |
00810022 <eeprom_lw_join_nonce>: | 00810021 <eeprom_lw_dev_nonce>: | 00810016 <eeprom_lw_s_nwk_s_int_key>: |
00810026 <eeprom_lw_has_joined>: | 00810023 <eeprom_lw_join_nonce>: | 00810026 <eeprom_lw_nwk_s_enc_key>: |
00810027 <eeprom_lw_tx_frame_counter>: | 00810027 <eeprom_lw_tx_frame_counter>: | 00810036 <eeprom_lw_app_s_key>: |
00810029 <eeprom_lw_rx_frame_counter>: | 00810029 <eeprom_lw_nwk_s_enc_key>: | 00810046 <eeprom_lw_has_joined>: |
0081002b <eeprom_lw_nwk_s_enc_key>: | 00810039 <eeprom_lw_rx_frame_counter>: | 00810047 <eeprom_lw_rx_frame_counter>: |
0081003b <eeprom_lw_app_s_key>: | 0081003b <eeprom_lw_app_s_key>: | 00810049 <eeprom_lw_tx_frame_counter>: |
0081004b <eeprom_lw_dev_addr>: | 0081004b <eeprom_lw_dev_addr>: | 0081004b <eeprom_lw_rx2_data_rate>: |
0081004f <eeprom_lw_rx2_data_rate>: | 0081004f <eeprom_lw_rx1_delay>: | 0081004c <eeprom_lw_rx1_data_rate_offset>: |
00810050 <eeprom_lw_rx1_data_rate_offset>: | 00810050 <eeprom_lw_rx2_data_rate>: | 0081004d <eeprom_lw_join_nonce>: |
00810051 <eeprom_lw_rx1_delay>: | 00810051 <eeprom_lw_rx1_data_rate_offset>: | 00810051 <eeprom_lw_rx1_delay>: |
Image kept for historical reasons.
1st window: Serial.print(s) only in sketch. 2nd window: without Serial.print(s) 3rd window: Serial.print(s) both on sketch and library. EEMEM from avr-objdump -D
2 Answers 2
I managed to reproduce your issue with the simple sketch below:
#include <avr/eeprom.h>
EEMEM uint8_t int8_ee;
EEMEM uint16_t int16_ee;
EEMEM uint32_t int32_ee;
EEMEM char str_ee[40];
void show(const char *name, void *address)
{
Serial.print(name);
Serial.print(": ");
Serial.println((uint16_t) address);
}
void setup() {
Serial.begin(9600);
show("int8_ee", &int8_ee);
#ifdef SWAP_PRINTING_VARS
show("int32_ee", &int32_ee);
show("int16_ee", &int16_ee);
#else
show("int16_ee", &int16_ee);
show("int32_ee", &int32_ee);
#endif
show("str_ee", str_ee);
}
void loop(){}
As is, it prints:
int8_ee: 46
int16_ee: 44
int32_ee: 40
str_ee: 0
If I #define SWAP_PRINTING_VARS
, it prints instead:
int8_ee: 46
int32_ee: 42
int16_ee: 40
str_ee: 0
I have no proper answer to your question. Although one may try to unveil the linker's reasoning, I believe it is safer to just assume that the way these variables are allocated is unpredictable, and unstable between subsequent versions of the program.
For your specific issue, I suggest you allocate only one variable to
EEMEM: put everything you need in a single struct
, and allocate that
as a whole. I am quite confident it will always end at address zero.
This should be robust against changes in the program provided that, if
you ever add extra fields to that struct
, you add them at the end.
-
Thank you for your effort to DEBUG this. If I understood correctly the variables change location according to reading instead of storing, or both.krg– krg2023年07月19日 19:18:59 +00:00Commented Jul 19, 2023 at 19:18
-
@krg: I guess it could depend on the order in which they are referenced in the code. But then, again, I would rather avoid second-guessing the linker and treat the locations as unspecified.Edgar Bonet– Edgar Bonet2023年07月19日 20:54:48 +00:00Commented Jul 19, 2023 at 20:54
I wouldn't rely on the compiler necessarily allocating variables at any particular memory address, especially after what you posted. You can make a little include file that simplifies reading from/writing to EEPROM, as below:
#include <Arduino.h> // for type definitions
#include <EEPROM.h>
template <typename T> unsigned int EEPROM_writeAnything (int ee, const T& value)
{
const byte* p = (const byte*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
EEPROM.write(ee++, *p++);
return i;
}
template <typename T> unsigned int EEPROM_readAnything (int ee, T& value)
{
byte* p = (byte*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
*p++ = EEPROM.read(ee++);
return i;
}
Then you can do something like this:
int someData; // declare a local variable
...
EEPROM_readAnything (0, someData); // read into it, from address zero in EEPROM
...
someData++;
EEPROM_writeAnything (0, someData); // write it back out, to address zero
The library above uses templates to manage any size data, so you can write or write ints, arrays, etc. One possibility would be to make a struct and read/write the whole thing every time. That way you don't need to keep track of offsets (the whole struct could be written to address 0).
Here's an example of doing that, using offsetof
to manage the offsets of individual fields inside your overall struct, the thing that is stored in the EEPROM.
#include <EEPROMAnything.h>
const long MAGIC_ID = 'Nick';
// information in EEPROM
typedef struct {
unsigned long magic; // to see if we initialised EEPROM
unsigned long counter;
char myName [30];
} myInformation;
void setup() {
Serial.begin (115200);
Serial.println ("Starting ...");
// see if EEPROM initialised
unsigned long magicTest;
EEPROM_readAnything (offsetof (myInformation, magic), magicTest);
if (magicTest != MAGIC_ID)
{
Serial.println ("Initialising EEPROM");
myInformation initStuff;
initStuff.magic = MAGIC_ID;
initStuff.counter = 0;
strcpy (initStuff.myName, "Nick Gammon");
EEPROM_writeAnything (0, initStuff); // write whole struct
} // end of if not initialised
// find counter
unsigned long myCounter;
EEPROM_readAnything (offsetof (myInformation, counter), myCounter);
Serial.print ("Counter is ");
Serial.println (myCounter);
// find name
char hisName [sizeof (myInformation().myName)];
EEPROM_readAnything (offsetof (myInformation, myName), hisName);
Serial.print ("Name is ");
Serial.println (hisName);
// add to counter
myCounter++;
// write counter back
EEPROM_writeAnything (offsetof (myInformation, counter), myCounter); // write whole struct
Serial.println ("Done.");
} // end of setup
void loop()
{
// whatever
} // end of loop
-
Note that your template functions
EEPROM_{read,write}Anything()
are provided by the standard Arduino EEPROM library since core 1.6.21. They are calledEEPROM.get()
andEEPROM.put()
.Edgar Bonet– Edgar Bonet2023年07月20日 08:02:09 +00:00Commented Jul 20, 2023 at 8:02 -
Oh, wow! That saves keying in my library. :)2023年07月20日 08:42:44 +00:00Commented Jul 20, 2023 at 8:42
EEMEM
variables in more than one compilation unit?EEMEM
variables are in a arduino library. I don't redefine them / change order. But maybe the commands to read / or write theEEMEM
variables change order! I will edit my question with avr-objdump of variables. With every compile it changes the placement.