Мой первый дозатор поливал цветок ровно три секунды: открыл клапан digitalWrite(HIGH), посчитал паузу через delay(3000), закрыл. Работало до дня, когда из бака упало давление — за те же три секунды вылилось вдвое меньше, цветок остался сухим. Я удвоил время, а на следующий день давление вернулось, и на подоконнике было море — фиалка, которой всё это устраивалось, простила, а ламинат нет. Той ночью я понял главное правило дозирования: время без датчика — плохая мера объёма, и строить надо автомат, который перестаёт наливать не по часам, а по факту.
Железо здесь скромнее моторного: соленоидный клапан на реле, кнопка и поплавок, — но скелет у схемы общий со всем разделом: конечный автомат с состояниями «стоит — наливает — отдыхает». Тот же автомат гоняет очередь движений в уроке про очередь задач и светофор в уроке про автоматы состояний. А в шаровом кране с электроприводом, который прикручивается прямо в разрыв трубы, живёт всё та же 28BYJ-48 со своим редуктором — так что шаговик и здесь где-то рядом, просто спрятан в латунный корпус. И на всякий случай про питание катушки: ей нужны честные 12 вольт — от пятёрки на плате мембрана может не подняться вовсе; проверяйте щелчок пальцем до монтажа на трубу.
Соберём автомат на millis: старт по расписанию, стоп по поплавку, таймаут как вторая линия обороны. И разберём главную граблю жанра — ведро, которое переполнилось при полностью исправном коде.
Клапан на реле и почему не только часы
Соленоидный клапан — катушка с мембраной: подали напряжение, мембрана поднялась, проход открыт; сняли — мембрана села на седло и вода перекрыта. Катушки на 12 вольт едят 300-500 миллиампер, пин отдаёт сорок, поэтому между ними стоит реле-модуль: D7 щёлкнул — клапан открылся. Реле берите с оптроном на входе — оно не щёлкает само от наводок, а светодиод на плате показывает состояние без мультиметра. В симуляторе реле уже висит на пине 7 и рисует состояние лампочкой: горит — вода идёт. Для уличного полива берите клапан с сеткой на входе: одна соринка на седле мембраны, и клапан будет капать после закрытия всю ночь. И уточните в магазине тип клапана: почти все соленоидные — нормально закрытые, без тока сидят на седле, и это подарок безопасности: при пропадании питания вся система сама приходит в закрытое состояние. Нормально открытые клапаны, которые без тока открыты, держите для сливных задач и переворачивайте логику реле.
Наливать по секундомеру можно ровно в одном случае: когда давление и уровень источника не меняются. В реальности они меняются всегда — напор из бака падает по мере опустошения, фильтр забивается, шланг передавлен ведром. Аквариумные дозаторы, отмеряющие реагенты по два-три миллилитра в день на несколько помп сразу, давным-давно ушли от голых таймеров к капельному дозированию и датчикам уровня: на форумах считают литры в час — проекты с непрерывной подачей два-четыре литра в час там живые, только объём у них задают обороты перистальтической помпы, а не секунды клапана. Часы оставьте в скетче как верхнюю границу, но не как единственный стоп — иначе каждая перемена погоды будет вашей калибровкой.
Второй аргумент против чистых секунд — характер самой воды: пузырь в шланге, капля на выходе, первая струя после открытия холоднее и вязче. Всё это размазывает объём на десятки миллилитров при дозе в двести. Автомат, который смотрит на датчик, а не на часы, этим ловушкам не поддаётся: он закрывает клапан, когда вода пришла, а не когда вышло её положенное время. И учтите хвост: после закрытия клапан докапывает несколько секунд — в шланге остаётся столб воды, и эти миллилитры секунды тоже не видят.
Автомат состояний и поплавок как стоп
Автомат из трёх состояний. IDLE: клапан закрыт, ждём команды — расписания или кнопки. FILL: клапан открыт, запомнили t0 = millis() и теперь каждая итерация loop() опрашивает поплавок и таймаут. DONE: клапан закрыт, идёт пауза между дозами, чтобы следующая выдача не слилась с предыдущей. Переходы — только по условиям, ни одного delay(): миллис вместо delay здесь не стиль, а необходимость — задержка в FILL означала бы слепую зону, в которую попадает момент, когда вода дошла до края. Номер состояния — обычная int и пара if в loop(); диаграмму состояний на бумажке полезно нарисовать до кода: три коробки и стрелки с условиями — меньше, чем гора вложенных флагов, и отлаживается она за один взгляд.
Стоп-условие — поплавковый датчик уровня: шарнирный поплавок с герконом внутри, замыкает контакт, когда вода дотащила его до заданной высоты. Проводка та же, что у концевика из урока про стоп по датчику: сигнальный пин и земля, INPUT_PULLUP, замкнут — LOW. В симуляторе его роль играет кнопка на D2: зажали — уровень достигнут, отпустили — вода ушла. Никакого отдельного кода под датчик не нужно: для скетча это тот же цифровой вход. Выбирая поплавок в магазине, берите вариант с герконом, а не с микропереключателем на пластике: конденсат за сезон убивает рычажок и контакты, а геркон в герметичном корпусе живёт годами; крепление — вертикальное на резьбе с уплотнительным кольцом.
Вторая линия обороны — таймаут: если за N секунд уровень не поднялся, автомат закрывает клапан и пишет в Serial «таймаут, воды нет или поплавок залип». Это же спасает от пустого бака — клапан не висит открытым в никуда и не мотает катушку часами. У защиты есть цена: таймаут ставят больше реального налива с запасом на треть, иначе честная медленная заливка при слабом напоре будет отбраковываться как авария. Лимиты с запасом мы уже страховали в уроке про защиту от заклинивания — механика та же, изменился только виновник. И сделайте таймаут громким: строка в Serial с причиной отказа, а не молчаливый переход — авария, о которой не напечатано ни слова, не оставляет следов.
| Состояние | Выход реле | Переход дальше |
|---|---|---|
| IDLE — стоит | LOW, клапан закрыт | подошло расписание — в FILL |
| FILL — налив | HIGH, клапан открыт | поплавок замкнулся — в DONE; таймаут — в TIMEOUT |
| DONE — отдых | LOW | пауза истекла — назад в IDLE |
| TIMEOUT — отказ | LOW | строка в Serial, затем пауза и назад в IDLE |
Теперь обещанное ведро. Дозатор отстоя воды стоял в подсобке и работал месяц безупречно: поплавок на проволоке, клапан, счётчик доз. Зимой поплавок залип в верхнем положении — геркон прилип к магниту от конденсата и холода, — вода пошла мимо уже сработавшего датчика и лилась через край ведра минут двадцать. Клапан был исправен, код был исправен, автомат ждал сигнала, который физически не мог прийти. С тех пор правило: любой датчик в цепи воды дублируется таймаутом, а любое событие — счётчиком в Serial, чтобы авария оставляла след, по которому её можно найти утром, а не по луже. Первый запуск любого дозатора я теперь делаю в ванне с ведром на дне, а не на подоконнике — цена проверки нулевая, а привычка сэкономила уже не один коврик.
Счётчик доз в Serial — не украшение, а следственная запись: пара doza=число с отметкой, по какой причине закрылся клапан — поплавок или таймаут, — за неделю показывает и ритм вашего напора, и начинающийся залипающий поплавок, который стал срабатывать позже. Приёмы такой отладки логикой через лог разобраны в уроке про отладку через Serial — здесь они ровно те же, только вместо шагов мотора литры. Через две недели таких записей вы начнёте видеть свой водопровод как график: утренний пик напора, полуденный спад, вечернее восстановление — и поймёте, почему секундная калибровка из первой версии не работала никогда. Если захочется времени в записи, выводите рядом millis()/1000 — не часы, но видно, когда что случилось.
const int PIN_FLOAT = 3; // поплавок: замкнут на GND = уровень достигнутconst int PIN_BTN = 2; // кнопка ручной дозыconst int PIN_RELAY = 7; const unsigned long TIMEOUT_MS = 30000; // вторая линия обороныconst unsigned long PAUSE_MS = 5000; int state = 0; // 0 IDLE, 1 FILL, 2 DONEunsigned long t0 = 0; void setup() { pinMode(PIN_FLOAT, INPUT_PULLUP); pinMode(PIN_BTN, INPUT_PULLUP); pinMode(PIN_RELAY, OUTPUT); digitalWrite(PIN_RELAY, LOW); Serial.begin(9600); Serial.println("dozator gotov");} void loop() { unsigned long now = millis(); if (state == 0) { if (digitalRead(PIN_BTN) == LOW) { t0 = now; digitalWrite(PIN_RELAY, HIGH); state = 1; Serial.println("FILL: klapan otkryt"); } } else if (state == 1) { if (digitalRead(PIN_FLOAT) == LOW) { digitalWrite(PIN_RELAY, LOW); t0 = now; state = 2; Serial.println("DONE: uroven dostignut"); } else if (now - t0 >= TIMEOUT_MS) { digitalWrite(PIN_RELAY, LOW); t0 = now; state = 2; Serial.println("TIMEOUT: net vody ili poplavok zalip"); } } else { if (now - t0 >= PAUSE_MS) state = 0; }}- Автомат IDLE-FILL-DONE на millis без единого delay(): в FILL поплавок и таймаут опрашиваются каждым оборотом loop()
- Время — только верхняя граница: объём задают поплавок и уровень, а не секунды
- Поплавок электрически — концевик: INPUT_PULLUP, замкнут — LOW, нужны только сигнальный провод и земля
- Таймаут — вторая линия обороны: пустой бак и залипший поплавок не оставят клапан открытым навсегда
- В шаровом кране с электроприводом живёт та же 28BYJ-48: ход крана — конкретное число шагов из уроков раздела
- 1. Почему налив по секундомеру без датчика — плохой план?
- 2. Что делает автомат в состоянии FILL каждую итерацию loop()?
- 3. Поплавок замкнулся через 8 секунд, таймаут стоит 30. Что произойдёт?
- 4. Вода кончилась, поплавок молчит. Что спасает бак, клапан и пол?
- 5. Что находится внутри бюджетного шарового крана с электроприводом?
Клапан, поплавок, таймаут — три слова, на которых стоит вся бытовая автоматика воды, от автополива до заливного бачка аквариума. Прикрутите сверху счётчик доз с отметкой причины закрытия и раз в сезон пожмите поплавок пальцем, глядя в Serial, — и дозатор переживёт и зиму, и забытый кран, и вашего завтрашнего себя, который забудет, зачем ставил второй порог. Это последний бытовой урок серии: дальше по разделу вас ждёт механика с ремнём GT2 и расчёт каретки, где шаги снова превратятся в миллиметры. А если ваш дозатор переживёт месяц с двумя линиями обороны — значит, вы собрали не игрушку, а узел, который можно смело переносить в аквариум, автополив и в теплицу из первого урока.
Частые вопросы по теме урока
- Почему налив по секундомеру без датчика — плохой план?
- Секунды меряют время, а не объём: напор бака падает по мере опустошения, фильтр забивается — при одной и той же паузе выливается разный объём. Объём при той же секунде гуляет вместе с давлением и забитым фильтром
- Что делает автомат в состоянии FILL каждую итерацию loop()?
- Именно потому, что в FILL нет задержек, момент достижения уровня ловится сразу, а таймаут не даёт наливу стать бесконечным. Опрашивает поплавок и проверяет таймаут
- Поплавок замкнулся через 8 секунд, таймаут стоит 30. Что произойдёт?
- Стоп-условий два, срабатывает то, что пришло первым: поплавок обогнал таймаут, таймаут остался страховкой на случай, когда датчик молчит. Клапан закроется по поплавку, автомат уйдёт в DONE
- Вода кончилась, поплавок молчит. Что спасает бак, клапан и пол?
- Таймаут закрывает клапан, когда уровень не пришёл за отведённое время, и пишет причину — это защита и от пустого бака, и от залипшего поплавка. Таймаут со строкой отказа в Serial
- Что находится внутри бюджетного шарового крана с электроприводом?
- Бюджетные краны с электроприводом внутри несут моторчик 28BYJ-48: полный ход занимает пять-десять секунд и равен конкретному числу шагов. Та же 28BYJ-48 со своим редуктором