Как EEMEM отображает переменные? (avr/eeprom.h)
Я могу сохранить и восстановить свои данные с помощью avr/eeprom.h, если не изменю скетч.
- У меня есть скетч, в котором хранятся данные. После включения и выключения устройства данные в порядке.
- Если я изменяю скетч с помощью операторов 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) как на скетче, так и в библиотеке.
@krg, 👍0
Обсуждение2 ответа
Лучший ответ:
Мне удалось воспроизвести вашу проблему с помощью простого наброска ниже:
#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
Я бы не стал полагаться на то, что компилятор обязательно разместит переменные по какому-либо конкретному адресу памяти, особенно после того, что вы опубликовали. Вы можете создать небольшой включаемый файл, который упростит чтение/запись в 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
- Является ли использование malloc() и free() действительно плохой идеей для Arduino?
- Как читать и записывать EEPROM в ESP8266
- Какой реальный срок службы EEPROM?
- Как запомнить значения переменных после перезагрузки платы Arduino Uno R3
- Получить доступ к EEPROM ATtiny с помощью кода Arduino?
- Очистка EEPROM
- Как сохранить переменную с плавающей запятой в EEPROM
- Spiffs против Eeprom на esp8266
Есть ли у вас переменные 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