Дача встретила меня утром инеем на траве, и первый вопрос семьи был не «сколько сейчас», а «а ночью-то сколько было?». Термометр за окном честно показывал текущие шесть градусов, но ночную историю он не хранит: что было в три часа ночи, мы уже не узнаем никогда. Я тогда повесил у двери блокнот и приучил себя переписывать показания утром и вечером — хватило на две недели. Следующим ходом был ноутбук с монитором порта: лог честный, но ноутбук занимает подоконник, шумит и не переживёт дачную зиму. Покупной метеобуд с памятью я тогда не нашёл дешевле половины зарплаты, а задача-то была про два числа.
Ответ в итоге уместился в два числа на экране: минимум и максимум за сутки. Скетч держит переменные minT и maxT, обновляет их при каждом чтении датчика и рисует второй строкой LCD 16x2 — текущая температура и влажность сверху, вехи суток снизу. Приехал вечером, глянул на экран и узнал, что ночью было плюс два, а днём в теплице под тридцать. Никакого журнала и никакой связи: два числа живут в ОЗУ платы и живут ровно до кнопки сброса.
Здесь соберу этот прибор целиком: скетч с экраном и кнопкой, разбор ловушки, на которой спотыкается почти каждый второй первый вариант, и куда двигать проект дальше — от полночного сброса по часам до энергонезависимой памяти. Экранную панель мы уже поднимали в уроке про термогигрометр на LCD, а таймер опроса — знакомый по millis вместо delay. Нового тут немного, но именно новая мелочь — инициализация минимума — стоит новичкам целых вечеров.
Три строки логики и одна ловушка
Логика вех — сравнение после каждого чтения. Прочитали температуру: если она ниже минимума, минимум обновить; если выше максимума, обновить максимум. Две переменные, два if — и вехи едут за воздухом весь день. На DHT22 с его шагом в десятые доли они ползут плавно, и по их поведению видно характер помещения: в комнате с батареей максимум прилипает к вечеру, в теплице прыгает к полудню, как только солнце выходит из-за тучи.
Ловушка прячется в инициализации. Первая версия скетча почти всегда выглядит невинно: float minT = 0.0 и float maxT = 0.0 — и вот тут прибор ломается молча. В отапливаемой комнате воздух теплее нуля всегда, значит сравнение t < minT не срабатывает ни разу за всю жизнь устройства: минимум прилипает к нулю и сидит там вечно, а рядом с ним горит честно работающий максимум. Экран при этом выглядит правдоподобно, и это маскирует поломку хуже любой тишины — ищешь обрыв в линии, а не ноль в переменной.
Лечение — стартовать вехи с текущего показания, а не с нуля. При первом удавшемся чтении присваиваем minT и maxT прочитанное значение, и дальше сравнения живут своей жизнью. Тот же механизм стоит за кнопкой сброса: нажали — обеим переменным присваивается то, что датчик видит прямо сейчас, и сутки отсчитываются заново. Сбрасывать в ноль нельзя: вы тем самым воспроизводите ту же ловушку руками, и через полминуты минимум снова мёртв.
Вехи полезны и как диагност, не только как справка для семьи. Минимум, который за ночь уполз на два градуса ниже обычного, — это приоткрытая форточка или щель в окне; максимум, прыгнувший на градус за пару минут, — это штора, закрытая поверх батареи и греющая теперь занавеску. Смотрите не столько на сами числа, сколько на их поведение: плавные вехи говорят о спокойном помещении, дёрганые выдают сквозняк — и всё это видно без единой строки журнала, прямо на второй строке экрана.
#include <LiquidCrystal_I2C.h>#include "DHT.h" DHT dht(2, DHT22); // датчик на пине 2LiquidCrystal_I2C lcd(0x27, 16, 2); #define BTN_PIN 3 // кнопка сброса float minT = 0.0, maxT = 0.0;bool firstRead = true; // вехи стартуют с текущего показанияbool resetReq = false;int lastBtn = 1; unsigned long lastPoll = 0;const unsigned long PERIOD = 2000; // пол DHT22: раз в две секунды void setup() { Serial.begin(9600); pinMode(BTN_PIN, INPUT_PULLUP); dht.begin(); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Meteostanciya");} void loop() { int now = digitalRead(BTN_PIN); if (lastBtn == 1 && now == 0) resetReq = true; // фронт нажатия lastBtn = now; if (millis() - lastPoll < PERIOD) return; lastPoll = millis(); float t = dht.readTemperature(); float h = dht.readHumidity(); if (isnan(t) || isnan(h)) { Serial.println("chtenie sorvalos, vehi stoyat"); return; } if (firstRead || resetReq) { minT = t; // сброс В ТЕКУЩЕЕ, не в ноль maxT = t; firstRead = false; resetReq = false; Serial.println("min i max ot tekushego chteniya"); } if (t < minT) minT = t; if (t > maxT) maxT = t; lcd.setCursor(0, 0); lcd.print("T "); lcd.print(t, 1); lcd.write(223); // глиф градуса lcd.print("C H "); lcd.print(h, 0); lcd.print("% "); lcd.setCursor(0, 1); lcd.print("min"); lcd.print(minT, 1); lcd.print(" max"); lcd.print(maxT, 1); lcd.print(" "); Serial.print("t="); Serial.print(t, 1); Serial.print(" min="); Serial.print(minT, 1); Serial.print(" max="); Serial.println(maxT, 1);}Обратите внимание, куда попадает сброс: не в отдельную ветку с нулями, а в ту же точку, где вехи стартуют впервые. Это не экономия строк, а защита от второго входа в ту же ловушку: кнопка, присваивающая minT ноль, через мгновение возвращает прибор в состояние сломанной первой версии. Один и тот же код обслуживает и первый запуск, и сброс, потому что по смыслу это одно событие: «начни отсчёт суток от того, что вижу сейчас». Проверьте себя прямо на макетке: подержите кнопку — строка сброса в Serial обязана появиться ровно один раз за удержание, иначе у вас не фронт, а уровень.
Кнопка обрабатывается по фронту через переменную lastBtn, как мы привыкли с уроков про кнопки: без этого сброс срабатывал бы десятки раз за одно удержание. Полночный сброс по часам устроен так же, только вместо фронта кнопки — условие «час равен нулю, минута равна нулю, а флага „сегодня уже сбрасывал“ нет». Часы для этого берутся не с millis — тот считает от включения платы, — а с RTC: как выставить время модулю, мы разбирали в уроке про установку времени RTC. Без флага ловушка четвёртая: весь нулевой час сброс срабатывал бы каждую секунду, и минимум ночи стирался бы на рассвете.
Про десятичные знаки на экране: одна цифра после точки — потолок, который имеет смысл показывать. Паспорт DHT22 — ±0,5 °C и ±2-5 % по влажности, и вторая десятая в таком числе — не точность, а шум сенсора, оформленный как данные. Хвостовые пробелы после чисел — привычка из экранного урока этого раздела: print пишет поверх и не стирает, и без них за уехавшим максимумом остаётся хвост старой цифры. На DHT11 тот же скетч живёт без правок, только вехи шагают целыми градусами, и минимум может «зависнуть» на час — это шаг сенсора, а не баг.
Строка на экране обязана помещаться в шестнадцать символов, и минус в этом смысле всё портит: max26.0 занимает восемь позиций, а max-12.5 — девять. Формат с прижатыми к числам метками min и max держит обе крайности: в парнике с минусом за стеклом строка не съезжает, и хвостовой пробел всё равно остаётся в запасе. Если вехи по влажности вам нужнее, чем по температуре, меняйте колонки местами смело — формат одинаковый, экран подмены не заметит.
| Что на экране | Диагноз | Что править |
|---|---|---|
| min прилип к нулю с первого включения | minT и maxT инициализированы нулями | первое чтение присваивает вехи, а не ноль |
| Кнопка сброса вернула минимум в ноль | сброс присваивает константу вместо текущего чтения | один код для первого запуска и сброса |
| Хвост старой цифры за новым числом | print пишет поверх и не стирает | хвостовые пробелы после значения |
| Минимум оказался больше максимума | сброс обновил только одну переменную | присваивать minT и maxT одной парой строк |
Куда расти дальше
Первое развитие — вторая пара вех по влажности: переменные те же, экран уже рисует две строки, а суточный минимум влажности для подвала или теплицы порой говорит больше, чем температура. Второе — журнал вместо одного экрана: вехи — предельный случай сжатия истории, последние часы целиком можно держать в ОЗУ, мы собирали такой журнал на кольцевом массиве, а полный лог на карту с метками времени — тема следующего урока про логгер с RTC.
И сверка с реальностью. Вехи, как и любые показания DHT22, живут в вилке ±0,5 °C: если минимум вызвал в семье спор «было ли ночью минус два или минус один», сверьте прибор с эталонным термометром по методике калибровки. Для бытовых вопросов — «замёрзнет ли рассада», «закрывать ли форточку» — этой точности хватает с запасом: окно между «всё хорошо» и «пора действовать» обычно два-три градуса, на порядок шире паспортной погрешности. Собранный целиком прибор с экраном, вехами и реле стоит в итоговом проекте раздела.
И последняя роскошь двух чисел на стене: их читают домочадцы без всякой инструкции. Блокнот у двери требовал дисциплины, монитор порта — компьютера, а экран с вехами работает как термометр в поликлинике: подошёл, посмотрел, понял. За месяц прибор на кухне не пришлось объяснять ни разу: спросили один раз, что значит строка min, и на этом вопросы кончились. Такую приборную простоту я теперь закладываю в дачный проект с самого начала, а не после первого сезона страданий.
- 1. Почему minT, инициализированный нулём, остаётся нулём навсегда?
- 2. С чего правильнее стартовать минимум и максимум?
- 3. Кнопка сброса вех должна присваивать minT и maxT...
- 4. Сколько знаков после точки имеет смысл печатать для DHT22?
- 5. Полночный сброс без флага «сегодня уже сбрасывал»...
- Вехи суток — две переменные и два сравнения после каждого чтения; на DHT22 они ползут десятых долями.
- Инициализация нулями убивает минимум навсегда: в тёплой комнате t < 0 не бывает — стартуйте с текущего показания.
- Сброс кнопкой и первый запуск — одно событие: minT и maxT присваиваются текущему чтению, никогда ноль.
- Полночный сброс ведёт RTC с флагом «сегодня уже сбрасывал»; millis полночь не знает.
- На экране одна десятая: паспорт ±0,5 °C и ±2-5 % RH делает сотые доли шумом.
Частые вопросы по теме урока
- Почему minT, инициализированный нулём, остаётся нулём навсегда?
- При minT = 0 и воздухе теплее нуля условие t < minT ложно всегда. Минимум мёртв с первой секунды, хотя экран выглядит правдоподобно. В тёплой комнате сравнение t < minT не срабатывает ни разу
- С чего правильнее стартовать минимум и максимум?
- Первое чтение присваивается обеим переменным. Восстановление из EEPROM — отдельная функция, а не стартовое поведение. С текущего показания при первом удавшемся чтении
- Кнопка сброса вех должна присваивать minT и maxT...
- Сброс в ноль воспроизводит ловушку инициализации: минимум снова мёртв. Безопасен только старт от того, что прибор видит сейчас. Текущему чтению датчика
- Сколько знаков после точки имеет смысл печатать для DHT22?
- Шаг DHT22 — 0,1 °C при погрешности ±0,5 °C. Вторая десятая не несёт информации о воздухе, только о шуме сенсора. Одну: паспорт ±0,5 °C делает сотые шумом
- Полночный сброс без флага «сегодня уже сбрасывал»...
- Условие «час 0, минута 0» истинно шестьдесят минут подряд. Без флага сброс выполняется на каждом проходе loop, и вехи ночи гибнут на рассвете. Сработает каждую секунду весь нулевой час и сотрёт минимум ночи