Моя первая метеостанция показывала температуру и влажность на одном экране — и была глуха к любому вопросу помимо «что сейчас». Друг заглянул вечером и спросил, какая была минимальная за ночь. Я полез в экран с упорством: втиснул «T22 H44 m17 x26» в шестнадцать знаков, получил кашу, которую сам не разобрал через день, а показания за ночь всё равно посчитал блокнотиком, вручную. Честный ответ лежал не в утрамбовке цифр, а в меню: два экрана, кнопка переключает, на первом — текущие значения, на втором — минимум, максимум и время работы. Шестнадцать знаков на строку — это не приговор, это повод придумать два экрана.
Это итоговый проект раздела, и он собирает всё, что мы прошли. Железо минимальное: Arduino Uno, LCD 16x2 по I2C, датчик DHT11, кнопка и светодиод. Про подключение датчика был урок про DHT11, механику переключения экранов мы делали в уроке про меню и курсор, а тик таймера из первого урока этой тройки здесь станет каркасом всего скетча. Нового кода почти нет — и в этом смысл: проект проверяет, что база собралась в работающую вещь.
Техническое задание на прибор
Пишу ТЗ так, как пишу себе: коротко и с числами, чтобы через месяц не спорить с самим собой. Экран 0: текущая температура с одним знаком после запятой, влажность целым числом и аптайм в секундах. Экран 1: минимум и максимум температуры с момента включения. Кнопка переключает экраны по нажатию. Датчик опрашивается раз в две секунды: DHT11 быстрее всё равно не отвечает, чаще читать бессмысленно. Экран перерисовывается в том же тике, кнопка опрашивается каждый проход loop(). Светодиод — индикатор комфорта: в диапазоне 20–26 °C горит ровно, вне диапазона мигает раз в секунду. Вилка 20–26 — моё бытовое «в комнате хорошо», под свою задачу переставьте: для оранжереи это будет 18–24, для кулера с электроникой — своя пара чисел.
Отдельное слово про кнопку на пине 3: она живёт вне тика датчика и проверяется каждые десять миллисекунд, поэтому экран меняется мгновенно, даже если до ближайшего чтения DHT осталась секунда с хвостом. Именно эта развязка отличает прибор от учебного скетча, где всё ждёт всё.
| Элемент прибора | Откуда берётся | Как обновляется |
|---|---|---|
| T и H на экране 0 | readTemperature() и readHumidity() | тиком 2000 мс вместе с опросом датчика |
| Аптайм на экране 0 | millis() / 1000 | тем же тиком 2000 мс |
| Min и Max температуры | сравнение после каждого чтения | копятся на тике, видны на экране 1 |
| Смена экрана | фронт нажатия кнопки | каждый проход loop(), вне тика |
| Светодиод комфорта | сравнение T с вилкой 20–26 °C | каждый проход loop() |
#include <Wire.h>#include <LiquidCrystal_I2C.h>#include <DHT.h> DHT dht(2, DHT11);LiquidCrystal_I2C lcd(0x27, 16, 2); const int btnPin = 3;const int ledPin = 12; long lastRead = 0;int screen = 0;int prevBtn = HIGH;float tCur = 0;int hCur = 0;float tMin = 100;float tMax = -40; void readSensor() { float t = dht.readTemperature(); int h = dht.readHumidity(); // на реальном железе здесь проверяют isnan(t): про это в note ниже if (t < -50 || t > 80) return; // бредовое значение — пропускаем tCur = t; hCur = h; if (tCur < tMin) tMin = tCur; if (tCur > tMax) tMax = tCur;} void drawScreen() { if (screen == 0) { lcd.setCursor(0, 0); lcd.print("T:"); lcd.print(tCur, 1); lcd.print(" H:"); lcd.print(hCur); lcd.print("% "); lcd.setCursor(0, 1); lcd.print("Rabota: "); lcd.print(millis() / 1000); lcd.print(" s"); } else { lcd.setCursor(0, 0); lcd.print("Min: "); lcd.print(tMin, 1); lcd.print(" "); lcd.setCursor(0, 1); lcd.print("Max: "); lcd.print(tMax, 1); lcd.print(" "); }} void comfortLed() { if (tCur >= 20 && tCur <= 26) { digitalWrite(ledPin, HIGH); // в вилке — ровный свет } else { if (millis() % 1000 < 500) digitalWrite(ledPin, HIGH); else digitalWrite(ledPin, LOW); // вне вилки — мигаем }} void setup() { pinMode(btnPin, INPUT_PULLUP); pinMode(ledPin, OUTPUT); dht.begin(); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Meteostanciya "); lcd.setCursor(0, 1); lcd.print("Zapusk datchika "); delay(1500); readSensor(); drawScreen();} void loop() { int btn = digitalRead(btnPin); // кнопка вне тика if (btn == LOW && prevBtn == HIGH) { screen = 1 - screen; drawScreen(); } prevBtn = btn; comfortLed(); // индикация тоже вне тика if (millis() - lastRead >= 2000) { // датчик и экран — по тику lastRead = millis(); readSensor(); if (screen == 0) drawScreen(); } delay(10);}Разберу места, где проект отличается от учебных скетчей. Чтение датчика и перерисовка живут в одном тике на 2000 мс: пока DHT «думает», loop() остаётся свободным для кнопки и светодиода. Минимум и максимум копятся после каждого успешного чтения простым сравнением — без массивов истории: для «за ночь» этого хватает, а суточный график лучше строить в сериал-мониторе или на компьютере. Обе строки экрана печатаются с хвостами из пробелов, затирающими прошлое значение: температура меняет длину при переходе через десятки, и без затирания на экране остаются хвосты цифр. Мигание светодиода построено на millis() % 1000: первая половина секунды — свет, вторая — тишина, и никакой delay() при этом не трогает ни кнопку, ни тик.
Замечание про гистерезис: вилка комфорта в этом скетче — просто индикация, ничего не включается. Если захотите управлять реле по той же вилке, добавьте зазор в полградуса между порогами включения и выключения, иначе прибор будет щёлкать на границе каждые две секунды, и реле проживёт недолго. А сам индикатор комфорта удобно вынести в отдельную функцию, как здесь, — её легко заменить на зуммер или на мигание подсветкой экрана без переделки остального.
Стенд в симуляторе: гоняем прибор до пайки
Перед выставкой прибора я гоняю один и тот же сценарий, теперь и в симуляторе. Поднимите температуру за верхнюю границу вилки — светодиод уйдёт в мигание, ровно как на реальном стенде. Верните в вилку — свет замрёт ровным. Переключите экран кнопкой и подождите пару тиков: минимум и максимум поймают крайние значения, которые вы задали, и ни одно из них не потеряется при смене экрана. Полторы минуты на весь прогон — и вся логика проверена до того, как паяльник прогреется.
Если экран при первом запуске вдруг показывает чёрную строку — вы уже знаете, где искать: тренажёр диагностики из предыдущего урока прогоняет этот сценарий шаг за шагом.
Куда расти дальше. Первое: экран 20x4 — на него влезают и текущие значения, и min/max одновременно, без меню; адресация строк у него своя, об этом был урок про 20x4. Второе: DHT22 вместо DHT11 — точность плюс-минус 0,5 °C против двух и диапазон шире; чем они отличаются, разобрано в уроке про выбор между DHT11 и DHT22. Третье: лог на SD-карту или отправка в сеть — это уже соседние разделы самоучителя. И главное: соберите этот проект на реальном железе и оставьте на сутки. Первый суточный график собственной комнаты радует сильнее любого симулятора, а минимум за ночь обычно оказывается ниже, чем вы ожидали.
- Соберите схему: дисплей по I2C на A4/A5, DHT11 на пин 2, кнопка на пин 3, светодиод через резистор на пин 12.
- Залейте скетч и переключите оба экрана кнопкой; первое чтение датчика занимает до двух секунд — это норма, а не зависание.
- Выведите температуру за вилку: светодиод обязан замигать; верните — обязан замереть ровным светом.
- Оставьте прибор на сутки: минимум за ночь обычно ниже ожидаемого — проверьте, что min/max дожили до утра без сброса.
- Меню из двух экранов решает нехватку 16 знаков лучше, чем утрамбовка цифр
- Тик 2000 мс на датчик: DHT11 быстрее не отвечает, а loop() остаётся свободным для кнопки
- Min/max копятся простым сравнением после каждого успешного чтения — без массивов истории
- Светодиод комфорта: ровный свет в вилке 20–26 °C, мигание вне её — границы перестраиваются под задачу
- NaN на реальном DHT — норма: проверка isnan() бережёт min/max и текущий экран
- 1. Почему датчик опрашивается раз в 2000 мс?
- 2. Что делает кнопка в этом проекте?
- 3. Светодиод заморгал. Что это значит?
- 4. Как копится минимум температуры?
- 5. На реальном железе readTemperature() вернул NaN. Что делать?
Частые вопросы по теме урока
- Почему датчик опрашивается раз в 2000 мс?
- Датчик выдаёт новое значение не чаще 1 Гц; остальное — повторы. Чтение чаще тика не добавит точности ни на градус. DHT11 не успевает готовить новые показания чаще
- Что делает кнопка в этом проекте?
- Кнопка меняет переменную screen и перерисовывает экран; она опрашивается вне тика, поэтому смена мгновенная. Переключает экраны меню
- Светодиод заморгал. Что это значит?
- В вилке светодиод горит ровно, мигание включается вне диапазона — это и есть индикация комфорта. Температура вышла за вилку комфорта
- Как копится минимум температуры?
- Одно сравнение после каждого чтения: если новое значение меньше tMin — оно и становится новым минимумом. Сравнением с текущим tMin после каждого успешного чтения
- На реальном железе readTemperature() вернул NaN. Что делать?
- NaN — разовый пропуск датчика: молча пропускаем значение, экран и min/max остаются с последними честными данными. Пропустить показание и подождать следующего тика