0

I can store and restore my data with avr/eeprom.h if I don't change the sketch.

  1. I have a sketch that stores the data. After power cycle of the device the data are fine.
  2. 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

8
  • Do you have EEMEM variables in more than one compilation unit? Commented Jul 19, 2023 at 14:58
  • All EEMEM variables are in a arduino library. I don't redefine them / change order. But maybe the commands to read / or write the EEMEM variables change order! I will edit my question with avr-objdump of variables. With every compile it changes the placement.
    – krg
    Commented Jul 19, 2023 at 15:41
  • By “in a arduino library” you mean in a library's header file or in a library's C++ file(s)? Commented Jul 19, 2023 at 15:47
  • The arduino library has both .h and .cpp files. EEMEM variables are only in .cpp file of library. In my sketch I don't have EEMEM variables. I have updated the question with comparison of EEMEM addresses with different compile "options". In my sketch I don't change the events. I just add Serial.print statements for debugging. Even if I define print statements, the EEMEM variables change place.
    – krg
    Commented Jul 19, 2023 at 16:00
  • 2
    please do not post any text as a picture ... copy the text and paste it into the post ... format it as code
    – jsotola
    Commented Jul 19, 2023 at 18:32

2 Answers 2

1

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.

2
  • 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
    Commented 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. Commented Jul 19, 2023 at 20:54
0

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
2
  • Note that your template functions EEPROM_{read,write}Anything() are provided by the standard Arduino EEPROM library since core 1.6.21. They are called EEPROM.get() and EEPROM.put(). Commented Jul 20, 2023 at 8:02
  • Oh, wow! That saves keying in my library. :)
    – Nick Gammon
    Commented Jul 20, 2023 at 8:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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