В щитке висит счётчик, который считает квартиру целиком: стиральная машина, холодильник и забытый обогреватель в гараже идут одной строкой. Когда я захотел узнать, сколько на самом деле съедает морозилка, ответ оказался делом техники: датчик тока, плата и память, которая не забывает итог при выдернутой вилке. Без последнего пункта затея бессмысленна — счётчик, обнуляющийся каждым отключением, не считает ничего, он просто крутится на месте.
От старой темы эту отличает сам предмет копилки. Моточасы отвечают на вопрос «сколько работало»: каждая секунда под нагрузкой стоит ровно один отсчёт, тянул прибор киловатт или ночник — неважно. Ватт-часы отвечают на «сколько потребило»: секунда чайника и секунда светодиода отличаются в пятьдесят раз, поэтому копить нужно не время, а произведение мощности на время — энергию. Механика бережной записи у них общая, продукт — разный.
План урока: мощность P = U×I с ползунком в роли датчика тока, интеграл — сумма слагаемых за тик, фиксация накопленного в EEPROM раз в пять минут по millis и арифметика износа, которую считаю до пайки, а не после первого отказа. В симуляторе время честно поджато: тик секунда вместо минуты, запись раз в 15 секунд вместо пяти минут — механика при этом один в один.
Мощность против энергии: что именно копим
Мощность — произведение напряжения на ток: P = U × I. Напряжение в розетке известно — 230 В; ток измеряет датчик, и в макетке его роль играет ползунок на A0: коды 0-1023 разворачиваются в 0-10,0 А, то есть от 0 до 2300 Вт. Связь тока, напряжения и сопротивления — закон Ома, мы считали его в отдельном уроке, здесь же интересует произведение величин, а не падение на резисторе.
Энергия — мощность, проинтегрированная по времени. Щитовой счётчик делает это непрерывно, скетч — тиками. Раз в минуту он спрашивает ток, считает мощность и добавляет к накопителю P×60 ватт-секунд; когда в накопителе набирается 3600 ватт-секунд — это один ватт-час, единица щитового счётчика. Сам накопитель живёт в оперативной памяти, которая изнашиваться не умеет вовсе: хоть миллион обновлений в секунду — ей безразлично.
Расклад по месту такой: ток — только чтение, сумма ватт-секунд — RAM, итоговые ватт-часы — EEPROM. Цена сбоя питания — неучтённый хвост текущего пятиминутного интервала: при нагрузке в киловатт это десятая доля ватт-часа на фоне сотен накопленных. Тот же компромисс, что у моточасов, только шаг фиксации выбран под темп роста именно энергии.
| Величина | Где живёт | Сколько места | Как обновляется |
|---|---|---|---|
| Ток с датчика | A0, чтение каждый тик | ничего не занимает | аналоговое чтение; в память не пишется |
| Сумма ватт-секунд | RAM | unsigned long, 4 байта | каждый тик: += P × длительность тика |
| Ватт-часы, итог | EEPROM, байты 10-13 | 4 байта | раз в 5 минут, update изменившихся байтов |
| Флаг заведённости | EEPROM, байт 9 | 1 байт | пишется один раз, дальше только читается |
#include <EEPROM.h> const int PIN_POT = A0; // «датчик тока»: 0-1023 -> 0-10.0 Аconst int PIN_BTN = 2; // двойное нажатие — сброс счётчикаconst int PIN_LED = 12; // вспышка на каждой записи в EEPROM const long U = 230; // напряжение сети, Вconst unsigned long TICK = 60000; // шаг интеграла: минутаconst unsigned long PERIOD = 300000; // период записи: 5 минут const byte ADDR_MAGIC = 9; // 90 — счётчик уже заведёнconst byte ADDR_WH0 = 10; // ватт-часы: четыре байта 10-13 unsigned long totalWs = 0; // ватт-секунды с последнего стартаunsigned long tTick = 0;unsigned long tSave = 0;unsigned long tBtn = 0;int prevBtn = HIGH;int flashes = 0; unsigned long readWh() { return (unsigned long)EEPROM.read(ADDR_WH0) + (unsigned long)EEPROM.read(ADDR_WH0 + 1) * 256UL + (unsigned long)EEPROM.read(ADDR_WH0 + 2) * 65536UL + (unsigned long)EEPROM.read(ADDR_WH0 + 3) * 16777216UL;} void saveWh() { unsigned long wh = totalWs / 3600; EEPROM.update(ADDR_WH0, wh & 255); EEPROM.update(ADDR_WH0 + 1, (wh >> 8) & 255); EEPROM.update(ADDR_WH0 + 2, (wh >> 16) & 255); EEPROM.update(ADDR_WH0 + 3, (wh >> 24) & 255); EEPROM.update(ADDR_MAGIC, 90); flashes = 3; // три вспышки — запись уехала в память} void setup() { Serial.begin(9600); pinMode(PIN_LED, OUTPUT); pinMode(PIN_BTN, INPUT_PULLUP); if (EEPROM.read(ADDR_MAGIC) == 90) { unsigned long stored = readWh(); totalWs = stored * 3600UL; Serial.print("start: v pamyati wh="); Serial.println(stored); } else { Serial.println("start: chistaya pamyat, schetchik s nulya"); } tTick = millis(); tSave = millis(); tBtn = millis();} void loop() { if (millis() - tTick >= TICK) { // интеграл: за тик добавляем P*1 с tTick += TICK; long i10 = map(analogRead(PIN_POT), 0, 1023, 0, 100); // 0-10.0 А long p = U * i10 / 10; // 230 В * ток totalWs += p; Serial.print("p="); Serial.print(p); Serial.print("W sum="); Serial.println(totalWs / 3600); } if (millis() - tSave >= PERIOD) { // фиксация раз в 5 минут tSave += PERIOD; saveWh(); Serial.print("zapis v EEPROM: wh="); Serial.println(totalWs / 3600); } int btn = digitalRead(PIN_BTN); // двойное нажатие — сброс if (btn == LOW && prevBtn == HIGH) { if (millis() - tBtn < 600) { totalWs = 0; saveWh(); Serial.println("SBROS: schetchik obnuljon i zapisan"); flashes = 6; } tBtn = millis(); } prevBtn = btn; if (flashes > 0) { digitalWrite(PIN_LED, HIGH); delay(60); digitalWrite(PIN_LED, LOW); flashes = flashes - 1; delay(60); } delay(20);}Три места скетча держат всю механику. readWh собирает число из четырёх ячеек умножением на 256UL, 65536UL и 16777216UL, а не сдвигами на 8, 16 и 24: на AVR int шестнадцатибитный, и сдвиг байта на 24 внутри int срезает старшие разряды — классическая грабля при упаковке длинных счётчиков, я собрал её лично. Приём раскладки типов по ячейкам подробно разобран в уроке про байты, а unsigned long даёт потолок 4,29 млрд ватт-часов — домашнему счётчику хватит на столетие непрерывного чайника.
saveWh пишет через update, и это не перестраховка: за одну фиксацию меняются обычно один-два младших байта, старшие лежат нетронутыми неделями и не тратят ни цикла — механика из урока про update. Запись одного байта занимает около 3,3 мс, четвёрка — около 13 мс раз в пять минут. Плата не моргнёт, пользователь не заметит, и главное — окно, внутри которого прибор уязвим для выключения света, узкое как щель.
Проверка magic в setup обязательна и на чистой плате: без неё четыре ячейки, читающиеся как 255, сложатся в 4,29 млрд ватт-часов, и счётчик начнёт жизнь с рекордного показания. Число 90 в ячейке 9 — та же идея контрольного байта, что в самом первом уроке раздела: по одному байту скетч отличает новую плату от уже ведущей учёт.
Арифметика износа: двенадцать записей в час
Считаем по худшей ячейке — младшей, она меняется почти при каждой фиксации. Интервал пять минут — это 12 записей в час и 288 в сутки; делим паспортные 100 000 циклов на 288 — выходит 347 дней непрерывной записи под нагрузкой, то есть почти год работы счётчика без выходных. Для прибора, который висит годами, год звучит скромно, но вспомним масштаб: моя первая версия такого счётчика писала раз в секунду и хоронила ячейку за 28 часов — тот расчёт я разбирал в уроке про частоту записи.
Рычагов три, и все честные. Ночной ноль: пока нагрузка не потребляет, сумма не растёт, байты не меняются, update не трогает физику — ячейка отдыхает, и реальный срок растёт пропорционально часам под нагрузкой. Интервал: 15 минут дают 96 записей в сутки и около трёх лет беспрерывной записи. Кольцо ячеек из урока про ресурс умножает срок на число ячеек, если и этого мало. Комбинация «15 минут плюс ночные простои» уводит показуху в десятилетие.
Граница применимости, чтобы не переоценить прибор: EEPROM здесь не узкое место — узкое место датчик. Ползунок в макетке честно играет ток для стенда, но в реальном щитке стоит датчик тока со своим классом точности, и ватт-часы ровно настолько честны, насколько он. Для домашних прикидок «сколько жрёт морозилка» — за глаза; для коммерческого учёта нужен прибор другого класса, и это уже не программная проблема, а метрологическая.
Сброс двойным нажатием
Сброс учётного счётчика — операция редкая и осознанная: сменили нагрузку, открыли новый период учёта, отдали прибор другому человеку. Кнопка, обнуляющая счётчик одним нажатием, — гарантированная потеря данных от случайного касания монтажа: я потерял так полгода моточасов на компрессоре. Компромисс — двойное нажатие в окне 600 мс: руками делается легко, случайно не собирается почти никогда; полный автомат состояний кнопки, включая длинные удержания, разобран в уроке про автомат, здесь работает его двухфронтовая выжимка.
В скетче сброс обнуляет сумму в RAM и тут же пишет нули в EEPROM — иначе после следующего перезапуска счётчик воскрешается из памяти, и это уже не сброс, а сюрприз для будущего себя. Подтверждение двух видов: плановая запись мигает трижды, сброс — шестью вспышками подряд, чтобы результат был виден без монитора порта. Magic при сбросе не трогается: счётчик заведён, плату никто не перепрошивал, ячейка 9 продолжает свою сторожевую службу.
В мониторе стенда идут строки p=1500W sum=25 — мгновенная мощность и накопленный итог; каждая плановая запись печатает wh= и отвечает тремя вспышками. Полезный эксперимент в симуляторе: остановите скетч и запустите снова — EEPROM между запусками сохраняется, и в мониторе появится строка «start: v pamyati wh=» с тем числом, что уехало в память. На живой плате то же самое выглядит как выдернутая и вставленная вилка: итог доехал до EEPROM, потерян максимум хвост интервала.
Отдельно про то, чего в скетче нет: записи при каждом тике. Соблазн «пишу честно каждую минуту» погубил мою первую версию за сутки, и с тех пор правило простое: темп записи задаёт не честность счётчика, а размер потери при сбое. Пять минут — компромисс: даже при минуте тика сумма до записи остаётся в RAM и после восстановления питания просто продолжает копиться, а память тревожится по расписанию, а не по настроению.
- Счётчик копит энергию, а не время: P = U×I складывается тиками в ватт-секундах, 3600 из них — один ватт-час
- Сумма живёт в RAM, в EEPROM уезжает только итог раз в 5 минут: потеря при сбое — меньше одного интервала
- 12 записей в час — 288 в сутки — 347 дней непрерывной записи; интервал 15 минут и ночные простои растягивают срок до лет
- update пишет только изменившиеся байты: старшие разряды счётчика не тратят циклы неделями
- Сброс — двойное нажатие за 600 мс и запись нулей в память; один клик такой прибор не обнуляет
- 1. Сколько фиксаций в EEPROM делает счётчик за сутки при интервале 5 минут?
- 2. Что добавляется к накопителю за один тик?
- 3. Почему для записи итога выбран update, а не write?
- 4. Через сколько непрерывной записи под нагрузкой младшая ячейка исчерпает паспортные 100 000 циклов?
- 5. Зачем сброс счётчика сделан двойным нажатием?
Частые вопросы по теме урока
- Сколько фиксаций в EEPROM делает счётчик за сутки при интервале 5 минут?
- Интервал 5 минут даёт 12 записей в час, 12×24 = 288 в сутки. Запуск записи идёт по таймеру и от нагрузки не зависит; физический ресурс тратит только update по изменившимся байтам, а при нулевой нагрузке они не меняются. 288 — по 12 за каждый час
- Что добавляется к накопителю за один тик?
- Интеграл мощности — это сумма произведений мощности на длительность каждого шага. За тик в минуту при киловатте копится 60 ватт-секунд; ватт-час набирается за 3600 ватт-секунд. P × длительность тика — ватт-секунды
- Почему для записи итога выбран update, а не write?
- Каждая запись изнашивает ячейку, поэтому пишут лишь то, что реально изменилось. Старшие байты счётчика меняются редко — write перезаписывал бы их впустую каждый раз. update пишет только изменившиеся байты и бережёт ресурс ячеек
- Через сколько непрерывной записи под нагрузкой младшая ячейка исчерпает паспортные 100 000 циклов?
- 288 записей в сутки: 100000 / 288 ≈ 347 дней. Раз в секунду та же ячейка умерла бы за 28 часов — темп записи решает всё, отсюда пятиминутный интервал. около 347 дней
- Зачем сброс счётчика сделан двойным нажатием?
- Сброс учётного счётчика — редкая осознанная операция, и одиночное нажатие превращает её в лотерею при каждом касании монтажа. Два нажатия за 600 мс руками делаются легко, а случайно — почти никогда. чтобы случайное касание кнопки не обнулило учёт