Срок службы EEPROM и размер страницы
Я хотел бы оценить эффективную (минимальную) выносливость EEPROM Arduino при однобайтовых операциях записи.
The в техническом описании ATmega, используемого на многих платах Arduino, говорится (стр. 20):
ATmega48A/PA/88A/PA/168A/PA/328/P содержит 256/512/512/1 КБ памяти EEPROM данных. Он организован как отдельное пространство данных, в котором можно читать и записывать отдельные байты. EEPROM имеет ресурс не менее 100 000 циклов записи/стирания.
Рассмотрим ATmega328P с 1 КБ EEPROM. Поскольку я могу записывать отдельные байты, если я пройдусь по EEPROM, записывая каждую позицию байта одинаковое количество раз, это составит 1024 * 100 000 операций записи (при условии номинального срока службы).
Но насколько я знаю перед записью стираются не отдельные байты, а страницы/ячейки. На странице 285/таблице 28.12 таблицы указан размер страницы 4 байта. Означает ли это, что указанная минимальная выносливость составляет 1024 * 100 000/4 однобайтовых записи?
@fuenfundachtzig, 👍6
Обсуждение2 ответа
Тестирование записи одного байта
Я запустил тестовый код ночью, чтобы попытаться разобраться в сути проблемы. Возможно, это несколько удивительно, но я набрал более 11 миллионов записей, прежде чем обратное чтение не удалось:
Current count = 11514199
Writes per minute: 17550
....................
Current count = 11538199
Writes per minute: 17550
....................
Current count = 11562199
Writes per minute: 17549
...................
Failed readback!
Expected: FF
Got: 7F
Current count = 11585314
Halted.
Итак, пишет 11,5 М. Тем не менее, как указано в теме AVR Freaks, гарантия 100 000 операций записи в EEPROM дает вам данные срок хранения 20 лет. Возможно, если вы превысите этот лимит, время хранения данных сократится. Для меня не особенно практично проверять, будет ли EEPROM действительным через 20 лет. ;)
Интересно, что на следующий день (когда я это пишу) EEPROM, кажется, восстановился и теперь все еще записывает и читает нормально. Таким образом , может показаться, что после 11 М записей вы можете иногда получить плохое обратное чтение.
В теме AVR Freaks, похоже, перед сбоем было записано где-то от 5 до 8 МБ.
Через несколько часов (2,4 часа) тот же байт снова потерпел неудачу после еще 2 559 219 операций записи!
Current count = 14033314
Writes per minute: 17549
....................
Current count = 14057314
Writes per minute: 17549
....................
Current count = 14081314
Writes per minute: 17549
....................
Current count = 14105314
Writes per minute: 17549
....................
Current count = 14129314
Writes per minute: 17549
............
Failed readback!
Expected: FF
Got: 7F
Current count = 14144533
Halted.
Несколько быстрее пишет третий сбой после еще 149 703:
Current count = 14312533
Writes per minute: 17550
....
Failed readback!
Expected: FF
Got: 7F
Current count = 14318236
Halted.
И еще один сбой (через 2,3 минуты) после того, как еще 41757 пишет:
Starting.
Current count = 14318236
Waiting for pin 2 to go LOW.
Started.
....................
Current count = 14342236
Writes per minute: 17551
..............
Failed readback!
Expected: FF
Got: 7F
Current count = 14359993
Halted.
Проверка соседнего байта
Теперь, когда у меня довольно быстро произошел сбой в несколько перегруженном байте, я скорректировал код для записи следующего байта (1021), который находился бы на той же 4-байтовой «странице».
Четыре часа спустя соседний байт был записан 4,36 миллиона раз без каких-либо ошибок, так что это, по-видимому, подтверждает идею о том, что каждый байт EEPROM может быть записан индивидуально, не затрагивая его соседей.
На следующее утро
Соседний байт (адрес 1021) окончательно завершился неудачно после 12 161 565 операций записи. Таким образом, предположение о том, что каждый байт может быть адресован индивидуально и имеет индивидуальное время жизни, похоже, поддерживается.
Current count = 12120000
Writes per minute: 17562
....................
Current count = 12144000
Writes per minute: 17562
..............
Failed readback!
Expected: FF
Got: 7F
Current count = 12161565
Halted.
Код
Основной эскиз
// Тестер EEPROM
// Автор: Ник Гаммон
// Дата: 22 августа 2015 г.
// Подключите перемычку от GND к контакту D2, когда будете готовы к работе.
// Удалите перемычку для чистого завершения (сохранения текущего счетчика в EEPROM)
// Одиночная вспышка, повторяющаяся каждые полсекунды: готово к работе
// Две вспышки, останавливаются по запросу
// Три вспышки, ошибка
// Случайная вспышка: работает
#include <EEPROMAnything.h>
#include <EEPROM.h>
const unsigned int WRITE_COUNT_ADDRESS = 1010;
const unsigned int TEST_ADDRESS = 1020;
const unsigned int ITERATIONS = 200;
const unsigned long MAX_DOTS = 20;
const byte TESTS [] = { 0x55, 0x00, 0xFF, 0x66, 0xAA, 0x11 };
const byte ACTIVATE_PIN = 2;
const byte LED = 13;
unsigned long dots = 0;
unsigned long writeCount;
unsigned long startTime;
unsigned long initialCount;
void showCount ()
{
Serial.print ("Current count = ");
Serial.println (writeCount);
} // конец showCount
// мигаем светодиодом необходимое количество раз
void flashLED (const int times,
const unsigned long interval = 100,
const unsigned long delayTime = 500)
{
for (int i = 0; i < times; i++)
{
digitalWrite (LED, HIGH);
delay (interval);
digitalWrite (LED, LOW);
delay (interval);
} // конец каждой итерации
delay (delayTime); // задержка между итерациями
} // конец flashLED
// проверяем, соответствует ли наша память тому, что мы ожидаем
void checkMemory (const byte target)
{
byte found = EEPROM.read (TEST_ADDRESS);
if (found != target)
{
Serial.println ();
Serial.println ("Failed readback!");
Serial.print ("Expected: ");
Serial.println ((int) target, HEX);
Serial.print ("Got: ");
Serial.println ((int) found, HEX);
showCount ();
EEPROM_writeAnything (WRITE_COUNT_ADDRESS, writeCount);
Serial.print ("Halted.");
Serial.flush ();
while (true)
flashLED (3); // три вспышки
}
} // конец checkMemory
void setup ()
{
Serial.begin (115200);
Serial.println ();
// получаем счетчик записей из предыдущего запуска
EEPROM_readAnything (WRITE_COUNT_ADDRESS, writeCount);
initialCount = writeCount;
// если все 1 бит, переменная никогда не инициализировалась
if (writeCount == 0xFFFFFFFF)
writeCount = 0;
// сообщаем им, что мы начинаем
Serial.println ("Starting.");
pinMode (2, INPUT_PULLUP);
pinMode (LED, OUTPUT);
showCount ();
// ждем, пока они отключат контакт 2
Serial.println ("Waiting for pin 2 to go LOW.");
while (digitalRead (ACTIVATE_PIN) == HIGH)
flashLED (1); // одна вспышка
Serial.println ("Started.");
startTime = millis ();
} // конец настройки
void loop ()
{
// запись в EEPROM, проверяя каждую запись
for (unsigned int i = 0; i < ITERATIONS; i++)
{
for (unsigned int j = 0; j < sizeof (TESTS); j++)
{
EEPROM.write (TEST_ADDRESS, TESTS [j]);
writeCount++;
checkMemory (TESTS [j]);
} // окончание выполнения каждого тестового шаблона
} // конец цикла из x ИТЕРАЦИЙ
// сохраняем текущий счетчик записей, чтобы мы могли получить его в следующий раз
EEPROM_writeAnything (WRITE_COUNT_ADDRESS, writeCount);
Serial.print (".");
dots++;
// подтверждение, что мы работаем
flashLED (1, 10, 0); // мигает один раз на 10 мс, без дополнительной задержки
// новая строка, показать счетчик
if (dots >= MAX_DOTS)
{
Serial.println ();
dots = 0;
showCount ();
unsigned long writes = writeCount - initialCount;
unsigned long timeTaken = millis () - startTime;
float writesPerMinute = float (writes) / float (timeTaken) * 60.0 * 1000.0;
Serial.print ("Writes per minute: ");
Serial.println ((int) writesPerMinute);
} // конец необходимости перевода строки и вывода счетчика
// проверяем, хочет ли пользователь, чтобы мы остановились, отключив перемычку от Gnd к D2
if (digitalRead (ACTIVATE_PIN) == HIGH)
{
Serial.println ();
Serial.println ("Stop request detected.");
showCount ();
Serial.print ("Halted.");
Serial.flush ();
while (true)
flashLED (2); // две вспышки
}
} // конец цикла
EEPROMAnything.h
#include <Arduino.h> // для определений типов
#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;
}
Код предназначен для ожидания, пока вы не подключите контакт 2 (цифровой контакт 2) к земле, прежде чем запускать. Это необходимо для остановки его выполнения сразу после загрузки, прежде чем вы запустите последовательный монитор. Итак, загрузите код, откройте последовательный монитор, а затем подключите Gnd к контакту D2 и наблюдайте за результатами.
Исправление ошибок
Если проблема в том, что чтение EEPROM может ухудшиться, можно использовать ошибку Рида-Соломона. коррекция для восстановления. Именно этот метод используется для исправления ошибок чтения компакт-дисков, а также QR-кодов. Он может восстанавливаться после ряда ошибок (вы сами выбираете желаемый уровень исправления ошибок — чем больше исправлений ошибок, тем больше памяти требуется).
Мои тесты на данный момент показали, что отказ затрагивает отдельные биты (как и тесты AVR Freaks), так что это вполне может быть способом продлить срок службы и надежность EEPROM.
Тестирование некоторого кода Рида-Соломона, который я нашел, показало, что добавление кодирования и декодирования потребует около 4800 байт программной памяти плюс около 600 байт ОЗУ (512 байтов, необходимых для внутренних таблиц, плюс некоторые дополнительные Рабочее пространство). Кроме того, вам потребуется 256 байт оперативной памяти для блока кода. Эти цифры можно уменьшить, используя меньше битов на «символ». Например, если вы храните 4-битные символы (например, числа BCD), требования к памяти несколько уменьшатся.
Смотрите отредактированный ответ. Второй байт вышел из строя после 12,16 миллиона операций записи, поэтому получается, что даже байты на одной и той же 4-байтовой странице имеют индивидуальное время жизни., @Nick Gammon
На форуме Arduino http://forum.arduino.cc/index.php?topic=293472.0 кто-то утверждает, что размер составляет 1 байт (каждая «страница») — каждая запись записывает и тратит цикл на один байт. Там же написано, что SPI-доступ к eeprom использует размер страницы в 4 байта. Я не стал читать дальше, так как мои сомнения были такими же, как и здесь, о записи байта в eeprom arduino.
- Является ли использование malloc() и free() действительно плохой идеей для Arduino?
- Как читать и записывать EEPROM в ESP8266
- Какой реальный срок службы EEPROM?
- Как запомнить значения переменных после перезагрузки платы Arduino Uno R3
- Получить доступ к EEPROM ATtiny с помощью кода Arduino?
- Очистка EEPROM
- Как сохранить переменную с плавающей запятой в EEPROM
- Spiffs против Eeprom на esp8266
Эта история ходит повсюду, но она [кажется неправдой](http://www.avrfreaks.net/comment/529996#comment-529996)., @Gerben
Я сейчас провожу свой собственный тест по этому поводу. На данный момент у меня есть до миллиона записей в один байт, и он правильно читается. Как только я получу его сбой (что может произойти через 7 часов, судя по сообщению AVR Freaks, я посмотрю, сбой ли соседний байт или нет. И мой собственный тест, и тест AVR Freaks, похоже, подтверждают, что вы можете вероятно, получится более 100 000 записей с одного адреса., @Nick Gammon