Руководства · 22 мин чтения

Умная теплица на Arduino: датчики, полив, вентилятор и код

Умная теплица на Arduino: измерения с DHT22, датчиком влажности почвы и LDR, управление вентилятором, нагревателем, насосом и светодиодами через реле и MOSFET, гистерезис, LCD и код для ESP32.

Умная теплица на Arduino — это система автоматизации, в которой Arduino или ESP32 измеряет температуру и влажность воздуха, влажность почвы и освещённость в теплице и сама включает вентилятор, нагреватель, водяной насос и фитосветодиоды. Это одна из самых популярных тем для научных выставок, школьных конкурсов и уроков технологии, потому что результат хорошо виден: подсохла почва — запускается насос, стало жарко внутри — крутится вентилятор, наступил вечер — загораются светодиоды. А ещё между макетом теплицы из контейнера для хранения и настоящей мини-теплицей на балконе почти нет разницы ни в схеме, ни в коде.

В этом руководстве мы начинаем с того, что на самом деле нужно растениям, а затем шаг за шагом собираем макет теплицы: список деталей и примерная стоимость, подключение датчиков (DHT22, ёмкостный датчик влажности почвы, LDR) и исполнительных устройств (вентилятор, нагреватель, насос и светодиоды через реле и MOSFET), план питания и таблица пинов. Дальше разбираем логику порогов и гистерезиса, которая не даёт реле постоянно щёлкать, полный код Arduino без delay() и дисплей LCD 16x2. В последних разделах — как следить за теплицей с телефона через ESP32, версия на MicroPython для Raspberry Pi Pico, советы для школьных презентаций и научных выставок и частые ошибки.

DHT22 (AM2302)
DHT22 (AM2302)
LDR
LDR
I2C LCD 16x2
I2C LCD 16x2
LM2596
LM2596
Arduino Uno
Arduino Uno

Как работает умная теплица?

Каждая умная теплица работает по одному и тому же циклу из четырёх шагов: измерить, решить, выполнить, показать. Датчики измеряют среду: сколько градусов внутри, насколько влажный воздух, не подсохла ли почва, хватает ли света? Arduino сравнивает эти значения с правилами, которые задаёшь ты: «если температура выше 30 °C, включи вентилятор», «если влажность почвы упала ниже 35%, полей». Исполнительные устройства выполняют решение: реле включают и выключают вентилятор, нагреватель и насос, а MOSFET задаёт яркость светодиодной ленты. Наконец, LCD и твой телефон показывают текущее состояние теплицы. Климатические компьютеры в настоящих промышленных теплицах работают ровно по такому же циклу, просто у них больше датчиков и мощнее моторы.

Что измеряем?ДатчикИсполнительное устройствоПравило (пример)
Температура воздухаDHT22Вентилятор и нагревательВентилятор включается при 30 °C, нагреватель — при 16 °C
Относительная влажностьDHT22ВентиляторВентилятор включается при 85% и выключается при 75%
Влажность почвыЁмкостный датчик влажности почвыПогружной мини-насосНиже 35% поливает импульсами по 3 секунды
СветLDRСветодиодная лента (MOSFET)Когда темнеет, светодиоды горят не больше 4 часов
Вода в бакеПоплавковый датчик уровняБлокировка насосаЕсли бак пуст, насос не включается вообще

Что нужно растениям? Целевая температура, влажность и свет

Прежде чем писать правила, нужно понять, к чему стремиться. Большинству растений нужна разная температура днём и ночью: небольшое ночное похолодание помогает растению отдыхать. Если влажность долго держится выше 85%, на листьях конденсируется вода и быстро распространяются грибковые болезни вроде плесени и ложной мучнистой росы; поэтому вентилятор в теплице работает не только для охлаждения, но и для удаления лишней влаги. Что касается света, большей части овощной рассады нужно 14–16 часов в сутки, но не меньше важен и тёмный период длиной хотя бы 6–8 часов; если оставлять светодиоды включёнными на всю ночь, растению будет больше вреда, чем пользы. Значения ниже — хороший ориентир для начала; подстрой их под то, что выращиваешь.

РастениеДеньНочьОтносительная влажностьПримечание
Томат21–27 °C15–18 °C60–75%Выше 32 °C цветки могут опадать
Перец21–29 °C16–20 °C60–75%Чувствительнее к холоду, чем томаты
Салат и шпинат15–20 °C10–15 °C60–70%В жару и при длинном дне рано уходят в стрелку
Клубника18–24 °C10–13 °C60–75%При высокой влажности растёт риск серой гнили
Рассада и проращивание20–25 °C18–20 °C70–85%Температура почвы важнее температуры воздуха
Если в день показа в классе 22 °C, вентилятор с порогом 30 °C так и не закрутится. Для презентации поставь пороги на 2–3 градуса выше комнатной температуры: стоит согреть DHT22 в ладони, и жюри через несколько секунд увидит, как запускается вентилятор. Для реальной работы верни значения из таблицы.

Строим макет теплицы

Самый простой и дешёвый корпус — прозрачный пластиковый контейнер для хранения с крышкой: контейнера на 40–60 литров хватит на два-три горшка, он не пропускает воду, а отверстия в нём легко сделать канцелярским ножом или нагретым жалом. Для более реалистичного вида собери каркас из деревянных реек и обшей его прозрачным оргстеклом (акрилом) или листами PETG толщиной 2–3 мм; теплица с двускатной крышей на презентации смотрится гораздо лучше. Размер около 50×35×40 см — хорошая отправная точка. Какой бы корпус ни был выбран, крышка или одна из боковых стенок должна открываться: внутрь придётся залезать рукой, чтобы полить растения, переставить датчик и поправить провода.

  • Электроника снаружи: внутри теплицы влажно, а по утрам на стенках конденсируется вода. Размести Arduino, модуль реле и блок питания в отдельной коробке снаружи теплицы; внутри остаются только датчики, вентилятор, нагреватель, шланг насоса и светодиоды.
  • Вентиляция: тёплый воздух поднимается вверх, поэтому установи вентилятор в верхней части одной из коротких стенок так, чтобы он выдувал воздух наружу. В нижней части противоположной стенки прорежь приточное отверстие размером с вентилятор и закрой его москитной сеткой; тогда свежий воздух входит снизу, а тёплый и влажный уходит сверху.
  • Нагреватель: закрепи нагреватель у дна на алюминиевой пластине или керамической плитке, не ближе 5 см к пластиковым стенкам и растениям. Поднимаясь, тёплый воздух расходится по всей теплице.
  • Светодиодное освещение: приклей светодиодную ленту к внутренней стороне крышки, на 15–30 см выше растений. Если закрепить ленту на тонком алюминиевом профиле, она лучше отводит тепло, а клей со временем не отходит.
  • Датчики: размести DHT22 на высоте растений там, куда не попадает прямое солнце или свет светодиодов. Датчик влажности почвы закопай в корневую зону, подальше от края горшка. А вот LDR не должен видеть светодиоды: вынеси его наружу, на крышу теплицы, лицом к небу.
  • Бак для воды: для насоса и поплавка хватит ёмкости на 1–2 литра. Поставь под горшки поддоны: лишняя вода соберётся в поддоне, а не разольётся лужей по дну контейнера.
Если конец шланга ниже уровня воды в баке, после остановки насоса вода продолжает течь из-за эффекта сифона и переливается через край горшка. Держи бак ниже горшков или проколи иглой маленькое воздушное отверстие в самой высокой точке шланга: когда насос останавливается, через отверстие заходит воздух, и поток сразу прерывается.

Список деталей и стоимость

ДетальКол-воЗадачаУровень цены
Arduino Uno R3 или ESP32 DevKit1Мозг теплицыСредний
Модуль температуры и влажности DHT22 (AM2302)1Температура и влажность воздухаНизкий
Ёмкостный датчик влажности почвы (v1.2)1 (по одному на горшок)Влажность почвыНизкий
LDR (GL5528) и резистор 10 kΩ1 + 1Внешняя освещённостьОчень низкий
Поплавковый датчик уровня воды1Блокирует насос, когда бак пустНизкий
4-канальный модуль реле 5 V (с оптопарами и JD-VCC)1Вентилятор, нагреватель и насосНизкий
N-канальный MOSFET с логическим уровнем управления (IRLZ44N), резисторы 220 Ω и 100 kΩ1 комплектЯркость светодиодной ленты (ШИМ)Очень низкий
Погружной мини-насос 3–6 V и силиконовый шланг 1 м1ПоливНизкий
Компьютерный вентилятор 12 V, 80 мм1–2Вентиляция и удаление влагиНизкий
PTC-нагреватель 12 V (10–30 W) и термопредохранитель1 + 1ОбогревНизкий
Светодиодная лента 12 V (фитолента или холодный белый), около 1 м1ДосветкаНизкий
Дисплей I2C LCD 16x21Экран состоянияНизкий – средний
Блок питания 12 V 5 A и понижающий преобразователь LM25961 + 1ПитаниеСредний
Диод 1N5819 или 1N4007, конденсатор 470 µF, винтовые клеммы, перемычки–Защита и соединенияОчень низкий
Прозрачный контейнер с крышкой или листы оргстекла, горшки, поддоны–Корпус теплицыСредний

Цены у продавцов сильно различаются, поэтому вместо точных цифр — примерный бюджет. Без учёта платы базовая версия (DHT22, датчик влажности почвы, LDR, 2-канальное реле, насос и вентилятор на 5 V) обычно обходится примерно в 25–40 $. Полная версия с нагревателем, светодиодной лентой, LCD, блоком питания 12 V, LM2596, поплавковым датчиком и корпусом обычно выходит около 70–120 $; основная часть бюджета уходит на плату, блок питания, дисплей и корпус. Если есть стартовый набор Arduino, плата, макетная плата, провода, LDR, резисторы, а часто и LCD уже в наличии. При скромном бюджете оставь нагреватель и светодиоды на второй этап: полив и вентиляция сами по себе уже дают осмысленный рабочий проект.

Датчики: DHT22, датчик влажности почвы и LDR

Для теплицы советуем DHT22 (AM2302). Он немного дороже DHT11, зато измеряет температуру от −40 до 80 °C с точностью ±0,5 °C и влажность во всём диапазоне 0–100% с точностью около ±2%; DHT11 точен лишь до ±2 °C в пределах от 0 до 50 °C и не видит влажность выше 90%. Утром влажность в теплице может подняться выше 90%, а зимой температура может опуститься ниже нуля, так что DHT22 подходит гораздо лучше. Опрашивать датчик можно не чаще одного раза в 2 секунды, поэтому код читает его каждые 2,5 секунды. Если поставить датчик туда, где на него прямо светит солнце или светодиоды, его корпус нагреется и он будет показывать на несколько градусов больше, чем температура воздуха; этого не случится, если затенить датчик белым колпачком с отверстиями. Подключение и библиотеки описаны в статье о датчиках температуры и влажности DHT11 и DHT22.

Модуль датчика влажности почвы
Датчик влажности почвы: щуп, закопанный в корневую зону, сообщает, насколько влажна почва, в виде аналогового напряжения; для долгой работы в теплице выбирай ёмкостную модель.

Для влажности почвы выбирай ёмкостный датчик. У дешёвых резистивных датчиков (YL-69, FC-28) два металлических щупа напрямую контактируют с почвой, и при постоянном питании их за несколько недель разъедает электролиз; для системы, которая будет работать месяцами, они не годятся. Электрод ёмкостного датчика покрыт защитным слоем, так что металл с почвой не соприкасается. Выход у него аналоговый и уменьшается по мере увлажнения почвы: на Uno датчик в воздухе даёт примерно 500–600, а опущенный в воду — около 250–300. Эти числа отличаются от датчика к датчику, поэтому перед загрузкой основного кода посмотри в мониторе порта значения своего датчика в воздухе и в воде и впиши их в константы SOIL_DRY_RAW и SOIL_WET_RAW. Код переводит эти два значения в 0–100% с помощью map(). Подробнее — в статье о датчике влажности почвы.

Для освещённости хватит фоторезистора LDR (GL5528) из набора. Подключи LDR делителем напряжения с резистором 10 kΩ: 5V → LDR → A1 → 10 kΩ → GND. Чем больше света, тем меньше сопротивление LDR и тем выше значение на A1; код тоже переводит его в 0–100% по двум числам, измеренным в темноте и на солнце. LDR — не точный люксметр, но на вопрос «стемнело ли, пасмурно или солнечно?» он отвечает надёжно. Для более точных измерений освещённости можно перейти на I2C-датчик освещённости, например BH1750. Расчёт делителя удобно сделать в утилите делителя напряжения, а подробности есть в статье о фоторезисторе LDR.

Если LDR видит свет светодиодов, система попадает в петлю и сама себя включает и выключает: темнеет, светодиоды загораются, LDR решает, что светло, и гасит их, а потом снова видит темноту. Чтобы этого избежать, размести LDR снаружи теплицы, подальше от светодиодов, и используй в коде гистерезис вместо одного порога. Электроника в верхней части ёмкостного датчика почвы тоже не водонепроницаема: не закапывай датчик выше нанесённой на нём линии, а края платы загерметизируй термоклеем или прозрачным лаком для ногтей.

Исполнительные устройства: реле или MOSFET?

Пин Arduino может отдать всего около 20 mA, а вентилятор, нагреватель, насос и светодиодная лента потребляют сотни миллиампер или даже несколько ампер. Поэтому каждая нагрузка подключается либо через реле, либо через MOSFET. Реле — это механический выключатель: оно включает и выключает нагрузку в полностью отдельной цепи, переменного или постоянного тока, но умеет только «вкл/выкл», щёлкает при каждом переключении, а ресурс его контактов ограничен. MOSFET — бесшумный электронный ключ; он работает только с нагрузками постоянного тока, но переключается сотни раз в секунду и поэтому может задавать яркость и скорость с помощью ШИМ (PWM). В этом проекте для нагревателя, вентилятора и насоса используется 4-канальный модуль реле, а для яркости светодиодной ленты — MOSFET.

НагрузкаТипичное значениеЧем управлятьПочему?
PTC-нагреватель 12 V10–30 W, примерно 1–2,5 AРеле (IN2)Большой ток, нужно только вкл/выкл
Вентилятор 12 V, 80 мм0,1–0,2 AРеле (IN1) или MOSFETРеле проще; MOSFET позволяет ещё и регулировать скорость ШИМ
Погружной мини-насос 3–6 V0,1–0,25 A, при запуске большеРеле (IN3) или MOSFET с диодомРаботает короткими импульсами; помехи от мотора нужно подавлять
Светодиодная лента 12 V (5050, 60 светодиодов/м)Около 1,2 A на метрMOSFET с логическим уровнем управленияРегулировка яркости ШИМ и бесшумная работа
Погружной мини-насос для воды
Погружной мини-насос 3–6 V: стоит в баке с водой, потребляет около 100–200 mA и подаёт воду по шлангу в горшок; никогда не включай его всухую.

В зависимости от высоты шланга погружной мини-насос подаёт примерно 1–1,5 литра в минуту, так что 3-секундный импульс даёт горшку 50–75 мл воды. Поэтому насос не работает непрерывно, «пока почва не намокнет», а включается короткими импульсами: полить 3 секунды, подождать 60 секунд, пока вода впитается, и снова измерить. Если бы датчик опрашивался снова раньше, чем до него дойдёт вода, почва всё ещё выглядела бы сухой, и насос работал бы, пока горшок не переполнится. Насос, работающий всухую, перегревается и выходит из строя за считаные минуты; поплавковый датчик в баке блокирует насос, когда вода заканчивается. При каждой остановке мотора его обмотка выдаёт выброс обратного напряжения; диод, включённый в обратном направлении параллельно клеммам насоса (1N5819 или 1N4007, полоской к плюсу), защищает от этих помех и контакты реле, и Arduino. Выбор насоса и способы управления им описаны в статье о мини-насосе для воды.

Для обогрева используй PTC-нагреватель на 12 V. Сопротивление керамических PTC-элементов (с положительным температурным коэффициентом) растёт по мере нагрева, поэтому температура их поверхности сама ограничивается на заданном уровне; это гораздо безопаснее открытой раскалённой спирали. Для пластикового корпуса выбирай низкотемпературную модель, у которой поверхность нагревается не выше примерно 80 °C. Нагреватель на 24 W при 12 V по формуле P = V × I потребляет 2 A; в первые секунды, пока он холодный, ток несколько выше. Для освещения подойдёт красно-синяя фитолента или светодиодная лента холодного белого света; для фотосинтеза растения используют в основном красный (около 660 нм) и синий (около 450 нм) свет. Управляй лентой через MOSFET с логическим уровнем управления, например IRLZ44N: затвор подключается к D9 через резистор 220 Ω и к GND через резистор 100 kΩ; плюс ленты идёт на 12 V, минус — на сток MOSFET, а исток — на GND. Обычные модули MOSFET вроде IRF520 при напряжении на затворе 5 V открываются не полностью и греются при нагрузке в несколько ампер; подробности — в статье об использовании MOSFET с Arduino. Ток и мощность можно посчитать в калькуляторе закона Ома.

Нагреватель — единственный реальный источник пожарной опасности в теплице. Закрепи его на металлическом основании, где он не сможет коснуться пластиковых стенок, поставь термопредохранитель последовательно с нагревателем в линию 12 V так, чтобы он касался пластины, на которой закреплён нагреватель, и много часов тестируй систему, прежде чем оставлять её без присмотра. Предохранитель должен срабатывать немного выше предельной температуры нагревателя (например, 92 °C для PTC на 80 °C); предохранитель на более низкую температуру будет перегорать без причины при обычной работе. Коврики для подогрева рассады и более мощные нагреватели обычно работают от сети, а сетевое напряжение смертельно опасно. Не используй сеть в макете теплицы: PTC-нагревателя на 12 V вполне достаточно. Если сетевой прибор действительно нужен, подключение должен выполнить электрик или знающий взрослый, а плата реле должна стоять в закрытой изолированной коробке. Правила безопасного подключения — в статье «Что такое модуль реле?».

План питания: шины 12 V и 5 V

Самые частые проблемы умных теплиц («Arduino перезагружается, когда работает насос», «на LCD появляются странные символы») связаны с питанием. Самое чистое решение — взять две шины от одного блока питания 12 V 5 A. Шина 12 V напрямую питает нагреватель, вентиляторы и светодиодную ленту, а также идёт на гнездо питания (DC) Arduino Uno. Те же 12 V подаются на понижающий преобразователь LM2596, настроенный на 5 V; от этой шины 5 V работают катушки реле, насос и LCD. Если снять перемычку JD-VCC на модуле реле, ток катушек полностью берётся от LM2596, а со стороны Arduino зажигаются только светодиоды оптопар. Датчики (DHT22, датчик влажности почвы и делитель с LDR) питай от собственного пина 5V Arduino: analogRead() измеряет относительно собственных 5 V платы, поэтому питание аналоговых датчиков от того же напряжения делает показания стабильными. Всегда соединяй вместе GND блока питания, LM2596 и Arduino.

НагрузкаШинаПримерный ток
PTC-нагреватель (24 W)12 V2 A (в первый момент, пока он холодный, больше)
Светодиодная лента (1 м)12 V1–1,2 A
Два вентилятора 80 мм12 V0,2–0,4 A
Arduino Uno и датчики (через гнездо DC)12 VОколо 0,06–0,08 A
Вход LM2596 (когда сторона 5 V потребляет 1 A)12 VОколо 0,5 A
Модуль реле (включены три реле)5 V0,2–0,25 A
Погружной мини-насос5 V0,1–0,25 A (при запуске больше)
I2C LCD (подсветка включена)5 V20–30 mA

Даже если всё работает одновременно, общий ток составляет около 4–4,5 A, так что блок питания 12 V 5 A (60 W) даёт достаточный запас. На практике нагреватель и вентилятор по температуре никогда не работают одновременно, потому что между их порогами большой промежуток. Прежде чем подключать LM2596 к схеме, измерь его выход мультиметром и выставь 5,0 V подстроечным резистором на плате: прямо из коробки он может пропускать почти все 12 V. Во время загрузки кода Arduino можно питать от USB, а в работе — от гнезда DC; для Uno одновременное подключение обоих не страшно. Подробности — в руководстве по питанию Arduino.

Подключение и таблица пинов

В таблице ниже — пины, которые используют три программы из этого руководства. На Uno MOSFET, управляющий светодиодной лентой, подключён к D9 с поддержкой ШИМ; на Uno ШИМ есть только на D3, D5, D6, D9, D10 и D11. Поплавковому датчику и реле ШИМ не нужен. LCD использует только шину I2C (A4 и A5), поэтому занимает очень мало пинов; на Uno остаются свободными A2, A3 и несколько цифровых пинов — для второго датчика почвы или кнопки. На ESP32 аналоговые датчики подключены к пинам ADC1 (GPIO 34 и 35), которые продолжают работать при включённом Wi-Fi. Расположение пинов своей платы можно посмотреть в утилите распиновки.

ПодключениеArduino UnoESP32Raspberry Pi Pico
DHT22, вывод DATAD2GPIO 4GP15
Поплавковый датчик (второй провод на GND)D3GPIO 13GP14
Реле IN3: насосD4GPIO 25GP18
Реле IN1: вентиляторD7GPIO 26GP16
Реле IN2: нагревательD8GPIO 27GP17
Затвор MOSFET (через 220 Ω): светодиодыD9GPIO 18GP19
Датчик влажности почвы, вывод AOUTA0GPIO 34GP26
Средняя точка делителя с LDRA1GPIO 35 (делитель от 3V3)GP27 (делитель от 3V3)
I2C LCD: выводы SDA / SCLA4 / A5GPIO 21 / 22GP4 / GP5
Питание датчиков5V3V33V3
Пины ESP32 и Pico работают от 3,3 V и не выдерживают 5 V. На этих платах питай DHT22, датчик влажности почвы и делитель с LDR от пина 3V3, чтобы на аналоговый пин не попадало больше 3,3 V. Модуль реле 5 V с активным LOW иногда не отпускает полностью при сигнале HIGH 3,3 V; если реле остаётся включённым, на модуле с JD-VCC подай на VCC 3,3 V, а на JD-VCC — 5 V, или купи модуль, срабатывающий от 3,3 V. Для светодиодной ленты выбирай MOSFET, который полностью открывается при напряжении на затворе 3,3 V (например, IRLB8721).

Пороги и гистерезис: почему реле постоянно щёлкают?

Представь, что вентилятор управляется одним порогом: «включить, если температура выше 30 °C, иначе выключить». Пока температура колеблется между 29,9 и 30,1 °C (а из-за шумов датчика так бывает часто), реле переключается при каждом измерении. Изнашиваются и контакты реле, и мотор вентилятора, а в случае нагревателя при каждом включении лишнюю нагрузку получают ещё и проводка, и блок питания. Решение — гистерезис: для включения и выключения используются два разных порога. Вентилятор включается при 30 °C, но выключается, только когда температура опустится до 27 °C. В мёртвой зоне шириной 3 градуса между ними система сохраняет последнее состояние. По тому же принципу работают комнатные термостаты котлов и холодильники.

УправлениеВключаетсяВыключаетсяМёртвая зона
Вентилятор (температура)≥ 30 °C≤ 27 °C3 °C
Вентилятор (влажность)≥ 85%≤ 75%10%
Нагреватель≤ 16 °C≥ 19 °C3 °C
ПоливПочва ≤ 35%Почва ≥ 50%15%
Светодиодное освещениеСвет ≤ 25%Свет ≥ 35% или через 4 часа10%

Ширина мёртвой зоны — это компромисс: если она слишком узкая, реле всё равно переключается часто, а если слишком широкая, температура уходит далеко от цели. Для начала хорошо подходят около 2–3 °C для температуры, 8–10% для влажности воздуха и 10–15% для влажности почвы. Кроме того, между 19 °C, где выключается нагреватель, и 30 °C, где включается вентилятор, оставлен большой промежуток, чтобы нагреватель и вентилятор по температуре никогда не работали друг против друга. В коде гистерезис живёт в маленькой функции hysteresis(), чтобы не писать его заново для каждого управления: если порог включения больше порога выключения, это значит «включать при росте» (вентилятор), если меньше — «включать при падении» (нагреватель, полив, светодиоды). Если хочется ещё и сгладить выбросы в данных датчиков, загляни в статью о фильтрации данных с датчиков.

Структура кода: измерить, решить, выполнить, показать

Самое важное правило кода теплицы: ни одна задача не ждёт другую. Если после насоса ждать целую минуту с delay(60000), всё это время вентилятор не будет работать, а дисплей не будет обновляться. Поэтому код состоит из маленьких функций, которые отсчитывают время через millis() и отвечают каждая за одно дело; loop() вызывает их по очереди тысячи раз в секунду, и каждая функция что-то делает, только когда пришло её время. Полив — это маленький конечный автомат с тремя состояниями: ожидание (IDLE), полив (PUMPING) и впитывание воды (SOAKING). Все настройки собраны константами в начале кода, так что ради изменения порога не придётся копаться во всём коде. Почему выбран такой подход, объясняют статьи о millis() и delay() и о конечных автоматах на Arduino. Кроме того, в коде есть такие меры безопасности:

  • Безопасный запуск: значение «выключено» записывается в пины реле ещё до вызова pinMode(), поэтому реле не щёлкают даже на мгновение, пока Arduino загружается.
  • Отказ датчика: если DHT22 не удаётся прочитать три раза подряд, код переходит в безопасное состояние: выключает нагреватель, включает вентилятор и показывает на LCD «Sensor hatasi» (ошибка датчика). Система, которая не знает температуру, ни в коем случае не должна оставлять нагреватель включённым.
  • Защита бака: когда поплавковый датчик сообщает, что бак пуст, насос вообще не запускается, а если уже работает — сразу останавливается.
  • Защита от перелива: если за один цикл полива после шести импульсов влажность почвы так и не дошла до 50%, возможно, датчик вылез из почвы или соскочил шланг; код прекращает полив и показывает «Sulama hatasi» (ошибка полива). Предупреждение снимается само, как только почва снова становится влажной.
  • Запись данных: каждые 10 секунд код выводит показания в монитор порта строкой через запятую (CSV); эти строки можно скопировать и построить по ним графики в Excel.

Пошаговая сборка и проверка

  1. Подготовь корпус: прорежь отверстия для вентилятора, притока воздуха, шланга и проводов, а коробку с электроникой поставь снаружи теплицы.
  2. Собери шины питания: подключи блок питания 12 V к винтовой клемме, выставь на LM2596 5,0 V по мультиметру и соедини все GND.
  3. Подключи I2C LCD и выведи на него какой-нибудь текст. Если дисплей ничего не показывает, попробуй скетч-сканер и подстроечный резистор контраста из статьи об I2C LCD.
  4. Добавляй датчики по одному и проверяй их в мониторе порта: запиши показания DHT22, сырое значение датчика почвы в воздухе и в воде и значение LDR в темноте и на солнце.
  5. Сначала проверь реле без нагрузки: следи за их светодиодами и прислушивайся к щелчку. Потом подключи вентилятор, насос (при полном баке) и в последнюю очередь нагреватель.
  6. Подключи светодиодную ленту через MOSFET и меняй значение analogWrite(), чтобы увидеть, как меняется яркость.
  7. Загрузи основной код, впиши свои калибровочные значения в константы и несколько часов погоняй систему под присмотром: нет ли ошибочного полива, реле, которое всё время щёлкает, или детали, которая слишком греется?
Не пытайся подключить всё сразу и запустить с первого раза. Когда шесть модулей не работают вместе, поиск неисправности может занять часы; модули, проверенные по одному, после объединения обычно работают с первой попытки.

Полный код для Arduino Uno

Чтобы скомпилировать код, установи через «Менеджер библиотек» (Library Manager) библиотеку DHT sensor library от Adafruit (вместе с её зависимостью Adafruit Unified Sensor) и библиотеку LiquidCrystal I2C автора Frank de Brabander; Wire входит в состав Arduino IDE. Адрес LCD обычно 0x27, на некоторых модулях — 0x3F. Перед загрузкой впиши измеренные калибровочные значения в константы SOIL_DRY_RAW, SOIL_WET_RAW, LDR_DARK_RAW и LDR_BRIGHT_RAW. Если твой поплавковый датчик в воде размыкается, а не замыкается, задай TANK_OK значение HIGH; если модуль реле с активным HIGH, поменяй местами значения RELAY_ON и RELAY_OFF.

C++ (Arduino IDE)
1#include <Wire.h>
2#include <LiquidCrystal_I2C.h>
3#include <DHT.h>
4
5// Pinler / Pins
6const byte DHT_PIN = 2, FLOAT_PIN = 3, PUMP_RELAY = 4, FAN_RELAY = 7, HEATER_RELAY = 8;
7const byte LED_PIN = 9;                          // MOSFET gate (PWM)
8const byte SOIL_PIN = A0, LDR_PIN = A1;
9
10// Kalibrasyon: kendi ölçümlerini yaz / Calibration: write your own readings
11const int SOIL_DRY_RAW = 520, SOIL_WET_RAW = 260;   // havada ve suda / in air and in water
12const int LDR_DARK_RAW = 60, LDR_BRIGHT_RAW = 900;  // karanlık ve güneş / dark and sunlight
13
14// Eşikler (açma, kapama) / Thresholds (on, off)
15const float FAN_ON_C = 30, FAN_OFF_C = 27;       // sıcaklıkta fan / fan on heat
16const float HUM_ON = 85, HUM_OFF = 75;           // nemde fan / fan on humidity
17const float HEAT_ON_C = 16, HEAT_OFF_C = 19;     // ısıtıcı / heater
18const float SOIL_DRY = 35, SOIL_OK = 50;         // sulama başla, dur / start, stop watering
19const float DARK_ON = 25, DARK_OFF = 35;         // ışık % / light %
20
21// Zamanlar / Timing
22const unsigned long READ_MS = 2500;              // DHT22: en az 2 sn / at least 2 s
23const unsigned long PUMP_MS = 3000;              // tek sulama darbesi / one watering pulse
24const unsigned long SOAK_MS = 60000;             // suyun yayılması / let the water soak in
25const byte MAX_PULSES = 6;                       // fazlası arıza / more means a fault
26const unsigned long EXTRA_LIGHT_MS = 4UL * 3600000UL;  // günü 4 saat uzat / extend the day by 4 h
27const byte LED_LEVEL = 200;                      // LED parlaklığı 0-255 / LED brightness 0-255
28const byte RELAY_ON = LOW, RELAY_OFF = HIGH;     // aktif LOW röle / active-LOW relay
29const byte TANK_OK = LOW;                        // şamandıra suyla kapanır / float closes with water
30
31DHT dht(DHT_PIN, DHT22);
32LiquidCrystal_I2C lcd(0x27, 16, 2);
33
34float tempC = NAN, hum = NAN, soil = 0, light = 0;
35byte dhtErrors = 0, pulses = 0, page = 0;
36bool fanByTemp = false, fanByHum = false, fanOn = false, heaterOn = false;
37bool needWater = false, pumpFault = false, isDark = false, ledOn = false, tankOk = true;
38unsigned long lastRead = 0, lastLcd = 0, lastLog = 0, pumpAt = 0, darkSince = 0;
39
40enum PumpState { IDLE, PUMPING, SOAKING };
41PumpState pumpState = IDLE;
42
43// onAt > offAt: yükselince aç (fan) / turn on when rising (fan)
44// onAt < offAt: düşünce aç (ısıtıcı) / turn on when falling (heater)
45bool hysteresis(bool state, float value, float onAt, float offAt) {
46  if (onAt > offAt) {
47    if (value >= onAt) return true;
48    if (value <= offAt) return false;
49  } else {
50    if (value <= onAt) return true;
51    if (value >= offAt) return false;
52  }
53  return state;                                  // ölü bölgede durumu koru / keep state in the dead band
54}
55
56void setRelay(byte pin, bool on) {
57  digitalWrite(pin, on ? RELAY_ON : RELAY_OFF);
58}
59
60float readPercent(byte pin, int raw0, int raw100) {
61  long sum = 0;
62  for (byte i = 0; i < 10; i++) sum += analogRead(pin);   // 10 ölçüm ortalaması / average of 10
63  long pct = map(sum / 10, raw0, raw100, 0, 100);
64  return constrain(pct, 0, 100);
65}
66
67void setup() {
68  Serial.begin(9600);
69  setRelay(PUMP_RELAY, false);   pinMode(PUMP_RELAY, OUTPUT);    // açılışta kapalı / off at boot
70  setRelay(FAN_RELAY, false);    pinMode(FAN_RELAY, OUTPUT);
71  setRelay(HEATER_RELAY, false); pinMode(HEATER_RELAY, OUTPUT);
72  pinMode(LED_PIN, OUTPUT);
73  pinMode(FLOAT_PIN, INPUT_PULLUP);
74  dht.begin();
75  lcd.init();
76  lcd.backlight();
77  lcd.print(F("BlueGrays Sera"));
78  Serial.println(F("sn,sicaklik,nem,toprak,isik,fan,isitici,pompa,led"));  // CSV başlığı / CSV header
79  delay(2000);                                   // DHT22 açılış süresi / DHT22 start-up time
80  readSensors();
81  lastRead = millis();
82}
83
84void loop() {
85  unsigned long now = millis();
86  if (now - lastRead >= READ_MS) {
87    lastRead = now;
88    readSensors();
89  }
90  controlClimate();
91  controlLight(now);
92  controlPump(now);
93  updateLcd(now);
94  logCsv(now);
95}
96
97// 1) Ölç / Measure
98void readSensors() {
99  float t = dht.readTemperature();
100  float h = dht.readHumidity();
101  if (isnan(t) || isnan(h)) {
102    if (dhtErrors < 255) dhtErrors++;            // okuma hatası / read error
103  } else {
104    tempC = t;
105    hum = h;
106    dhtErrors = 0;
107  }
108  soil = readPercent(SOIL_PIN, SOIL_DRY_RAW, SOIL_WET_RAW);
109  light = readPercent(LDR_PIN, LDR_DARK_RAW, LDR_BRIGHT_RAW);
110  tankOk = digitalRead(FLOAT_PIN) == TANK_OK;
111}
112
113// 2) Fan ve ısıtıcı / Fan and heater
114void controlClimate() {
115  if (dhtErrors >= 3) {                          // sensör yok: güvenli durum / no sensor: safe state
116    heaterOn = false;
117    fanByTemp = true;
118  } else {
119    fanByTemp = hysteresis(fanByTemp, tempC, FAN_ON_C, FAN_OFF_C);
120    fanByHum = hysteresis(fanByHum, hum, HUM_ON, HUM_OFF);
121    heaterOn = hysteresis(heaterOn, tempC, HEAT_ON_C, HEAT_OFF_C);
122  }
123  fanOn = fanByTemp || fanByHum;
124  setRelay(FAN_RELAY, fanOn);
125  setRelay(HEATER_RELAY, heaterOn);
126}
127
128// 3) LED aydınlatma / LED grow light
129void controlLight(unsigned long now) {
130  bool wasDark = isDark;
131  isDark = hysteresis(isDark, light, DARK_ON, DARK_OFF);
132  if (isDark && !wasDark) darkSince = now;      // akşam oldu / dusk detected
133  ledOn = isDark && (now - darkSince < EXTRA_LIGHT_MS);
134  analogWrite(LED_PIN, ledOn ? LED_LEVEL : 0);
135}
136
137// 4) Sulama: darbe + bekleme / Watering: pulse + soak
138void controlPump(unsigned long now) {
139  needWater = hysteresis(needWater, soil, SOIL_DRY, SOIL_OK);
140  if (!needWater) {
141    pulses = 0;
142    pumpFault = false;                           // toprak ıslandı / soil is wet again
143  }
144  switch (pumpState) {
145    case IDLE:
146      if (needWater && tankOk && !pumpFault) {
147        if (pulses >= MAX_PULSES) {
148          pumpFault = true;                      // nem yükselmiyor / moisture is not rising
149        } else {
150          pumpState = PUMPING;
151          pumpAt = now;
152          pulses++;
153        }
154      }
155      break;
156    case PUMPING:
157      if (now - pumpAt >= PUMP_MS || !tankOk) {
158        pumpState = SOAKING;
159        pumpAt = now;
160      }
161      break;
162    case SOAKING:
163      if (now - pumpAt >= SOAK_MS) pumpState = IDLE;
164      break;
165  }
166  setRelay(PUMP_RELAY, pumpState == PUMPING);
167}
168
169// 5) LCD: üç sayfa / three pages
170void updateLcd(unsigned long now) {
171  if (now - lastLcd < 3000) return;
172  lastLcd = now;
173  page = (page + 1) % 3;
174  lcd.clear();
175  if (page == 0) {
176    lcd.print(F("Sicaklik: "));
177    if (isnan(tempC)) lcd.print(F("--")); else lcd.print(tempC, 1);
178    lcd.print('C');
179    lcd.setCursor(0, 1);
180    lcd.print(F("Hava nemi: %"));
181    if (isnan(hum)) lcd.print(F("--")); else lcd.print(hum, 0);
182  } else if (page == 1) {
183    lcd.print(F("Toprak: %"));
184    lcd.print(soil, 0);
185    lcd.setCursor(0, 1);
186    lcd.print(F("Isik: %"));
187    lcd.print(light, 0);
188  } else {
189    lcd.print(F("F:"));  lcd.print(fanOn);
190    lcd.print(F(" I:")); lcd.print(heaterOn);
191    lcd.print(F(" P:")); lcd.print(pumpState == PUMPING);
192    lcd.print(F(" L:")); lcd.print(ledOn);
193    lcd.setCursor(0, 1);
194    if (dhtErrors >= 3) lcd.print(F("Sensor hatasi"));
195    else if (!tankOk) lcd.print(F("Depo bos!"));
196    else if (pumpFault) lcd.print(F("Sulama hatasi"));
197    else lcd.print(F("Sistem normal"));
198  }
199}
200
201// 6) Seri port CSV kaydı / Serial CSV log
202void logCsv(unsigned long now) {
203  if (now - lastLog < 10000) return;
204  lastLog = now;
205  Serial.print(now / 1000);  Serial.print(',');
206  Serial.print(tempC, 1);    Serial.print(',');
207  Serial.print(hum, 0);      Serial.print(',');
208  Serial.print(soil, 0);     Serial.print(',');
209  Serial.print(light, 0);    Serial.print(',');
210  Serial.print(fanOn);       Serial.print(',');
211  Serial.print(heaterOn);    Serial.print(',');
212  Serial.print(pumpState == PUMPING); Serial.print(',');
213  Serial.println(ledOn);
214}
ФункцияЗадачаКак часто?
readSensors() (чтение датчиков)Читает DHT22, влажность почвы, LDR и поплавковый датчик; переводит аналоговые значения в 0–100% по среднему из 10 измеренийКаждые 2,5 секунды
controlClimate() (климат)Управляет вентилятором по гистерезису температуры или влажности, а нагревателем — по гистерезису температуры; при отказе датчика переходит в безопасное состояниеВ каждом проходе цикла
controlLight() (свет)Включает светодиоды с ШИМ, когда темнеет, и выключает их не позже чем через 4 часаВ каждом проходе цикла
controlPump() (насос)Конечный автомат полива: полить 3 с, подождать 60 с, снова измерить; с защитой бака и защитой от переливаВ каждом проходе цикла
updateLcd() (дисплей)Переключает три страницы: показания воздуха, влажность почвы и свет, состояние устройствКаждые 3 секунды
logCsv() (журнал CSV)Выводит показания и состояния в монитор порта строкой CSVКаждые 10 секунд

На первой странице LCD — температура и влажность воздуха, на второй — влажность почвы и уровень освещённости, на третьей — состояние устройств: «F:1 I:0 P:0 L:1» означает, что вентилятор работает, нагреватель (I — от турецкого ısıtıcı, «нагреватель») и насос выключены, а светодиоды горят. В нижней строке появляется одно из сообщений: «Sistem normal» (всё в порядке), «Depo bos!» (бак пуст), «Sulama hatasi» (ошибка полива) или «Sensor hatasi» (ошибка датчика); эти строки в коде легко перевести. После компиляции этот код занимает около 31% памяти программ Uno и 25% её ОЗУ, так что места с запасом хватит на второй датчик почвы, зуммер или модуль часов реального времени (RTC). Поскольку у Uno нет настоящих часов, правило для светодиодов работает как «не больше 4 часов после наступления темноты» — хороший способ продлить короткие зимние дни. Если нужно, чтобы светодиоды включались каждый день в заданное время, добавь модуль RTC или перейди на версию для ESP32 ниже.

Мониторинг и полив с телефона через ESP32

Отладочная плата ESP32
ESP32: благодаря Wi-Fi за теплицей можно следить с телефона, а время из интернета позволяет включать светодиоды по реальным часам.

У Uno нет Wi-Fi. Если хочется видеть состояние теплицы на телефоне и поливать удалённо, замени «мозг» на ESP32. Код ниже переносит ту же логику управления на ESP32 и запускает небольшой веб-сервер. Подключи телефон к той же сети Wi-Fi, открой в браузере IP-адрес, показанный в мониторе порта, и увидишь температуру, влажность, влажность почвы, освещённость, состояние бака и то, включено или выключено каждое устройство; страница сама обновляется каждые 10 секунд. Кнопка Water now («Полить сейчас») один раз включает насос на 3 секунды, если в баке есть вода. При запуске ESP32 получает время из интернета (NTP), поэтому светодиоды включаются только с 06:00 до 20:00 и только когда снаружи темно, а ночью растения отдыхают.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WebServer.h>
3#include <DHT.h>
4#include <time.h>
5
6const char* ssid = "AgAdi";                      // Wi-Fi adın / your Wi-Fi name
7const char* password = "Sifre123";               // Wi-Fi şifren / your Wi-Fi password
8
9// Pinler / Pins
10const int DHT_PIN = 4, FLOAT_PIN = 13, PUMP_RELAY = 25, FAN_RELAY = 26, HEATER_RELAY = 27;
11const int LED_PIN = 18;                          // MOSFET gate (PWM)
12const int SOIL_PIN = 34, LDR_PIN = 35;           // ADC1: Wi-Fi açıkken çalışır / works with Wi-Fi on
13
14// Kalibrasyon (0-4095): kendin ölç / Calibration (0-4095): measure your own
15const int SOIL_DRY_RAW = 2800, SOIL_WET_RAW = 1200;
16const int LDR_DARK_RAW = 200, LDR_BRIGHT_RAW = 3800;
17
18// Eşikler (açma, kapama) / Thresholds (on, off)
19const float FAN_ON_C = 30, FAN_OFF_C = 27, HUM_ON = 85, HUM_OFF = 75;
20const float HEAT_ON_C = 16, HEAT_OFF_C = 19, SOIL_DRY = 35, SOIL_OK = 50;
21const float DARK_ON = 25, DARK_OFF = 35;
22const int LIGHT_FROM = 6, LIGHT_TO = 20;         // LED yalnız 06:00-20:00 / LEDs only 06:00-20:00
23
24const unsigned long READ_MS = 2500, PUMP_MS = 3000, SOAK_MS = 60000;
25const unsigned long EXTRA_LIGHT_MS = 4UL * 3600000UL;  // saat yoksa yedek kural / fallback without a clock
26const int MAX_PULSES = 6, LED_LEVEL = 200;
27const int RELAY_ON = LOW, RELAY_OFF = HIGH;      // aktif LOW röle / active-LOW relay
28const int TANK_OK = LOW;                         // şamandıra suyla kapanır / float closes with water
29
30DHT dht(DHT_PIN, DHT22);
31WebServer server(80);
32
33float tempC = NAN, hum = NAN, soil = 0, light = 0;
34int dhtErrors = 0, pulses = 0;
35bool fanByTemp = false, fanByHum = false, fanOn = false, heaterOn = false;
36bool needWater = false, pumpFault = false, isDark = false, ledOn = false, tankOk = true;
37unsigned long lastRead = 0, pumpAt = 0, darkSince = 0;
38
39enum PumpState { IDLE, PUMPING, SOAKING };
40PumpState pumpState = IDLE;
41
42bool hysteresis(bool state, float value, float onAt, float offAt) {
43  if (onAt > offAt) {                            // yükselince aç / turn on when rising
44    if (value >= onAt) return true;
45    if (value <= offAt) return false;
46  } else {                                       // düşünce aç / turn on when falling
47    if (value <= onAt) return true;
48    if (value >= offAt) return false;
49  }
50  return state;
51}
52
53void setRelay(int pin, bool on) {
54  digitalWrite(pin, on ? RELAY_ON : RELAY_OFF);
55}
56
57float readPercent(int pin, int raw0, int raw100) {
58  long sum = 0;
59  for (int i = 0; i < 16; i++) sum += analogRead(pin);    // 16 ölçüm ortalaması / average of 16
60  long pct = map(sum / 16, raw0, raw100, 0, 100);
61  return constrain(pct, 0, 100);
62}
63
64int hourNow() {
65  struct tm t;
66  if (!getLocalTime(&t, 0)) return -1;           // saat henüz yok / time not set yet
67  return t.tm_hour;
68}
69
70void readSensors() {
71  float t = dht.readTemperature();
72  float h = dht.readHumidity();
73  if (isnan(t) || isnan(h)) {
74    dhtErrors++;
75  } else {
76    tempC = t;
77    hum = h;
78    dhtErrors = 0;
79  }
80  soil = readPercent(SOIL_PIN, SOIL_DRY_RAW, SOIL_WET_RAW);
81  light = readPercent(LDR_PIN, LDR_DARK_RAW, LDR_BRIGHT_RAW);
82  tankOk = digitalRead(FLOAT_PIN) == TANK_OK;
83}
84
85void controlClimate() {
86  if (dhtErrors >= 3) {                          // sensör yok: güvenli durum / no sensor: safe state
87    heaterOn = false;
88    fanByTemp = true;
89  } else {
90    fanByTemp = hysteresis(fanByTemp, tempC, FAN_ON_C, FAN_OFF_C);
91    fanByHum = hysteresis(fanByHum, hum, HUM_ON, HUM_OFF);
92    heaterOn = hysteresis(heaterOn, tempC, HEAT_ON_C, HEAT_OFF_C);
93  }
94  fanOn = fanByTemp || fanByHum;
95  setRelay(FAN_RELAY, fanOn);
96  setRelay(HEATER_RELAY, heaterOn);
97}
98
99void controlLight(unsigned long now) {
100  bool wasDark = isDark;
101  isDark = hysteresis(isDark, light, DARK_ON, DARK_OFF);
102  if (isDark && !wasDark) darkSince = now;
103  int h = hourNow();
104  if (h >= 0) ledOn = isDark && h >= LIGHT_FROM && h < LIGHT_TO;   // saat kuralı / clock rule
105  else ledOn = isDark && (now - darkSince < EXTRA_LIGHT_MS);       // yedek kural / fallback rule
106  analogWrite(LED_PIN, ledOn ? LED_LEVEL : 0);
107}
108
109void controlPump(unsigned long now) {
110  needWater = hysteresis(needWater, soil, SOIL_DRY, SOIL_OK);
111  if (!needWater) {
112    pulses = 0;
113    pumpFault = false;
114  }
115  if (pumpState == IDLE && needWater && tankOk && !pumpFault) {
116    if (pulses >= MAX_PULSES) {
117      pumpFault = true;                          // nem yükselmiyor / moisture is not rising
118    } else {
119      pumpState = PUMPING;
120      pumpAt = now;
121      pulses++;
122    }
123  } else if (pumpState == PUMPING && (now - pumpAt >= PUMP_MS || !tankOk)) {
124    pumpState = SOAKING;
125    pumpAt = now;
126  } else if (pumpState == SOAKING && now - pumpAt >= SOAK_MS) {
127    pumpState = IDLE;
128  }
129  setRelay(PUMP_RELAY, pumpState == PUMPING);
130}
131
132String row(const char* name, const String& value) {
133  return String("<tr><td>") + name + "</td><td><b>" + value + "</b></td></tr>";
134}
135
136String onOff(bool on) {
137  return on ? "ON" : "OFF";
138}
139
140void handleRoot() {
141  String html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
142                "<meta name='viewport' content='width=device-width,initial-scale=1'>"
143                "<meta http-equiv='refresh' content='10'><title>BlueGrays Sera</title>"
144                "<style>body{font-family:sans-serif;max-width:420px;margin:auto;padding:12px}"
145                "td{padding:8px;border-bottom:1px solid #ddd}"
146                "a{display:block;margin-top:16px;padding:16px;border-radius:10px;background:#2e7d32;"
147                "color:#fff;text-align:center;text-decoration:none;font-size:18px}</style>"
148                "</head><body><h2>BlueGrays Sera</h2><table>";
149  html += row("Sıcaklık / Temperature", String(tempC, 1) + " °C");
150  html += row("Hava nemi / Humidity", "%" + String(hum, 0));
151  html += row("Toprak nemi / Soil", "%" + String(soil, 0));
152  html += row("Işık / Light", "%" + String(light, 0));
153  html += row("Fan", onOff(fanOn));
154  html += row("Isıtıcı / Heater", onOff(heaterOn));
155  html += row("Pompa / Pump", onOff(pumpState == PUMPING));
156  html += row("LED", onOff(ledOn));
157  html += row("Depo / Tank", tankOk ? "OK" : "BOŞ / EMPTY");
158  if (pumpFault) html += row("Uyarı / Warning", "Sulama hatası / Watering fault");
159  html += "</table><a href='/water'>Şimdi sula / Water now</a></body></html>";
160  server.send(200, "text/html", html);
161}
162
163void handleWater() {                             // elle tek darbe / one manual pulse
164  if (pumpState == IDLE && tankOk) {
165    pumpState = PUMPING;
166    pumpAt = millis();
167  }
168  server.sendHeader("Location", "/");
169  server.send(303);
170}
171
172void setup() {
173  Serial.begin(115200);
174  setRelay(PUMP_RELAY, false);   pinMode(PUMP_RELAY, OUTPUT);
175  setRelay(FAN_RELAY, false);    pinMode(FAN_RELAY, OUTPUT);
176  setRelay(HEATER_RELAY, false); pinMode(HEATER_RELAY, OUTPUT);
177  pinMode(LED_PIN, OUTPUT);
178  pinMode(FLOAT_PIN, INPUT_PULLUP);
179  dht.begin();
180  WiFi.begin(ssid, password);
181  while (WiFi.status() != WL_CONNECTED) {
182    delay(500);
183    Serial.print('.');
184  }
185  Serial.println();
186  Serial.println(WiFi.localIP());                // bu adresi telefonda aç / open this address on your phone
187  configTime(3 * 3600, 0, "pool.ntp.org");       // Türkiye UTC+3; kendi saat dilimini yaz / set your own time zone
188  server.on("/", handleRoot);
189  server.on("/water", handleWater);
190  server.begin();
191  readSensors();
192  lastRead = millis();
193}
194
195void loop() {
196  server.handleClient();
197  unsigned long now = millis();
198  if (now - lastRead >= READ_MS) {
199    lastRead = now;
200    readSensors();
201  }
202  controlClimate();
203  controlLight(now);
204  controlPump(now);
205}

Вот что меняется по сравнению с кодом для Uno: АЦП ESP32 12-битный и выдаёт значения от 0 до 4095, поэтому калибровку нужно провести заново; к тому же датчики питаются от 3V3, так что сырые значения тоже будут другими. Часовой пояс задаётся в строке configTime(); для Турции (UTC+3) мы написали 3 × 3600 секунд, и это же значение подходит для московского времени. Для пояса без перехода на летнее время достаточно поменять число часов, а для пояса с переходом замени эту строку на configTzTime() со строкой часового пояса POSIX, например configTzTime("GMT0BST,M3.5.0/1,M10.5.0", "pool.ntp.org") для Великобритании. Если интернета нет или время ещё не получено, правило для светодиодов возвращается к правилу Uno «не больше 4 часов». У страницы нет пароля, так что насос может запустить любой в твоей домашней сети; добавить простой пароль с помощью функции authenticate() библиотеки WebServer — хорошая привычка. Если LCD нужен и на ESP32, функцию updateLcd() из кода для Uno можно перенести без изменений; SDA — это GPIO 21, SCL — GPIO 22.

Если в школе нет Wi-Fi, замени строку WiFi.begin() и следующий за ней цикл ожидания на WiFi.softAP("BlueGrays-Sera", "12345678"), и ESP32 создаст собственную сеть; телефон подключается к этой сети и открывает 192.168.4.1. Интернета в этом случае нет, поэтому время получить не удастся, и светодиоды будут работать по запасному правилу.
СпособДоступ вне домаСложностьДля чего лучше всего
Веб-сервер на ESP32 (код из этого руководства)Нет, только в той же сетиСредняяШкольные презентации, показ без интернета
Blynk IoTДаНизкаяГотовые шкалы, графики и уведомления
Telegram-ботДаСредняяОповещения «бак пуст» и «слишком высокая температура»
Google ТаблицыДаСредняяЗапись данных за несколько недель и графики
MQTT и Home AssistantДа, нужна настройкаВысокаяНастоящая автоматизация дома и сада

Для школьной презентации самый надёжный вариант — веб-сервер на ESP32: он не зависит от облачного сервиса и работает в собственной сети, даже если в зале нет интернета. Если хочется заглядывать в теплицу и вне дома, проще всего пойти через Blynk IoT с готовыми шкалами и графиками. Чтобы получать сообщение на телефон, когда бак опустел или температура стала опасной, загляни в руководство о Telegram-боте на ESP32; чтобы собрать данные за несколько недель для отчёта — в статью об ESP32 и Google Таблицах; а чтобы подключить теплицу к настоящей домашней автоматизации — в статью о MQTT и Home Assistant.

Версия на MicroPython для Raspberry Pi Pico

Ту же теплицу можно запустить и на Raspberry Pi Pico в MicroPython. В прошивку MicroPython для Pico уже встроен драйвер DHT, так что копировать дополнительные файлы не нужно. Аналоговые входы — только GP26, GP27 и GP28, а read_u16() возвращает значения от 0 до 65535; калибровочные числа измеряй с учётом этого. Pico работает от 3,3 V, поэтому датчики питай от 3V3, а модуль реле — от VBUS (5 V от USB) или от шины LM2596. Код ниже содержит все правила для вентилятора, нагревателя, светодиодов и полива и выводит значения в окно Shell среды Thonny. Если сохранить файл на Pico под именем main.py, плата будет работать, даже когда не подключена к компьютеру. Если у тебя Pico W с Wi-Fi, можно добавить способ из статьи о веб-сервере на Pico W, чтобы следить за значениями с телефона.

MicroPython
1from machine import Pin, ADC, PWM
2import dht
3import time
4
5sensor = dht.DHT22(Pin(15))              # GP15: DHT22 DATA
6soil_adc = ADC(26)                       # GP26: toprak nem AOUT / soil moisture AOUT
7ldr_adc = ADC(27)                        # GP27: LDR bölücü (3V3) / LDR divider (3V3)
8tank = Pin(14, Pin.IN, Pin.PULL_UP)      # şamandıra: suyla 0 / float switch: 0 with water
9fan = Pin(16, Pin.OUT, value=1)          # aktif LOW röleler kapalı başlar / active-LOW relays start off
10heater = Pin(17, Pin.OUT, value=1)
11pump = Pin(18, Pin.OUT, value=1)
12led = PWM(Pin(19))                       # MOSFET gate
13led.freq(1000)
14
15SOIL_DRY_RAW, SOIL_WET_RAW = 50000, 22000    # kendin ölç / measure your own
16LDR_DARK_RAW, LDR_BRIGHT_RAW = 2000, 60000
17EXTRA_LIGHT_MS = 4 * 3600 * 1000             # günü 4 saat uzat / extend the day by 4 h
18
19
20def percent(adc, raw0, raw100):
21    raw = sum(adc.read_u16() for _ in range(10)) // 10
22    pct = (raw - raw0) * 100 // (raw100 - raw0)
23    return max(0, min(100, pct))
24
25
26def hysteresis(state, value, on_at, off_at):
27    if on_at > off_at:                   # yükselince aç / turn on when rising
28        if value >= on_at:
29            return True
30        if value <= off_at:
31            return False
32    else:                                # düşünce aç / turn on when falling
33        if value <= on_at:
34            return True
35        if value >= off_at:
36            return False
37    return state                         # ölü bölge / dead band
38
39
40def relay(pin, on):
41    pin.value(0 if on else 1)
42
43
44t = h = None
45errors = pulses = 0
46fan_t = fan_h = heat_on = need_water = dark = led_on = False
47pump_state, pump_at, dark_since = "idle", 0, 0
48soil = light = 0
49tank_ok = True
50time.sleep(2)                            # DHT22 açılış süresi / DHT22 start-up time
51last_read = time.ticks_add(time.ticks_ms(), -2500)
52
53while True:
54    now = time.ticks_ms()
55    if time.ticks_diff(now, last_read) >= 2500:      # 2,5 sn'de bir ölç / measure every 2.5 s
56        last_read = now
57        try:
58            sensor.measure()
59            t, h = sensor.temperature(), sensor.humidity()
60            errors = 0
61        except OSError:
62            errors += 1                  # okuma hatası / read error
63        soil = percent(soil_adc, SOIL_DRY_RAW, SOIL_WET_RAW)
64        light = percent(ldr_adc, LDR_DARK_RAW, LDR_BRIGHT_RAW)
65        tank_ok = tank.value() == 0
66
67        if errors >= 3 or t is None:     # güvenli durum / safe state
68            heat_on, fan_t = False, True
69        else:
70            fan_t = hysteresis(fan_t, t, 30, 27)
71            fan_h = hysteresis(fan_h, h, 85, 75)
72            heat_on = hysteresis(heat_on, t, 16, 19)
73        relay(fan, fan_t or fan_h)
74        relay(heater, heat_on)
75
76        was_dark = dark
77        dark = hysteresis(dark, light, 25, 35)
78        if dark and not was_dark:
79            dark_since = now             # akşam oldu / dusk detected
80        led_on = dark and time.ticks_diff(now, dark_since) < EXTRA_LIGHT_MS
81        led.duty_u16(50000 if led_on else 0)
82
83        need_water = hysteresis(need_water, soil, 35, 50)
84        if not need_water:
85            pulses = 0
86        print("T", t, "N", h, "Toprak", soil, "Isik", light, "Pompa", pump_state,
87              "Depo OK" if tank_ok else "Depo BOS", "HATA" if pulses >= 6 else "")
88
89    # Sulama: 3 sn darbe, 60 sn bekleme / Watering: 3 s pulse, 60 s soak
90    if pump_state == "idle" and need_water and tank_ok and pulses < 6:
91        pump_state, pump_at = "pump", now
92        pulses += 1
93    elif pump_state == "pump" and (time.ticks_diff(now, pump_at) >= 3000 or not tank_ok):
94        pump_state, pump_at = "soak", now
95    elif pump_state == "soak" and time.ticks_diff(now, pump_at) >= 60000:
96        pump_state = "idle"
97    relay(pump, pump_state == "pump")
98    time.sleep_ms(100)

Советы для школьных презентаций и научных выставок

Умная теплица — настолько популярная тема, что жюри видит десятки похожих макетов. Выделиться поможет не просто демонстрация того, что система работает, а ответ на вопрос, подкреплённый измерениями. На научной выставке (например, TÜBİTAK 4006 в Турции) должны быть понятны цель, метод и ожидаемый результат; в исследовательских конкурсах (например, TÜBİTAK 2204) ждут сравнительного эксперимента с данными. Хороший исследовательский вопрос для этой теплицы: «Сколько воды экономит полив по влажности почвы по сравнению с поливом каждый день в одно и то же время и как это влияет на рост растений?»

  • Создай две группы: раздели рассаду одного вида и одной высоты на две группы. Одну группу поливай с помощью системы с датчиком, другую — вручную, одинаковым количеством воды каждый день; свет и температура для обеих групп должны быть одинаковыми.
  • Измеряй и записывай: веди таблицу с недельным расходом воды (по замерам бака, в мл), высотой растений (см) и количеством листьев. CSV-вывод кода или журнал в Google Таблицах бесплатно даст тебе график температуры и влажности.
  • Нарисуй структурную схему: датчики слева, Arduino в центре, исполнительные устройства справа; подпиши стрелки правилами («почва < 35% → насос»). Жюри не будет читать твой код, но эту схему запомнит.
  • Объясни гистерезис: два графика, показывающие, сколько раз за час включается и выключается вентилятор с одним порогом и вентилятор с гистерезисом, — лучший способ показать жюри инженерную сторону проекта.
  • Напиши сценарий живой демонстрации: согрей DHT22 в ладони, чтобы запустился вентилятор, переставь датчик почвы в сухой горшок, чтобы показать импульсы насоса, закрой LDR рукой, чтобы загорелись светодиоды. Каждая демонстрация — не дольше 10–20 секунд.
  • Свяжи проект с реальной жизнью: расскажи о настоящих проблемах — расходе воды и энергии в теплицах, грибковых болезнях из-за избыточной влажности, нагрузке на фермеров — и покажи цифрами, какие из них решает твоя система.
  • Подготовь запасной план: сними работающую систему на видео; возьми с собой запасные провода, запасной насос и блок питания; запиши калибровочные значения и настройки Wi-Fi на листке бумаги.
  • Подготовься к вопросам жюри: держи наготове короткие ответы на вопросы «Что будет, если датчик откажет?», «Что случится с растениями при отключении электричества?» и «Как масштабировать это до настоящей теплицы?». Лучшие ответы на них — безопасное состояние и защита от перелива в коде.
Если конкурс тематический, этому проекту естественнее всего подходит направление сельского хозяйства и пищевых технологий (в TÜBİTAK 4006 это тема Технологии сельского хозяйства и животноводства). Больше идей, таблицы стоимости и критерии оценки — в статье о проектах на Arduino для научной выставки.

Частые ошибки и как их исправить

ПризнакВероятная причинаРешение
Arduino перезагружается или на LCD появляется мусор, когда работает насосНасос и реле питаются от пина 5V Arduino, помехи от мотораПитай насос и катушки реле от LM2596, соедини GND, поставь у насоса обратный диод и конденсатор 470 µF
Реле всё время щёлкаютИспользуется один порог или показания шумныеИспользуй отдельные пороги включения и выключения (гистерезис) и усредняй аналоговые показания
Вода продолжает течь после остановки насосаЭффект сифона: конец шланга ниже уровня воды в бакеПоставь бак ниже или сделай маленькое воздушное отверстие в верхней точке шланга
Влажность почвы всегда 0% или всегда 100%Неверные калибровочные значения или AOUT подключён не к тому пинуИзмерь сырое значение датчика в воздухе и в воде и впиши эти значения в константы
Светодиоды то включаются, то выключаютсяLDR видит свет светодиодовВынеси LDR за пределы теплицы, лицом к небу
DHT22 возвращает nanНе тот пин, нет подтягивающего резистора или опрос чаще раза в 2 секундыПроверь пин, используй модуль или поставь 10 kΩ между DATA и VCC
Температура выше, чем должна бытьНа датчик прямо светит солнце или светодиодыЗатени его белым колпачком с отверстиями и отодвинь от нагревателя
Капли воды и плесень на стенкахСлишком высокая влажность, слишком слабая вентиляцияСнизь порог влажности, увеличь приточное отверстие, поливай меньше
На ESP32 показания почвы сбиваются при включении Wi-FiДатчик подключён к пину ADC2Перенеси его на пин ADC1 в диапазоне GPIO 32–39
С ESP32 реле никогда не отпускаетHIGH 3,3 V не может выключить модуль 5 V с активным LOWРаздели питание через JD-VCC или используй модуль реле, срабатывающий от 3,3 V

Проект умной теплицы объединяет в одной работе чтение датчиков, калибровку, принятие решений с гистерезисом, управление реле и MOSFET, планирование питания и беспроводной мониторинг, а в итоге выращивает что-то по-настоящему живое — растение. Начни с полива и вентиляции, проверь каждую часть отдельно, а затем добавь нагреватель, светодиоды и телефон. В приложении BlueGrays есть пошаговые проекты со схемами и кодом: «Температура и влажность с DHT11», «Измерение освещённости с LDR», «Умная станция полива» и «Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)».

Частые вопросы

Что нужно для проекта умной теплицы?

Для базовой умной теплицы достаточно Arduino Uno или ESP32, датчика температуры и влажности DHT22, ёмкостного датчика влажности почвы, LDR с резистором 10 kΩ, модуля реле, погружного мини-насоса и вентилятора. В полной версии добавляются PTC-нагреватель 12 V, светодиодная лента с управлением через MOSFET, I2C LCD, поплавковый датчик уровня, блок питания 12 V 5 A и LM2596. Для корпуса проще и дешевле всего взять прозрачный контейнер для хранения с крышкой.

Как работает умная теплица?

Умная теплица работает по циклу «измерить, решить, выполнить, показать». Датчики измеряют температуру, влажность, влажность почвы и освещённость; Arduino сравнивает эти значения с порогами и включает и выключает вентилятор, нагреватель, насос и светодиоды через реле или MOSFET. Результаты выводятся на LCD, а с ESP32 — ещё и на телефон.

Что ставить в теплицу: DHT11 или DHT22?

Выбирай DHT22. DHT11 точен лишь до ±2 °C в пределах от 0 до 50 °C и не видит влажность выше 90%, а в теплице утром влажность может подняться выше 90%, зимой же температура может опуститься ниже нуля. DHT22 измеряет температуру от −40 до 80 °C с точностью ±0,5 °C и влажность с точностью около ±2%, а код для него почти не отличается от кода для DHT11.

Сколько стоит проект теплицы на Arduino?

Цены зависят от продавца, но без учёта платы базовая версия (DHT22, датчик влажности почвы, LDR, реле, насос и вентилятор) обычно обходится примерно в 25–40 $. Полная версия с нагревателем, светодиодной лентой, LCD, блоком питания 12 V и корпусом выходит около 70–120 $. Если стартовый набор Arduino уже есть, стоимость заметно снижается.

Как следить за теплицей с телефона?

Проще всего заменить «мозг» на ESP32 и запустить веб-сервер: в той же сети Wi-Fi значения видны в браузере телефона, и оттуда же можно включить насос. Для доступа вне дома подойдёт Blynk IoT, для уведомлений — Telegram-бот, а для долгой записи данных — Google Таблицы.

Как представить умную теплицу на научной выставке?

Вместо того чтобы просто показывать работающий макет, поставь измеримый исследовательский вопрос: например, в течение нескольких недель на двух группах растений измеряй, сколько воды экономит полив по датчику по сравнению с поливом по расписанию. Представь расход воды, высоту растений и графики температуры и влажности в таблицах и на диаграммах, объясни систему структурной схемой и подготовь короткий сценарий для живой демонстрации.

Все проекты
Температура и влажность с DHT11
#55Средний

Температура и влажность с DHT11

Насколько тепло в твоей комнате и влажный ли воздух или сухой? Датчик DHT11 измеряет температуру и влажность, а Arduino выводит значения на двухстрочный LCD-экран и обновляет их каждые пару секунд. Ес

4.7(47)
73
Измерение освещённости с LDR
#11Новичок

Измерение освещённости с LDR

Хочешь увидеть яркость своей комнаты в виде числа? LDR — маленькая деталь, реагирующая на свет, — измеряет освещённость вокруг, а Arduino каждые полсекунды выводит значение на экран компьютера. Закрой

4.6(81)
139
Измерение освещённости модулем LDR на ESP32
#113Новичок

Измерение освещённости модулем LDR на ESP32

Наблюдение в мониторе порта в реальном времени за сырым значением ADC модуля LDR и относительным процентом освещённости, вычисленным по опорным значениям темноты и света. Этот процент — не измерение в люксах. Учит логике калибровки модуля. Идеально выполнить его перед проектом 10, потому что он нужен для определения порога.

4.6(36)
55
Умная станция полива
#83Продвинутый
Premium

Умная станция полива

Садовник, который сам поливает растения, чтобы они не засохли, пока ты в отпуске. Датчик влажности почвы измеряет, насколько влажная земля; когда она сохнет, Arduino на несколько секунд включает водян

5.0(42)
57
Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)
#117Средний

Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)

Этот проект измеряет температуру и влажность воздуха датчиком DHT22 и в реальном времени показывает данные на I2C LCD 16x2. Данные читаются с датчика по однопроводному протоколу через библиотеку Adafruit DHT. Проект служит основой для IoT-метеостанций, умного дома или систем мониторинга среды в теплицах. Связь с DHT22 идёт через пин GPIO 4 у ESP32.

4.8(38)
56
Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Продвинутый
Premium

Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

Собери на ESP32 регистратор температуры с отметками времени! Каждую минуту читай температуру и влажность с DHT22, бери реальное время из DS1307 RTC и записывай всё на карту microSD в формате CSV. OLED показывает текущую температуру (крупным шрифтом), влажность в процентах, время и счётчик записей. Файл /temp_log.csv на SD-карте открывается в Excel, Google Sheets или Python pandas — идеально для теплиц, комнат, лабораторий и хранения продуктов.

4.7(7)
12
Умное освещение на ESP32 (PIR + LDR + реле + OLED)
#141Продвинутый
Premium

Умное освещение на ESP32 (PIR + LDR + реле + OLED)

Сделай умное освещение для коридора, ванной или кладовки на ESP32! С PIR-датчиком движения, датчиком освещённости LDR и модулем реле собирается классический сценарий «свет включается при движении в темноте»: если вокруг уже светло, движение свет НЕ включает, и днём энергия экономится. Свет горит 30 секунд; если движение продолжается, таймер сбрасывается, и свет горит дольше. OLED SSD1306 крупно показывает состояние (ON / DAYTIME / WAITING) и обратный отсчёт.

4.3(6)
11