Кот будил меня в половине шестого каждое утро: не из вредности, а потому что миска пустая, а у него внутренний будильник точнее любых часов. Кормушка из бутыли от воды и серво сняла вопрос за один уик-энд: контейнер стоит на полке, внизу шибер-диск с прорезью, под ним миска, кнопка «ещё» на боку корпуса для ручной выдачи. Утром кот подходит, ждёт семь часов, и миска наполняется сама — без меня, без будильника и без обиды в жёлтых глазах.
Механика нарочно примитивная. Серво стоит на 90° хода: ноль — заслонка закрыта, кость качалки перекрывает горловину, девяносто — прорезь диска совпала с ней, и корм сыпется сам. Порция измеряется двумя способами: временем, сколько заслонка была открыта, или числом качаний туда-обратно. Сыпучий корм пробовал оба: время дало ровнее порции, качания лучше разбивают сводившийся столбик корма. Диск вырезал из пластика пищевого контейнера толщиной два миллиметра: тоньше — гнётся на гранулах, толще — тяжело валу; качалку к диску сажал на два винта М2 через штатные отверстия, и вся механика собирается и разбирается без инструментов, что для отладки критично.
В этом уроке — механика, скетч с кнопкой и блокировкой повтора, ловушка проседания под нагрузкой и мостик к расписанию: часы DS3231 из раздела про RTC прикрутятся к той же функции выдачи без единого изменения. Простейший дозатор на качаниях я уже собирал в уроке про дозатор корма, а здесь — шибер, порция по времени и вся обвязка, которой хватает на автономную жизнь.
Шибер, кулиса и что считать порцией
Конструкция: горловина контейнера переходит в короткий патрубок, между патрубком и миской — зазор в один-два миллиметра, в нём вращается диск с прорезью. Диск жёстко сидит на качалке серво, серво — на стенке корпуса, ось строго горизонтальна. Зазор держите честным: прижатый к днищу диск клинит крупной гранулой, и серво бьётся о стопор до сброса платы. Для крупных гранул 8-10 мм прорезь делайте не щелью, а окном на треть диска — корм проходит столбиком, а не по одной штучке. Проверка зазора делается листом бумаги: лист проходит между диском и днищем со скрипом — зазор правильный, около миллиметра; проходит свободно — корм будет высыпаться при закрытой заслонке, не лезет — готовьтесь к клинам.
Доза-время: заслонка открыта DERSHIM миллисекунд — насыпалась порция, моя рабочая вилка 700-1500 мс на горсть. Подбор делается весами, не глазами: пять выдач в стакан, среднее, правка константы. Доза-качания: одно качание ноль-девяносто-ноль — одна порция, два качания — двойная; этот режим спасает при слёживании, когда корм стоит столбиком и не идёт. Оба режима живут на одной функции выдачи — отличается только задержка между открытием и закрытием, механика та же.
Почему шибер, а не шнек: шнек — это второй мотор, застревания и контроль оборотов, то есть отдельный проект. Шибер из серво — две позиции и ноль электроники выше сигнального провода: коту не нужен конвейер, коту нужна горсть в срок. Когда проект вырастет до собаки или до точного веса — тогда и шаговик с тензодатчиком, а на старте честная заслонка закрывает задачу целиком, о чём я писал в разборе итогового проекта кормушки. И ещё довод в пользу шибера: он не дробит корм. Шнек ломает гранулы, пыль от них оседает в миске и на дне контейнера, а коты к пыли привередливы — моя кошка объявила голодовку после первых порций со шнеком.
#include <Servo.h> Servo zaslonka; // шибер, D11 const int ZAKRYTO = 0;const int OTKRYTO = 90;const unsigned long DERSHIM = 800; // открыта 800 мс — это и есть порцияconst unsigned long OKNO = 30; // окно дребезга, мс int lastKnopka = 1; // 1 = отпущена (подтяжка вверх)int armed = 1; // новый цикл разрешён после отпусканияint etap = 0; // 0 закрыто, 1 открыто, 2 закрываетсяunsigned long tEtap = 0;unsigned long tFronta = 0;unsigned long porciya = 0; void setup() { Serial.begin(9600); zaslonka.attach(11); zaslonka.write(ZAKRYTO); pinMode(2, INPUT_PULLUP); Serial.println("kormushka: knopka D2 - odna porciya");} void loop() { unsigned long now = millis(); int knopka = digitalRead(2); // фронт + окно 30 мс + блокировка: пока цикл идёт, кнопка мертва if (etap == 0 && armed == 1 && knopka == 0 && lastKnopka == 1 && now - tFronta > OKNO) { etap = 1; tEtap = now; tFronta = now; armed = 0; porciya++; zaslonka.write(OTKRYTO); Serial.print("porciya "); Serial.println(porciya); } if (armed == 0 && knopka == 1 && now - tFronta > 200) { armed = 1; // палец убрали — разрешаем следующий цикл } if (etap == 1 && now - tEtap >= DERSHIM) { // корм насыпался etap = 2; tEtap = now; zaslonka.write(ZAKRYTO); Serial.println("zakryto"); } if (etap == 2 && now - tEtap >= 500) { // дали доехать — цикл закрыт etap = 0; } lastKnopka = knopka;}Разбор начинается с кнопки, потому что здесь классика на классике. Фронт: шаг делается не на «нажата», а на переходе из отпущенной в нажатую; окно 30 мс гасит звон контакта; флаг armed не даёт одному нажатию посчитаться дважды. Этот скелет я разбирал по косточкам в уроке про кнопку и кольцо позиций, а физику дребезга — в уроке про анти-дребезг; здесь он склеен с автоматом выдачи.
Блокировка повтора — то, что отличает кормушку от демо. Условие etap == 0 в фронте означает: пока заслонка открыта или закрывается, нажатия игнорируются целиком. Без этого кот, придавивший кнопку лапой, получил бы серию порций до заклинивания горловины — у моей первой версии так и было: шесть качаний подряд, куча на полу и торжествующий взгляд. С блокировкой одно нажатие — одна порция, сколько бы loop ни пробежал мимо зажатой кнопки.
Ни одного delay() в скетче: цикл выдачи живёт на трёх таймерах millis(), и между тиками скетч свободен — сюда влезет RTC-опрос, Serial и что угодно ещё. Счётчик порций в Serial — не украшение: по журналу видно, сколько раз сработала кнопка без вас, а после подключения часов рядом с номером порции появится время. Журнал удобно вести в два столбца — номер и тип выдачи, «кнопка» или «расписание»: по неделе логов сразу видно, во сколько кот реально подходит к миске, и расписание правится по факту, а не по фантазии.
Расписание, питание и следующий уровень
Расписание — самое короткое подключение в этом проекте: часы DS3231 держат время даже без питания платы, и скетч раз в минуту сверяет now().hour() и now().minute() с двумя константами — семь ноль и девятнадцать ноль. Совпало — вызываем ту же функцию выдачи, что и кнопка. Будни и выходные, разные порции и лето-зима — это уже арифметика над DateTime, и вся она разобрана в уроке про кормушку по расписанию — там же таблица точности и дрейфа. Кварц DS3231 точнее платного: дрейф в пару секунд за месяц против минут у кварца самой Uno, и для кормления это значит, что семь ноль наступает в семь ноль, а не в 7:04.
Питание автономной кормушки скромное: серво просыпается на секунду дважды в день, остальное время ест только часы и спящий контроллер. USB-блок питания от роутера закрывает год работы, а конденсатор 1000 мкФ у серво всё равно ставлю: просыпаться под столбом корма тяжело даже с хорошей шиной. И проверка на реальном коте: первую неделю расписание шлёт в Serial уведомления без выдачи — «пора бы насыпать» — по ним видно, совпадает ли ваш график с его аппетитом, и только потом выдача включается физически. Дальше по лестнице — миска на тензодатчике: скетч выдаёт не миллисекунды, а граммы до целевого веса, и точность переезжает с весов на кухне в сам проект.
В симуляторе корм невесом, и заслонка ходит как по маслу. На живой плате первый же честный запуск открывает ловушку из заголовка: под столбом корма серво едет медленнее и не доезжает — задал открытие 60°, механика встала на 54°, прорезь не дошла, порция неполная. Я ловил это маркером: полоска на качалке, пять выдач, сверка угла после остановки. Причины две — просадка питания под нагрузкой и собственно вес корма на диске, и лечатся они вместе: конденсатор у клемм, нормальный БП и запас хода. Проседание тем заметнее, чем тоньше и длиннее провод: моя первая сборка на метке тонкого шлейфа теряла четыре градуса хода, после пересадки на толстый короткий провод — полтора.
Запас хода выглядит так: если механике достаточно 60° открытия, назначайте 90° — серво упрётся в пределы прорези с запасом, и недоезд в пару градусов перестанет быть проблемой. Упоры при этом должны быть мягкими или разнесёнными по ходу, чтобы серво не билась о мёртвый стопор каждое кормление. Пластиковым SG90 такой режим прощают не всегда — для кормушки с крупными гранулами я ставлю металл, а цифры по сравнению SG90 и MG996R уже у вас.
Четыре самопроверки перед тем, как оставить кормушку с котом наедине. Первая: десять нажатий кнопки — десять строк в журнале, ни больше ни меньше. Вторая: пять порций подряд на весах — разброс не шире пары граммов, иначе правьте DERSHIM. Третья: застрявшая гранула — серво тужится пару секунд, но не сбрасывает плату; если сбрасывает, вернитесь к питанию из предыдущего урока. Четвёртая, ленивая: снимите на телефон минуту работы механики в момент кормления — редкие заклинивания проявляются раз в неделю, и видео помогает увидеть их в спокойной обстановке, а не по крику кота. После этого механике можно доверять на неделю без присмотра.
| Способ дозы | Настройка | Где подвох |
|---|---|---|
| Время открытой заслонки | 700-1500 мс, подбор весами | корм слёживается и не идёт без встряхивания |
| Число качаний 0-90-0 | 1-3 качания за выдачу | резкий ход выстреливает гранулы мимо миски |
| Диск с окном на треть | окно задаёт порцию за один поворот | зазор к днищу меньше 1 мм — клинит на крупных гранулах |
| Миска на тензодатчике | выдаём до целевых граммов | следующий этаж: датчик веса, калибровка тары |
- Шибер на 90°: 0° закрыто, 90° открыто — электроника выше серво не нужна
- Порция = время открытой заслонки (700-1500 мс) или число качаний, отладка весами
- Фронт + окно 30 мс + armed: одно нажатие — одна порция, дребезг не считает
- Блокировка на время цикла: кот с зажатой кнопкой получает ровно одну выдачу
- Под нагрузкой серво недоезжает — назначайте угол с запасом и ставьте конденсатор
- 1. Одно нажатие кнопки выдаёт две порции подряд. Что пропущено в скетче?
- 2. Заслонка под кормом встаёт на 54° вместо заданных 60°. С чего начать?
- 3. Порция измеряется временем открытой заслонки. Как выставить честные 15 г?
- 4. Зачем выдача блокируется на время цикла (etap != 0)?
- 5. Что даст миска на тензодатчике следующему уровню проекта?
Частые вопросы по теме урока
- Одно нажатие кнопки выдаёт две порции подряд. Что пропущено в скетче?
- Без фронта и окна каждое нажатие звенит 5-20 мс, и loop успевает насчитать несколько фронтов. Окно 30 мс плюс armed оставляют один цикл на одно нажатие. Фронт нажатия и окно дребезга
- Заслонка под кормом встаёт на 54° вместо заданных 60°. С чего начать?
- Недоезд — просадка питания плюс вес корма. Конденсатор 1000 мкФ, нормальный БП и запас хода до 90° закрывают вопрос без смены механики. Назначить угол с запасом и проверить питание серво
- Порция измеряется временем открытой заслонки. Как выставить честные 15 г?
- Горсть — единица неточная. Пять выдач в стакан на весах дают среднее, дальше константа DERSHIM правится линейно: больше граммов — пропорционально больше миллисекунд. Пять выдач на весах, среднее, правка константы
- Зачем выдача блокируется на время цикла (etap != 0)?
- Без блокировки зажатая кнопка запускает циклы подряд — шесть качаний и куча на полу, как у моей первой версии. Флаг etap закрывает фронт до конца цикла. Кот, зажавший кнопку лапой, получит ровно одну порцию
- Что даст миска на тензодатчике следующему уровню проекта?
- С датчиком веса скетч выдаёт до целевой массы: порция становится граммами, а не временем. Калибровка тары и ручная выдача при этом остаются. Дозу в граммах вместо миллисекунд