Как EEMEM отображает переменные? (avr/eeprom.h)

Я могу сохранить и восстановить свои данные с помощью avr/eeprom.h, если не изменю скетч.

  1. У меня есть скетч, в котором хранятся данные. После включения и выключения устройства данные в порядке.
  2. Если я изменяю скетч с помощью операторов Serial.print (я хочу перечитать данные на экране), скетч ищет данные по неправильным адресам EEPROM, хотя я могу проверить с помощью другого скетча, что с данными все в порядке.

Более подробно: я храню около 50 байт, но проверил местоположение только 4-байтовой переменной. Я вижу, что эта переменная находится в начале EEPROM (адрес: 0). Но второй скетч ищет данные по адресу 30. Даже близко.

Насколько я понимаю, avr/eeprom.h более прост в использовании, поскольку вам не нужно контролировать расположение данных. Но похоже, что если изменить (что?) в вашем скетче, он ищет данные в других местах, хотя с eeprom все в порядке.

Другими словами, это похоже на то, что если я меняю прошивку (а не переменные/определения EEMEM!), эта прошивка ищет данные в разных местах.

Я что-то не так понял?

В моей ситуации лучше использовать Arduino EEPROM.h со статическим расположением адресов EEPROM?

Забавный факт. Google Bard, кажется, понял это совершенно неправильно:

Чем отличаются avr/eeprom.h и EEPROM.h от Arduino?

(Ответ Google Барда)

Библиотеки avr/eeprom.h и EEPROM.h в Arduino используются для доступа к памяти EEPROM на микроконтроллерах AVR. Однако между этими двумя библиотеками есть некоторые ключевые различия. avr/eeprom.h — это библиотека нижнего уровня, обеспечивающая прямой доступ к регистрам EEPROM. Это означает, что его можно использовать для доступа к любому месту в EEPROM, но он также требует больше кода и может быть более подвержен ошибкам. EEPROM.h — это библиотека более высокого уровня, обеспечивающая более удобный интерфейс для EEPROM. Он позволяет получить доступ только к адресам, находящимся в диапазоне EEPROM, но его проще использовать и он менее подвержен ошибкам. В общем, avr/eeprom.h следует использовать только в том случае, если вам нужен доступ к определенным местам в EEPROM, которые находятся за пределами диапазона EEPROM.h. В противном случае предпочтительнее использовать EEPROM.h.

Изменить: avr-objdump раздела eeprom

Печать в Sketch Без отпечатков Печать как в библиотеке, так и в 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>:

Изображение сохранено по историческим причинам.

1-е окно: Serial.print(s) только в скетче. 2-е окно: без Serial.print(s) 3-е окно: Serial.print(s) как на скетче, так и в библиотеке. EEMEM из avr-objdump -D

, 👍0

Обсуждение

Есть ли у вас переменные EEMEM более чем в одной единице компиляции?, @Edgar Bonet

Все переменные EEMEM находятся в библиотеке Arduino. Я не переопределяю их/меняю порядок. Но, возможно, команды чтения/записи переменных EEMEM изменят порядок! Я отредактирую свой вопрос с помощью avr-objdump переменных. При каждой компиляции расположение меняется., @krg

Под «_в библиотеке Arduino_» вы имеете в виду заголовочный файл библиотеки или файл(ы) библиотеки C++?, @Edgar Bonet

В библиотеке Arduino есть файлы .h и .cpp. Переменные EEMEM находятся только в файле библиотеки .cpp. В моем скетче нет переменных EEMEM. Я обновил вопрос, сравнивая адреса EEMEM с различными «параметрами» компиляции. В своем скетче я не меняю события. Я просто добавляю операторы Serial.print для отладки. Даже если я определяю операторы печати, переменные EEMEM меняются местами., @krg

пожалуйста, не размещайте текст в виде изображения... скопируйте текст и вставьте его в сообщение... отформатируйте его как код, @jsotola

Я пытался. Я писал перед изображением, что сложно заполнить таблицу для удобства сравнения., @krg

Таким образом, вы возлагаете на нас, посетителей, бремя, повторяемое для каждого отдельного посетителя, вместо того, чтобы потратить свое время в качестве задающего вопрос только один раз. :-}, @the busybee

В моем компьютере таблица не поместилась, но поскольку она важна как минимум для двух человек, я внес правку., @krg


2 ответа


Лучший ответ:

1

Мне удалось воспроизвести вашу проблему с помощью простого наброска ниже:

#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(){}

Как есть, он печатает:

int8_ee: 46
int16_ee: 44
int32_ee: 40
str_ee: 0

Если я #define SWAP_PRINTING_VARS, вместо этого он печатает:

int8_ee: 46
int32_ee: 42
int16_ee: 40
str_ee: 0

У меня нет правильного ответа на ваш вопрос. Хотя можно попытаться раскрыть рассуждения компоновщика, я считаю, что безопаснее просто предположить, что способ распределения этих переменных непредсказуем и нестабилен между последующие версии программы.

Для вашей конкретной проблемы я предлагаю вам выделить только одну переменную для EEMEM: поместите все, что вам нужно, в одну структуру и выделите ее в целом. Я совершенно уверен, что он всегда закончится по нулевому адресу. Это должно быть устойчиво к изменениям в программе при условии, что, если если вы когда-либо добавляете дополнительные поля в эту структуру, вы добавляете их в конце.

,

Спасибо за ваши усилия по ОТЛАДКЕ этого. Если я правильно понял, переменные меняют местоположение в зависимости от чтения, а не сохранения, или того и другого., @krg

@krg: Я думаю, это может зависеть от порядка, в котором они упоминаются в коде. Но опять же, я бы предпочел избегать сомнений в отношении компоновщика и рассматривать местоположения как _неуказанные_., @Edgar Bonet


0

Я бы не стал полагаться на то, что компилятор обязательно разместит переменные по какому-либо конкретному адресу памяти, особенно после того, что вы опубликовали. Вы можете создать небольшой включаемый файл, который упростит чтение/запись в EEPROM, как показано ниже:

#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;
}

Тогда вы можете сделать что-то вроде этого:

int someData;  // объявляем локальную переменную


...


EEPROM_readAnything (0, someData);  // считываем с нулевого адреса в EEPROM

...

someData++;
EEPROM_writeAnything (0, someData);  // записываем его обратно по нулевому адресу

Приведенная выше библиотека использует шаблоны для управления данными любого размера, поэтому вы можете записывать или писать целые числа, массивы и т. д. Один из вариантов — создать структуру и каждый раз читать/записывать все целиком. Таким образом, вам не нужно отслеживать смещения (вся структура может быть записана по адресу 0).


Вот пример использования offsetof для управления смещениями отдельных полей внутри вашей общей структуры, которая хранится в EEPROM.

#include <EEPROMAnything.h>

const long MAGIC_ID = 'Nick';

// информация в EEPROM
typedef struct {
    unsigned long magic;   // чтобы проверить, инициализировали ли мы EEPROM
    unsigned long counter;
    char myName [30];
}  myInformation;


void setup() {
  Serial.begin (115200);
  Serial.println ("Starting ...");

  // проверяем, инициализирована ли EEPROM

  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); // пишем всю структуру
    } // конец, если не инициализировано


  // находим счетчик
  unsigned long myCounter;
  EEPROM_readAnything (offsetof (myInformation, counter), myCounter);
  Serial.print ("Counter is ");
  Serial.println (myCounter);

  // находим имя
  char hisName [sizeof (myInformation().myName)];
  EEPROM_readAnything (offsetof (myInformation, myName), hisName);
  Serial.print ("Name is ");
  Serial.println (hisName);

  // добавляем в счетчик
  myCounter++;

  // записываем счетчик обратно
  EEPROM_writeAnything (offsetof (myInformation, counter), myCounter); // пишем всю структуру

  Serial.println ("Done.");
  
} // конец настройки

void loop() 
{
  // что бы ни
}   // конец цикла
,

Обратите внимание, что функции вашего шаблона EEPROM_{read,write}Anything() предоставляются стандартной библиотекой EEPROM Arduino, начиная с ядра 1.6.21. Они называются EEPROM.get() и EEPROM.put()., @Edgar Bonet

Ух ты! Это экономит время на вводе данных в мою библиотеку. :), @Nick Gammon