Значение неинициализированной EEPROM в ESP8266

В ходе моего ограниченного тестирования выяснилось, что EEPROM в Wemos D1 не инициализирован до 255, как указано в спецификациях, как в случае с Arduino на базе AtMega. На самом деле значения выглядят более или менее случайными. Предположительно, это связано с тем, что ESP8266 на самом деле не содержит EEPROM, а вместо этого эмулирует его с помощью флэш-памяти. Есть ли способ инициализировать регион EEPROM при прошивке?

(Это почти тот же вопрос, что и здесь, на который нет ответа.) Поскольку мне нужно всего несколько байтов данных, я Я бы предпочел не использовать SPIFS.

Обновление:

Сценарий использования: я использую библиотеку WiFiManager, чтобы выбрать сеть для подключения Wemos D1 mini к Интернету и загрузки данных в Thingspeak. Я хотел бы использовать WiFiManager для запроса API-ключа и идентификатора канала у пользователя. Однако, как только пользователь введет их, я бы предпочел не заставлять его снова вводить значения. (Ключ API состоит из 16 случайных символов, и его сложно ввести правильно.) Это означает, что устройство должно постоянно сохранять значения между загрузками — основной вариант использования EEPROM. Но как определить, что значения в EEPROM (фактически мигающие на устройстве ESP) — это неинициализированная чушь, а не предыдущее значение? Как предложено в комментариях, установка значений флагов в нескольких переменных делает статистически маловероятным случайные значения из неинициализированной памяти. На устройстве AtMega, если оно не использовалось ранее, значения будут равны 255 в каждом байте.

, 👍2

Обсуждение

Вы должны решить это в своем эскизе. Так должно быть в первую очередь. Даже при использовании Atmega опасно полагаться на то, что EEPROM будет полностью 0xff. Когда вы используете Atmega для тестирования и забыли об этом, EEPROM может перестать быть 0xff. Вопрос тот же, по правилам stackexchange думаю этот вопрос будет закрыт., @Jot

Просто купите I2C EEPROM. Гораздо лучшее решение., @Pararera

@Jot — За исключением использования постоянной памяти, такой как EEPROM, программа (скетч) остается одинаковой каждый раз, когда вы ее запускаете. Как вы предлагаете «решить это в своем скетче», если цель состоит в том, чтобы определить, запускалось ли оно каждый раз с момента перепрошивки устройства?, @Llaves

Имейте отдельный эскиз для очистки EEPROM. Сначала загрузите его, запустите, а затем загрузите свой собственный эскиз., @Gerben

Альтернативно; запишите примерно 10 постоянных значений в 10 неиспользуемых ячеек флэш-памяти. При загрузке проверьте, содержат ли эти 10 местоположений эти 10 констант. Если они этого не знают, вы знаете, что у вас неинициализированная флэш-память. Вероятность того, что неинициализированная флэш-память содержит эти 10 констант, довольно мала., @Gerben

@Gerben - ESP8266 не содержит фактической EEPROM, он имитирует поведение с помощью флэш-памяти. Очистка «EEPROM» с помощью отдельной программы в этом случае не удастся, поскольку флэш-память будет перезаписана при записи новой программы., @Llaves

@Gerben - я подумал об идее написания нескольких значений и проверки и могу попробовать, если никто не предложит реального решения. Как вы заметили, это не гарантировано, но статистически довольно безопасно (если только последствия ошибки не велики, чего в моем приложении нет), @Llaves

В случае ESP Flash и EEPROM — это одно и то же. Я думаю, что они [всегда используют одно и то же место](https://github.com/esp8266/Arduino/blob/master/libraries/EEPROM/EEPROM.cpp#L44) Flash/EEPROM для хранения «поддельных» данных EEPROM. ., @Gerben

Вы можете добавить к данным идентификатор и контрольную сумму. Когда скетч скомпилирован, можно указать дату и время компиляции в скетче с помощью __ DATE __ и __ TIME __. Если вы проверите их и сохраните в eeprom, вы узнаете, был ли загружен новый скетч. Пожалуйста, сообщите нам причину вашего вопроса. Какую проблему ты пытаешься решить?, @Jot

Дело не в том, чтобы узнать, был ли загружен новый эскиз. Это сохранение данных между перезагрузками. То, что вы предлагаете, для другой цели требует, чтобы данные сохранялись при перепрошивке памяти. Я написал программу быстрого тестирования и могу проверить (по крайней мере, для моего простого теста), что смоделированная EEPROM сохраняется после перепрошивки. Это говорит о том, что устройство можно «подготовить», запустив программу, которая инициализирует EEPROM некоторым известным значением, затем после этого прошивается нужная программа, которая может зависеть от известного состояния EEPROM., @Llaves

Я очень на это надеюсь, иначе придется каждый раз вводить данные калибровки или пароль Wi-Fi. «Гарантируют» ли они, что ценности останутся там? Когда вы используете @Jot, я получаю уведомление., @Jot


3 ответа


0

Раньше я делал это, просто проверяя наличие строки (если хотите, массива символов) в определенном месте памяти. Я использовал «ИНИЦИАЛИЗИРОВАНО». Очень маловероятно, чтобы ПЗУ случайно имело такую последовательность символов. Если я нахожу строку, я читаю переменные из ПЗУ. Если нет, я пишу строку и устанавливаю для переменных значения по умолчанию.

Я нашел немного другой пример, в котором используется та же техника:

// Библиотека EEPROM.
#include <EEPROM.h>
// Постоянное состояние обуви.
byte shoeState = 0;
// Количество состояний обуви.
#define SHOE_STATE_MAX 3 

// setup() запускается один раз при сбросе.
void setup() {     
  // Базовый адрес кода инициализации EEPROM
  #define EEPROM_INIT_CODE_ADDR 0x00
  // Проверка кода инициализации EEPROM
  byte shoeStateInit[] = {0xde, 0xad, 0xbe, 0xaf, 0xaa};
  for(int b = EEPROM_INIT_CODE_ADDR; b < EEPROM_INIT_CODE_ADDR + sizeof(shoeStateInit); b++) {
    // EEPROM = Код инициализации?
    if(EEPROM.read(b) != shoeStateInit[b]) {
      // Нет, записать код инициализации в EEPROM.
      EEPROM.write(b, shoeStateInit[b]);
      // Устанавливаем флаг неинициализации.
      shoeState = -1;
    }
  }
  // Код инициализации EEPROM?
  if(shoeState == 0) {
    // Да, считываем состояние обуви из EEPROM.
    shoeState = EEPROM.read(sizeof(shoeStateInit));
  }
}

Единственная переменная, сохраняемая в ПЗУ, — это ShoesState.

,

Во время тестирования вам понадобится еще один эскиз или какой-нибудь способ очистить память флагов., @linhartr22


2

Вычислите контрольную сумму данных и сохраните ее как часть данных. Пересчитайте контрольную сумму, когда вы снова прочтете (сомнительные) данные. Если контрольная сумма неверна, она недействительна. Если он пройдет, данные действительны с некоторым статистическим уровнем достоверности.

16-битная контрольная сумма сделает ложное принятие крайне маловероятным, но сделайте ее достаточно длинной для вашего собственного уровня комфорта. Каким будет худшее последствие ложного принятия: неудобство (пользователю придется повторно вводить номер)? Потеря денег или имущества? Потеря жизни? Соответственно выберите сложность вашего решения.

,

Это лучший, самый разумный ответ на этот вопрос. Другие ответы включают «не используйте EEPROM» и «полагайтесь на наличие магического значения». Мне определенно больше всего нравится ваша стратегия, и, как вы можете сказать, ее можно расширить для повышения уверенности (возможно, лучший тип хеша вместо просто контрольной суммы). Я бы добавил, что у меня будет какая-то возможность выполнять «миграцию», то есть хранить версию EEPROM и, если значения добавляются/изменяются, предлагать возможность обновления до нового формата., @Brett Gmoser


1

Вместо использования EEPROM, что очень сложно, учитывая, что оно используется всеми хранимыми данными и вам нужно запоминать смещения и длины, используйте SPIFFS.

Включив FS.h в свою программу (или включаемый файл, в котором необходимо хранить данные, если вы пытаетесь сделать ее модульной), вы можете хранить несколько мегабайт данных в виде строк в именованный файл (так же, как fprintf() или open() позволяют это сделать в C++).

Вызовите SPIFFS.begin(); в setup(), а затем используйте следующие разделы кода для чтения или записи данных в файл:

(при условии, что вы хотите сохранить start и end в SPIFFS, но это работает для любого типа данных, известного Arduino, и, возможно, для любого типа данных с атрибутом printable)

>

Читать сохраненную конфигурацию:

Serial.println(F("Loading config"));
File f = SPIFFS.open("/ACTIVE_TIMES.cnf", "r");
if (!f) {
  //Файл не существует — сначала запустите или кто-то вызвал format()
  //Не создаст файл; запустите код сохранения, чтобы это сделать (здесь нет необходимости, поскольку
  // оно не изменилось)
  Serial.println(F("Failed to open config file"));
  start = 8;
  end = 20; // значения по умолчанию
  return;
}
while (f.available()) {
  String key = f.readStringUntil('=');
  String value = f.readStringUntil('\r');
  f.read();
  Serial.println(key + F(" = [") + value + ']');
  Serial.println(key.length());
  if (key == F("START_TIME")) {
    start = value.toInt();
  }
  if (key == F("END_TIME")) {
    end = value.toInt();
  }
}
f.close();

Сохранить конфигурацию:

Serial.println(F("Saving config"));
File f = SPIFFS.open("/ACTIVE_TIMES.cnf", "w+");
if (!f) {
  //В таких случаях вызов SPIFFS.format() может быть хорошей идеей,
  //но здесь вы бы не хотели этого делать, поскольку если это ошибка для
  //другая причина, по которой все ваши данные будут удалены. Сделайте это вручную или подсказкой
  //вместо.
  Serial.println(F("Failed to open config file"));
  return;
}
f.print(F("START_TIME="));
f.println(start);
f.print(F("END_TIME="));
f.println(end);
f.flush();
f.close();
Serial.println(F("Saved values"));

Вы также можете вызвать SPIFFS.format() для сброса файловой системы (скажем, для возврата к заводским настройкам), но это вызовет проблемы при попытке прочитать любые сохраненные данные, поскольку файл не будет существовать. Таким образом, создайте раздел, который устанавливает разумные значения по умолчанию (или просто предлагает пользователю установить значения).

Примечание. Поскольку это предназначено для хранения ключа API и тому подобного, просто оставьте его неудачным, поскольку ключ API не работает — это хорошее уведомление, что пользователю необходимо его настроить, и вы НЕ хотите поставлять продукт с

em>рабочий ключ API, который принадлежит вам, а не пользователю.

Последнее предупреждение: внимательно обратите внимание на использование F() вокруг определенных элементов этого кода. Этот макрос, специфичный для Arduino, позволяет хранить данные ТОЛЬКО во Flash, где строки обычно также потребляют ОЗУ (после загрузки системы они загружаются в ОЗУ, поскольку большая часть кода ожидает их там). Преимущество F(), а не директивы progmem, заключается в том, что многие функции, встроенные в Arduino, могут читать его напрямую благодаря перегрузкам, или вы можете обернуть String () вокруг F(), чтобы преобразовать его в строку во время выполнения, если функция не допускает такую перегрузку. Конечным результатом является то, что, как я написал, этот код должен потреблять ОЗУ только тогда, когда он действительно работает, как и должен хороший код (в противном случае на таких встроенных устройствах очень легко использовать всю вашу оперативную память, если вы добавите достаточно строк ).

Я не удосужился сделать последнее с именем файла, переданным в open(), поскольку оно используется только один раз за операцию загрузки или сохранения и не поддерживает F() (поэтому мне нужно использовать трюк String()), и я совершенно уверен, что компилятор затем разделяет эту область памяти, сокращая объем используемой оперативной памяти до одной редакции этого имени файла.

,