Про ресурс в сто тысяч циклов на ячейку написано везде, и всё равно мой термостат однажды приехал на сервис с мёртвыми настройками — при том, что «настройки меняются кнопкой, ну раз в день». Аудит показал невинную строку, которую я добавил «на минутку»: лог порога в EEPROM после каждого срабатывания реле. Реле щёлкало каждые сорок секунд, ячейка прожила два месяца, и никакая аккуратность с update её не спасла. Мораль в том, что паспорт носителя узнаёт не даташит, а ваш собственный скетч: пока вы не перечислили все точки записи с их частотами, ресурс — не число, а чувство.
Расчётную часть ресурса я уже разбирал в уроке про расчёт износа по частоте и в уроке про бюджет циклов до прошивки — там формулы и кольца. Здесь практика в два слоя: аудит живого скетча, который находит такие вот «минутные» строки, и полевые симптомы умирающей ячейки, чтобы вы узнали износ в лицо, когда он уже случился. Базовую механику обновления ячеек брал из урока про сто тысяч циклов, здесь — то, что вокруг расчёта.
Аудит: инвентаризация всех пишущих строк
Откройте скетч и выпишите в одну таблицу каждое место, где встречается EEPROM.write, EEPROM.update или EEPROM.put. Для каждой строки честно ответьте на один вопрос: когда эта строка реально выполняется? Не «как задумывалось», а когда: на каждое включение, на каждое нажатие кнопки, на каждое срабатывание датчика, раз в час по таймеру. Частоту умножьте на число затрагиваемых ячеек — update трогает только изменённые байты, put тоже, но структура из шести полей при каждом событии ест шесть ячеек, а не одну.
Дальше арифметика приговоров: циклов в сутки умножить на 365 — циклов в год; сто тысяч поделить на это — годы жизни самой нагруженной ячейки. Раз в сутки — двести семьдесят четыре года, раз в час — одиннадцать лет, раз в минуту — два месяца, раз в десять секунд — меньше двух недель. Все мои поломки EEPROM за пять лет сидели в двух категориях: строка, добавленная «на минутку», и событие, которое происходило чаще, чем я думал, — реле, дребезг кнопки без антидребезга, датчик на границе порога. Про последний случай отдельно: запись по факту «значение изменилось» с проверкой на дрожь у порога — сначала гистерезис из урока про пороги и кнопку, потом запись.
#include <EEPROM.h> const unsigned long REPORT_MS = 3600000UL; // отчёт раз в часconst unsigned long DAY_MS = 86400000UL; // сутки unsigned long writes = 0; // все update/put за текущие суткиunsigned long totalWrites = 0; // всего с момента стартаunsigned long nextReport = 0;unsigned long dayStarted = 0; // Обёртка: вся запись в скетче идёт только через неё - иначе счётчик врётvoid eeSave(int addr, byte value) { EEPROM.update(addr, value); writes += 1; totalWrites += 1;} void setup() { Serial.begin(9600); nextReport = millis() + REPORT_MS; dayStarted = millis(); Serial.println("аудит: счётчик циклов записи включён");} void loop() { // ... рабочий код скетча; каждая запись - через eeSave() ... if (millis() >= nextReport) { nextReport += REPORT_MS; Serial.print("[аудит] за последний час: "); Serial.print(writes); Serial.println(" записей"); Serial.print("[аудит] прогноз ячейки: "); if (writes == 0) { Serial.println("записей нет, ресурс бесконечен"); } else { unsigned long perDay = writes * 24UL; Serial.print(perDay); Serial.print(" в сутки, "); Serial.print(100000UL / perDay); Serial.println(" суток на ячейке при этом ритме"); } } if (millis() - dayStarted >= DAY_MS) { // сутки замерли - сброс Serial.print("[аудит] сутки: "); Serial.print(totalWrites); Serial.println(" записей суммарно"); writes = 0; totalWrites = 0; dayStarted += DAY_MS; }}Модель делает ровно то, что делает счётчик в code-блоке, только сутки спрессованы в минуту: передвиньте ползунок — и вердикт меняется от «десятилетия» до «недели» на глазах, LED показывает плотность записей, а кнопка добавляет ручное событие поверх расписания. На плате счётчик живёт в ОЗУ и не претендует на точность до единицы — его задача поймать порядок: двадцать записей в час или две тысячи.
Симптомы умирающей ячейки из моей практики, по нарастающей. Сначала отдельные биты перестают пробиваться: записали 0x3C, прочитали 0x7C — единица там, где был ноль; записанное через update значение не подтверждается чтением, и первая жертва — как раз контрольные суммы и маркеры. Потом ячейка начинает «плыть»: прочитанное значение меняется без записи, битовая пыль оседает за часы. В финале ячейка залипает в 0xFF или 0x00 и не отвечает ни на какую запись. Диагностический тест на месте: записать в подозрительную ячейку 0x00, прочитать, записать 0xFF, прочитать — ячейка, которая в любой из четырёх проб вернула не то, доживает последние недели, и данные с неё пора переносить. Лечится переносом: кольцо из N ячеек умножает ресурс на N, механика в уроке про wear leveling своими руками, а при частотах выше минуты в которую-либо ячейку — внешний носитель.
| Точка записи в скетче | Частота | Ячеек за раз | Вердикт на 100000 циклов |
|---|---|---|---|
| счётчик включений в setup | раз в сутки | 3 (двойник+маркер) | ~91 год — не думать |
| настройки по кнопке | 10 в день | 6 | ~4.5 года — норма |
| порог срабатывания реле | 90 в час | 1 | ~46 суток — уже кольцо |
| лог значения с датчика | раз в минуту | 1 | ~69 суток — не EEPROM |
| каждое показание с гистерезисом порога | десятки в час | 2 | недели — переносить на SD |
План Б по нарастающей: когда кольцо из десятка ячеек уже не спасает, переезжайте. Внешняя 24C256 даёт тридцать две тысячи ячеек за копейки и подключается по I2C — механика в уроке про 24C256; ресурс у неё тот же на ячейку, но ячеек в тридцать раз больше, и кольцо получается в тысячу слотов. Лог вообще без ограничений по циклам живёт на SD — карта не имеет паспортного предела на сектор, износ там размазан контроллером, и минутные записи ложатся годами, что проверено погодным логгером из соседнего раздела. И самый дешёвый носитель — вообще не писать: большая часть того, что я когда-то складировал в EEPROM, прекрасно живёт в ОЗУ, а в память уходит одно итоговое значение раз в час.
И финальное правило, которое я вывесил над рабочим столом после термостата: каждая строка с EEPROM.update проходит аудиторский вопрос «сколько раз в сутки ты реально выполнишься?». Если ответ неизвестен — сначала счётчик записи, потом прошивка. Ресурс EEPROM — не паспортная цифра, а договор между вашей логикой и носителем, и договор этот вы подписываете каждой строкой кода.
- 1. Строка пишет 1 ячейку раз в 10 секунд. Сколько проживёт ячейка?
- 2. Первый симптом умирающей ячейки EEPROM на практике?
- 3. Что делать с записью по факту изменения на датчике у порога?
- Аудит — это список всех EEPROM.write/update/put с честной частотой выполнения каждой строки.
- Вся запись через одну обёртку со счётчиком: порядок частоты в часах-сутках важнее точности.
- Ориентиры: раз в сутки — века, раз в час — годы, раз в минуту — месяцы, раз в 10 секунд — дни.
- Симптомы износа: биты не пробиваются, значение плывёт без записи, финал — залипание в 0xFF/0x00.
- План Б по нарастающей: гистерезис, кольцо ячеек, внешняя 24C256, лог на SD, отказ от записи.
Частые вопросы по теме урока
- Строка пишет 1 ячейку раз в 10 секунд. Сколько проживёт ячейка?
- 8640 записей в сутки делят 100000 циклов примерно на 11.5 суток. Раз в десять секунд EEPROM писать нельзя — нужны кольцо или другой носитель. Около двух недель
- Первый симптом умирающей ячейки EEPROM на практике?
- Деградация начинается с отдельных битов: нули записываются в единицы не полностью. Проверка «записал 0x00 — прочитал — записал 0xFF — прочитал» показывает битовую пыль сразу. Отдельные биты не пробиваются: записали 0x3C, читается 0x7C
- Что делать с записью по факту изменения на датчике у порога?
- Дрожь у порога даёт десятки записей в час. Гистерезис с запасом вокруг порога и millis-пауза режут частоту в десятки-сотни раз до того, как ячейка это заметит. Добавить гистерезис и паузу между записями