Мой кот выработал простой протокол: в 05:40 он открывает лапой дверь спальни и до подъёма не отстаёт. Будильник, поставленный на 05:35, только приучил нас обоих. Поэтому первая автоматическая кормушка собралась из того, что было под рукой: контейнер, моторчик со шнеком из проволоки и честное убеждение, что «кручу три секунды — сыплется порция». У моторчика нет обратной связи, и моя три секунды однажды превратилась в полную миску на полу: кормушка отдала всё, что имела. Вторая версия строилась уже на серво с заслонкой — и на расписании, а не на надежде.
Серво в этой роли сильнее моторчика одним свойством: она знает свой угол и держит его, сколько бы её ни просили. Три секунды открытия — это три секунды, а не «сколько завезёт напряжение»; порция перестала зависеть от заряда батареек и вязкости кормы. Именно эта предсказуемость и делает серво правильным приводом для дозатора: уроки всего раздела сходятся в этом проекте — от первых углов до поведения под нагрузкой.
Этот урок — итоговый по разделу: он собирает в один стенд почти всё, что мы прошли. Серво перекрывает щель по углу, как в уроке про серво-переключатель с двумя положениями, кнопка даёт ручную выдачу по фронту, светодиод показывает, что выдача идёт, а Serial печатает расписание и события. Полный скетч занимает пятьдесят строк, механика — один вечер с контейнером из хозяйственного магазина.
Механика: заслонка над воронкой
Конструкция без изысков: контейнер горлышком вниз, в щель шириной 20–25 мм сыплется корма, щель перекрывает рычаг серво — заслонка. Закрыто — 20°, открыто — 100°, и это не случайные числа: от крайних упоров я намеренно отступаю на 15–20°, чтобы серво не билась в механический ограничитель каждые 20 мс. Урок про облизанные шестерни обошёлся мне дороже, чем стоила бы эта привычка сразу. Порция здесь задаётся временем открытия: при корме с гранулой 4–6 мм три секунды открытой заслонки — это 12–15 гранул; для крупной кормы щель придётся расширить, и тогда SG90 может не хватить усилия на подпоре — берите MG90S с металлическими шестернями.
Питание скромное: одна SG90 с лёгкой заслонкой съедает 100–250 мА только в момент старта, всё остальное время стоит. USB-порт такое вывозит — это тот редкий случай, когда внешнее питание не обязательно; но если контейнер большой и заслонку подпирает столб кормы, ток при открытии уходит к 600–700 мА, и раз в неделю плата может перезагрузиться на выдаче. Я ставлю электролит 470 мкФ у серво и сплю спокойно. И проверьте узел без корма: сухая заслонка должна открываться в ту же секунду, когда загорелся светодиод, — пауза между ними означает просевшее питание.
Про воронку тоже два слова, потому что там живёт второй тупик. Корма в контейнере слёживается и нависает над щелью мостиком, особенно если в доме влажно: заслонка открывается, а сыпаться нечему. Лечится геометрией: стенки горловины под углом не круче 45° к вертикали, щель без уступов, и — мой любимый приём — вторая щель малого диаметра в паре сантиметров от первой, воздух через неё проходит и мостик ломает. Первую версию я испытывал не кормом, а гречкой из магазина: она суше и сыпучее, и если гречка проходит через сто циклов без застреваний, корма тем более. И держите контейнер съёмным: кормушка, которую нельзя разобрать без отвёртки, промывается ровно до первого раза, потом в ней живут запахи, против которых у кормы нет аргументов.
| Событие | Заслонка (пин 11) | Светодиод (пин 12) | Что в Serial |
|---|---|---|---|
| Ожидание | закрыта, 20° | погашен | раз в минуту: vremya=минуты sostoyanie=0 |
| Ручная выдача: кнопка на D2 | открыта 3 с | горит | kormlenie=ручная, затем zaslonka=zakryta |
| По расписанию 07:00 и 09:00 | открыта 3 с | горит | kormlenie=расписание vremya=… |
Теперь расписание. В симуляторе часы я собираю из millis(): каждая 600 мс виртуального времени — одна минута суток, день ужимается до наблюдаемого. В реальной кормушке millis()-часы живут ровно до первой потери питания: контроллер не знает, сколько сейчас времени, и «расписание от включения» уедет. Взрослое решение — модуль часов DS3231 с батарейкой: как подключать и ставить время — в уроке RTC DS3231: настоящее время, а аппаратные будильники этого модуля умеют будить сам Arduino: RTC-будильник. Время кормлений стоит хранить в EEPROM, чтобы оно пережило перезагрузку, а менять удобно командами из Serial — разбор чтения чисел в уроке про parseInt.
Обратите внимание на окно срабатывания в условии: minSut >= raskl[i] и minSut < raskl[i] + 10. Оно нужно потому, что цикл проверяет время раз в 200 мс и теоретически может проскочить точную минуту; окно в десять минут со флагом syto[i] гарантирует одно срабатывание на слот, даже если скетч был занят три секунды выдачей соседнего кормления. В моих прогонах окно ни разу не понадобилось — но одна подвисшая выдача стоила бы пропущенного кормления, поэтому живу спокойнее с ним, чем без него.
Кнопка, светодиод и Serial: три интерфейса
Кнопка ручной выдачи висит на D2 с INPUT_PULLUP и срабатывает по фронту — переходу 1→0, а не по уровню: loop у нас пробегает десятки тысяч раз в секунду, и без отслеживания фронта одно нажатие превратилось бы в десяток порций. Механика фронта и подтяжки разобрана в уроке про кнопку и digitalRead. Светодиод на D12 горит, пока заслонка открыта, — с ним видно выдачу из другого конца комнаты, а заодно он честно покажет и подвисание: если светодиод не гаснет дольше трёх секунд, значит delay() в цикле кормления не отработал и стоит смотреть Serial.
Насчёт delay() внутри выдачи — честная оговорка: здесь он допустим. Кормушка в момент выдачи не делает ничего другого, три секунды блокировки loop никому не мешают. Если захотите повесить на тот же цикл опрос датчиков или дисплей — переводите на millis() по методичке millis вместо delay.
Практичная мелочь: кнопку я выношу на длинном проводе прямо к месту кормления — двойные провода макетки до прихожей не дотягиваются, а затяжка по пину «на всякий случай» превращает ручную выдачу в лотерею. Светодиод же, наоборот, оставляю на плате: он дублирует состояние заслонки для отладки, когда самой кормушки перед глазами нет.
#include <Servo.h> Servo vorotka; // заслонка — пин 11const int ZAKR = 20; // закрыто: отступ от упораconst int OTKR = 100; // открытоconst int PIN_BTN = 2; // кнопка ручной выдачиconst int PIN_LED = 12; // индикатор выдачи int raskl[2] = {420, 540}; // минуты суток: 07:00 и 09:00bool syto[2] = {false, false};int lastBtn = 1; // 1 = отпущена (PULLUP)const long START_MIN = 360; // старт симуляции в 06:00 void setup() { vorotka.attach(11); vorotka.write(ZAKR); pinMode(PIN_BTN, INPUT_PULLUP); pinMode(PIN_LED, OUTPUT); Serial.begin(9600); Serial.println("Kormushka. Start 06:00");} void kormit(int prichina, long minSut) { Serial.print("kormlenie="); Serial.print(prichina); // 0 = расписание, 1 = кнопка Serial.print(" vremya="); Serial.println(minSut); digitalWrite(PIN_LED, HIGH); vorotka.write(OTKR); delay(3000); // порция = время открытия vorotka.write(ZAKR); digitalWrite(PIN_LED, LOW); Serial.println("zaslonka=zakryta");} void loop() { long minSut = START_MIN + millis() / 600; // 600 мс = 1 минута суток if (minSut >= 1440) minSut = minSut - 1440; for (int i = 0; i < 2; i++) { if (!syto[i] && minSut >= raskl[i] && minSut < raskl[i] + 10) { syto[i] = true; kormit(0, minSut); } } int btn = digitalRead(PIN_BTN); if (btn == 0 && lastBtn == 1) kormit(1, minSut); // фронт нажатия lastBtn = btn; delay(200);}Что докрутить дальше
Первый апгрейд — контроль остатка: ультразвуковой дальномер над столбом кормы покажет, когда пора досыпать; распиновка и pulseIn — в уроке про HC-SR04. Второй — журнал кормлений на карте памяти: дата, время, размер порции — библиотека SD пишет CSV без труда, если захотите следить за аппетитом по неделям. Третий — Serial-меню для правки расписания без перепрошивки: две команды «время кормления» и «показать расписание», а сохранение — в EEPROM. Есть и четвёртый путь для перфекционистов — тензодатчик с HX711 под поддоном и порция в граммах, но мне не понадобился: три секунды заслонки дают разброс плюс-минус две гранулы, кота это устроило. Втроём основные апгрейды превращают поделку из набора в прибор, который не стыдно оставить в квартире на неделю одну с котом — я проверял, именно в таком составе моя вторая кормушка и дожила до переезда в прихожую на постоянку.
- 1. Почему заслонка стоит на 20° и 100°, а не на 0° и 180°?
- 2. Как кнопка выдаёт ровно одну порцию на нажатие?
- 3. Что случится с millis()-расписанием после отключения питания?
- 4. Порция в этой кормушке задаётся…
- 5. Светодиод на D12 в проекте нужен, чтобы…
- Серво с заслонкой вместо моторчика со шнеком: обратная связь по углу и порция = время открытия (3 с ≈ 12–15 гранул при корме 4–6 мм).
- Рабочие углы 20°/100° — отступ 15–20° от упоров; крупная корма или подпор — MG90S с металлическими шестернями.
- Расписание — массив минут суток с флагом syto[i]; millis()-часы годятся для отладки, для жизни — DS3231 с батарейкой.
- Кнопка на D2 по фронту 1→0 с INPUT_PULLUP, светодиод на D12 горит только пока заслонка открыта.
- Одна лёгкая SG90 живёт от USB, но электролит 470 мкФ у серво снимает просадки на старте.
- Апгрейды: HC-SR04 для остатка корма, журнал на SD, Serial-меню с хранением расписания в EEPROM.
Частые вопросы по теме урока
- Почему заслонка стоит на 20° и 100°, а не на 0° и 180°?
- Упор в краях хода — это удар каждые 20 мс и облизанные шестерни. Отступ 15–20° с каждой стороны — правило раздела. отступ 15–20° от упоров избавляет серво от перегруза в ограничителе
- Как кнопка выдаёт ровно одну порцию на нажатие?
- loop пробегает десятки тысяч раз в секунду. Фронт — сравнение с прошлым состоянием: btn == 0 и lastBtn == 1. по фронту: срабатывает переход 1→0 при INPUT_PULLUP
- Что случится с millis()-расписанием после отключения питания?
- millis() считает от старта платы. После потери питания «время суток» начнётся с START_MIN. Память о реальном времени даёт RTC DS3231 с батарейкой. отсчёт начнётся заново: время суток от включения
- Порция в этой кормушке задаётся…
- Заслонка открывается на фиксированное время: при грануле 4–6 мм три секунды — это 12–15 гранул. Серво даёт обратную связь по углу, которой был лишён моторчик со шнеком. временем открытой заслонки — 3 секунды
- Светодиод на D12 в проекте нужен, чтобы…
- LED включается на время выдачи и гаснет с закрытием заслонки. Если он горит дольше трёх секунд — в цикле кормления что-то зависло. гореть, пока заслонка открыта — видно выдачу и подвисание