Полив у меня начался с одной грядки и одного клапана, а когда появилась вторая, я сделал самое дешёвое из возможного: второй клапан на второй пин, оба по расписанию в семь утра. Через три дня схема обошла меня саму себя: обе зоны открылись одновременно, насос не поднял давление на две линии сразу, вода сочилась вяло на обоих концах, а журнал показал, что насос честно отработал вдвое дольше обычного. Один насос — одна линия, и никакая арифметика расписания этого не отменяет: так в прибор пришла очередь.
Очередь — это когда зоны ставят заявки, а насос обслуживает их по одной, и следующая стартует только после закрытия предыдущей. Вокруг очереди два страховых пояса. Первый — таймаут: зона не имеет права лить дольше отпущенного, даже если клапан залип и скетч сошёл с ума. Второй — защита от сухого хода: пустая бочка убивает насос за считанные минуты, крыльчатка без воды не только не качает, но и не охлаждается. Расписание полива по часам RTC уже собрано в уроке про полив по расписанию, а гейт «поливать, только если сухо» — в уроке про автополив с датчиком почвы; здесь машина очереди и защита насоса.
Откуда скетч знает, что воды нет? Самый честный сигнал — ток насоса: всухую мотор крутится вхолостую и ест заметно меньше рабочего тока, и эта разница видна даже на дешёвом датчике. ACS712 на 5 А в разрыв питания насоса отдаёт 185 мВ на ампер, ноль тока — половина питания, и порог «ток есть» ищется один раз, Serial-монитором. Промышленная защита делает то же самое через реле давления с уставкой 0,1-0,6 бар: давление ниже порога — воды нет, насос замолкает; принцип у обоих решений общий, разница в цене и калибровке.
Очередь на millis-автомате
Состояний четыре: Покой, Зона один, Зона два, Блокировка. Заявку ставит расписание, кнопка или Serial — флаги zayavka[0] и zayavka[1]; машина в покое берёт первую заявку по очереди, открывает своё реле, запоминает старт на millis. Пока зона льёт, вторая заявка терпеливо ждёт — вот и вся очередь, целиком. Автомат состояний как приём разобран в уроке про автомат на состояниях; здесь он на четырёх состояниях, но каждый переход имеет цену в литрах, и потому честно прописан.
Таймаутов два, и они разные. Первый — потолок длительности зоны: у меня сто пятьдесят секунд против рабочих ста двадцати и девяноста, и его роль — ловить мусор. Однажды я забыл в массиве расписания ноль вместо девяноста секунд: клапан висел открытым, пока потолок не закрыл его сам, и с тех пор потолок живёт в каждом скетче с поливом — расписание ошибётся, скетч нет. Второй таймаут ждёт внутри защиты: если насос включён, а ток не поднялся за пятнадцать секунд — воды нет. Пятнадцать секунд — рабочая цифра бытовых станций: линия и вентиль заполняются за считанные секунды, и если за это время нагрузка не пришла, ждать больше нечего.
Заявки в моём приборе ставит расписание из окна по минутам: час и минута совпали — флаг встал, очередь взяла. Механика окна с флагом та же, что в расписании из урока про полив по часам, поэтому здесь она не повторяется; важно одно: заявка — это флаг, а не вызов реле. Пока зона занята, флаг висит и терпит: очередь обслужит его после закрытия текущей зоны, и от того, что между заявкой и стартом прошло сорок минут, ничего не портится — вода в бочке за это время не высыхает. Число зон растёт копированием строки в массиве и одного ветвления: третий клапан — это ещё один флаг, ещё одна длительность и один шаг состояния.
Блокировка — единственное состояние, из которого нет автоматического выхода: насос замолчал, оба реле разомкнуты, светодиод на корпусе горит, журнал печатает причину с меткой времени. Перезагрузка блокировку не отменяет — флаг лежит в EEPROM, и пустую бочку невозможно уговорить перезапуском питания. Сброс — только человек: кнопкой на корпусе или командой из Serial, после того как заглянул в бочку и понял причину. Бюджет ячеек под такой флаг смешной — запись случается раз в сезон, ресурс ячейки против него бесконечен, что я и считал в уроке про износ EEPROM.
#include <Wire.h>#include <RTClib.h> RTC_DS3231 rtc;const int ZONA1 = 7; // реле первой зоныconst int ZONA2 = 8; // реле второй зоныconst int ALARM = 12; // светодиод блокировки const unsigned long DLIT[2] = {120000UL, 90000UL}; // длительность зон, мсconst unsigned long POTOLOK = 150000UL; // потолок зоны с запасомconst unsigned long SUXOST = 15000UL; // ожидание подъёма токаconst int POROG = 120; // аналоговые единицы: ниже - воды нет int sostoyanie = 0; // 0 покой, 1-2 зоны, 3 блокировкаunsigned long tStart = 0, tSux = 0;bool zayavka[2] = {false, false}; void startZony(int z) { zayavka[z] = false; sostoyanie = z + 1; tStart = millis(); tSux = 0; if (z == 0) digitalWrite(ZONA1, HIGH); else digitalWrite(ZONA2, HIGH); Serial.print("Zona "); Serial.print(z + 1); Serial.println(" start");} void stopZony(int z, String prichina) { if (z == 0) digitalWrite(ZONA1, LOW); else digitalWrite(ZONA2, LOW); Serial.print("Zona "); Serial.print(z + 1); Serial.print(" stop: "); Serial.println(prichina); if (prichina.startsWith("SUHOI")) { sostoyanie = 3; digitalWrite(ALARM, HIGH); // тут в приборе EEPROM.write - флаг блокировки переживает перезагрузку Serial.println("BLOKIROVKA: sbros tolko rukami - knopka ili komanda S"); } else { sostoyanie = 0; }} void setup() { Serial.begin(9600); pinMode(ZONA1, OUTPUT); pinMode(ZONA2, OUTPUT); pinMode(ALARM, OUTPUT); digitalWrite(ZONA1, LOW); digitalWrite(ZONA2, LOW); digitalWrite(ALARM, LOW); if (rtc.begin() == false) { Serial.println("DS3231 ne najden: SDA A4, SCL A5"); while (1) {} } // заявки ставит расписание по rtc.now() как в уроке про полив по расписанию Serial.println("Ochered gotova: 1 - zona 1, 2 - zona 2, S - sbros blokirovki");} void loop() { if (Serial.available() > 0) { String s = Serial.readStringUntil(10); s.trim(); if (s.startsWith("12")) { zayavka[0] = true; zayavka[1] = true; } else if (s.startsWith("1")) zayavka[0] = true; else if (s.startsWith("2")) zayavka[1] = true; else if (s.startsWith("S") && sostoyanie == 3) { sostoyanie = 0; digitalWrite(ALARM, LOW); Serial.println("Blokirovka snyata: voda proverena"); } } int tok = analogRead(A0); // ACS712 в разрыве питания насоса if (sostoyanie == 0) { if (zayavka[0]) startZony(0); else if (zayavka[1]) startZony(1); } else if (sostoyanie == 1 || sostoyanie == 2) { int z = sostoyanie - 1; unsigned long polnoe = DLIT[z]; if (POTOLOK < polnoe) polnoe = POTOLOK; // потолок ловит мусор расписания bool vremya = millis() - tStart >= polnoe; bool suho = tok < POROG; if (suho && tSux == 0) tSux = millis(); if (suho == false) tSux = 0; if (vremya) stopZony(z, "vremya vyrabotano"); else if (suho && millis() - tSux >= SUXOST) stopZony(z, "SUHOI HOD: tok ne podnialsya"); } delay(100);}Прогон: команда 12 ставит обе заявки сразу, и в журнале видна вся суть урока — стартует первая, вторая молчит до её закрытия, потом стартует сама с собственной меткой времени. Теперь главное упражнение: во время полива уроните ручку тока ниже порога, и через десять секунд журнал напечатает СУХОЙ ХОД, оба реле замолчат, LED загорится. Команда S снимет блокировку; в моём приборе на её месте стоит кнопка на корпусе, и логика та же — без человека машина не оживает. Время в каждой строке журнала — живое, от rtc.now(), и это не украшение: по меткам вы потом разберёте, какая зона виновата в затопленном подоконнике.
На плате ручку меняет ACS712 в разрыве плюса насосного блока: ноль тока — 2,5 В, ампер — плюс 185 мВ, и порог в аналоговых единицах подбирается двумя запусками печати analogRead — с водой и со снятой трубкой. Заявки в приборе ставит расписание по rtc.now(), кнопка и Serial остаются для тестов, и машина очереди безразлична к происхождению заявки — это её главная сила. Насос при такой жизни живёт дольше, если считать его наработку: связка unixtime и моточасов собрана в уроке про наработку насоса, и она же подскажет, когда крыльчатке пора на чистку.
| Способ защиты от сухого хода | Что мерит | Где подводит |
|---|---|---|
| По току (ACS712, 185 мВ/А) | насос всухую ест заметно меньше рабочего тока | порог подбирается под конкретный насос, нужен замер |
| По давлению (реле 0,1-0,6 бар) | давление в линии после насоса | воздух в трубке имитирует работу, протечки путают |
| Поплавок в бочке | сам уровень воды до насоса | не видит засор и завоздушивание после бочки |
Блокировка, EEPROM и перезагрузка в разгар полива
Самая коварная перезагрузка этого прибора случается в момент старта насоса: бросок тока просаживает питание, плата взлетает заново, все переменные чисты. Машина в покое, заявки стёрты — казалось бы, безопасно. Но если сухой ход уже был пойман, а блокировка лежит в переменной, тот же сброс сотрёт и её: расписание снова откроет зону, и пустой насос снова получит питание. Поэтому флаг блокировки обязан жить в EEPROM: запись при входе в блокировку, ноль при ручном сбросе, чтение в setup — проснулся, прочитал единицу, молчит, пока человек не разберётся. Просадка в момент старта насоса перестаёт быть судьбой прибора и становится обычным перезапуском.
Таймауты связаны арифметикой, и её стоит зафиксировать: потолок больше длительности каждой зоны процентов на двадцать, и сравнение идёт с меньшим из двух. Роль потолка — страховка от автора скетча: ноль вместо девяноста в массиве расписания, забытая заявка, кривая правка на коленке в гостях. Я один раз проехал на этом сто пятьдесят секунд протечки — клапан висел открытым, пока потолок не закрыл его сам, и с тех пор потолок присутствует в каждом поливочном скетче, даже там, где расписание, кажется, выверено. Выверенное расписание не отвечает за своё будущее.
И про сборку, потому что защита по току любит аккуратную землю: ACS712 сидит в разрыве плюса насосного блока, земля насоса и платы соединяется в одной точке, иначе аналоговые единицы плавают вместе с нагрузкой. Бросок при старте мотора виден на датчике как короткий горб — это нормально: порог стоит выше горба, а окно в пятнадцать секунд переживает и горб, и гул запуска. Часы в силовой части не участвуют, но журнал с их метками — то, по чему вы потом соберёте хронологию каждой аварии: расклад меток и форматов я делал в уроке про журнал в Serial.
Отдельно про силовую часть, потому что клапаны бывают и на 220 вольт: реле-модуль берёт на себя изоляцию, но затянутые клеммы, короткие провода и недоступность винтов под пальцем остаются вашей заботой — разводка силовой половины с предохранителями разобрана в уроке про реле и 220 В. Мой полив живёт на 12-вольтовых соленоидах от отдельного блока, и после переезда с сетевого напряжения я сплю спокойнее: вода и 220 В в одном корпусе требуют уважения, которого 12 вольт не просят. Если остаётесь на 220 — предохранитель в разрыве фазы и общий рубильник обязательны, и это не перестраховка, а счёт на одну оплошность.
- 1. Почему обе зоны нельзя открывать одновременно?
- 2. Как скетч понимает, что насос пошёл всухую?
- 3. Сколько секунд защита ждёт подъёма тока на плате, прежде чем объявить сухой ход?
- 4. Зачем потолок таймаута, если у каждой зоны есть длительность?
- 5. Где живёт флаг блокировки и почему именно там?
- Один насос — одна линия: очередь на millis-автомате из состояний Покой, Зона 1, Зона 2, Блокировка
- Таймаут-потолок выше рабочих длительностей на 20% и ловит мусор расписания без вмешательства
- Сухой ход: всухую насос ест меньше рабочего тока; ACS712 отдаёт 185 мВ/А, окно ожидания 15 с
- Блокировка без авторазблокировки: флаг в EEPROM, сброс только человеком после проверки воды
- Заявке всё равно, откуда она пришла: расписание по rtc.now(), кнопка или Serial — машина одна и та же
Частые вопросы по теме урока
- Почему обе зоны нельзя открывать одновременно?
- Насос рассчитан на одну линию: на две он отдаёт давление в обе, вода сочится вяло, а время работы растёт. Очередь обслуживает зоны по одной. Один насос не поднимет давление на две линии сразу, полив станет вялым
- Как скетч понимает, что насос пошёл всухую?
- Без воды мотор крутится вхолостую и ест меньше рабочего тока. ACS712 переводит ток в напряжение, порог ищется одним замером Serial-монитором. По току насоса: всухую он заметно ниже рабочего
- Сколько секунд защита ждёт подъёма тока на плате, прежде чем объявить сухой ход?
- Линия и вентиль заполняются за считанные секунды: окно в 15 с переживает стартовый бросок тока и успевает увидеть рабочую нагрузку, но не тянет пустой ход дольше. Пятнадцать секунд
- Зачем потолок таймаута, если у каждой зоны есть длительность?
- Расписание ошибается: ноль вместо девяноста, правка на коленке. Потолок на двадцать процентов выше рабочих длительностей и закрывает зону сам. Чтобы ловить мусор расписания вроде нулевой длительности
- Где живёт флаг блокировки и почему именно там?
- Просадка питания в момент старта насоса — родная перезагрузка этого прибора. Флаг в EEPROM делает блокировку старше сбросов: проснулись — прочитали — молчим. В EEPROM: переживает перезагрузку, которую устраивает сам сухой ход