После разборки старого велосипеда на моём верстаке месяц жили две банки: гайки М8 и М10, перемешанные в один слой. Перебирать руками — минут пятнадцать занудства, и однажды вечером я собрал сортировщик: бункер, из которого воронка-качели отдаёт по одной детали, жёлоб-развилка с заслонкой и два стакана. Механика простая до безобразия, но проект оказался лучшим тренажёром по автоматам из всех, что я делал: две серво нельзя писать вразнобой, им нужен дирижёр.
Роли здесь разделены не «две серво танцуют синхронно», а конвейером. Воронка — та же механика, что у дозатора корма для питомца: качнулась — зачерпнула порцию, вернулась — высыпала. Заслонка — наклонная перекладина на развилке жёлоба, перекладывает поток в левый или правый стакан. А размер детали на живой плате мерит дальномер в просвете под воронкой: большая деталь проходит ниже — вот вам и сортировка по фракции.
Сценарий соберём автоматом из пяти состояний на millis — расширение четырёх состояний из урока про конечный автомат на светофоре. Новой здесь будет очередь: кнопка нажимается тогда, когда ей удобно, а не когда автомат свободен, — и ни одно нажатие терять нельзя.
Роли двух серво: черпать и направлять
Воронка-качели — самый нетривиальный узел, и его стоит собрать до кода. Серво лежит под бункером, на роге — ложечка-карман: ход от 90 до 45 градусов зачерпывает из слоя деталей ровно одну, обратный ход высыпает её в жёлоб. Углы подбираются под фракцию: у меня М8 с гровером просились в карман при 45°, а М10 — только при 35°, иначе по две за раз. Тот же принцип черпания порции разобран в уроке про качели плеча манипулятора — там качается плечо, тут ложечка, физика одна.
Заслонка проще: жёлоб после воронки расходится вилкой, перекладина на второй серво наклоняет развилку влево или вправо. Два положения — 30 и 150 градусов, середина никогда не нужна: пока автомат меряет, заслонка стоит в положении прошлого бункера, и это осознанное решение — лишний ход это лишний износ и лишняя просадка питания.
Питание — то место, где двух-серво проекты умирают на первой проверке. Две SG90 в старте тянут до ампера с лишним на пиковой паре, и USB-порт ловит просадку, а то и перезагрузку платы — арифметика пиков разобрана в уроке про пик тока и сброс. Дирижёр помогает и здесь: автомат едет воронкой, когда заслонка стоит, и наоборот — ходы разнесены во времени программно, а одновременный старт бывает только в первый миллисекунду после подачи питания. На живой плате я всё равно кормлю серво от отдельного блока с общим грунтом — правило из урока про внешнее питание.
Ложечка крепится к качалке проволочной скобой, и у неё два зазора, которые стоит проверить рукой до первого запуска: два-три миллиметра до стенок бункера, иначе деталь заклинит между карманом и стеной, и трёхсотпятидесяти миллисекунд на мах перестанет хватать. Второй зазор — между карманом и горловиной жёлоба: деталь должна успевать упасть за обратный ход, у меня горловина уходит вниз на сантиметр с четвертью, и ни одна гайка ещё не уехала обратно в бункер.
Пять состояний и очередь заказов
#include <Servo.h> Servo vasilok; // воронка-качелиServo zaslon; // заслонка развилки const int PIN_BTN = 2; // деталь подана — заказconst int PIN_POT = A0; // размер детали (в симе — ползунок)const int PIN_LED = 12; // автомат занят const unsigned long T_CHERP = 350; // ход воронкиconst unsigned long T_MERIM = 300; // заслонке на переездconst unsigned long T_SIPEM = 350; // обратный ход воронки int sost = 0; // 0 zhdet, 1 cherp, 2 merim, 3 sipemint ochered = 0; // заказов в очереди, максимум 3int vypusheno = 0;int lastBtn = HIGH;unsigned long tBtn = 0;unsigned long tSost = 0; void setup() { vasilok.attach(11); zaslon.attach(10); vasilok.write(90); pinMode(PIN_BTN, INPUT_PULLUP); pinMode(PIN_LED, OUTPUT); Serial.begin(9600); Serial.println("Sorter: D2 zakaz, A0 razmer");} void loop() { unsigned long now = millis(); int b = digitalRead(PIN_BTN); if (lastBtn == HIGH && b == LOW && now - tBtn > 150) { tBtn = now; if (ochered < 3) { ochered = ochered + 1; Serial.print("zakaz: v ocheredi "); Serial.println(ochered); } else { Serial.println("ochered polna"); } } lastBtn = b; if (sost == 0) { if (ochered > 0) { ochered = ochered - 1; sost = 1; tSost = now; vasilok.write(45); // зачерпнуть digitalWrite(PIN_LED, HIGH); } } else if (sost == 1) { if (now - tSost >= T_CHERP) { sost = 2; tSost = now; int razmer = analogRead(PIN_POT); if (razmer > 600) { zaslon.write(150); // крупная — вправо Serial.print("razmer="); Serial.print(razmer); Serial.println(" -> bunker R"); } else { zaslon.write(30); // мелкая — влево Serial.print("razmer="); Serial.print(razmer); Serial.println(" -> bunker L"); } } } else if (sost == 2) { if (now - tSost >= T_MERIM) { sost = 3; tSost = now; vasilok.write(90); // высыпать } } else { if (now - tSost >= T_SIPEM) { sost = 0; vypusheno = vypusheno + 1; digitalWrite(PIN_LED, LOW); Serial.print("gotovo, vsego "); Serial.println(vypusheno); } }}Очередь — три переменные и два места их изменения. Кнопка по фронту с повторным интервалом 150 мс прибавляет счётчик, но не чаще трёх: бункер физически вмещает ограниченное число деталей, и честный отказ с записью «очередь полна» предсказуемее бесконечного обещания. Автомат в состоянии «ждёт» снимает заказ и уводит скетч в цикл — нажатие, случившееся посреди отсчёта любой стадии, спокойно дожидается своей очереди. Прямой вызов «кнопка — сразу ехать» ломал бы середину хода воронки.
Стадии подобраны под механику, а не по вкусу: 350 мс на ход воронки при скорости SG90 в 600°/с — запас втрое против сорокапятиградусного маха, 300 мс заслонке на переезд между 30 и 150 градусами. Полный цикл на бумаге — секунда, с реальным набором деталей и их падением в стакан выходит две-три: пропускная способность сортировщика упирается в гравитацию, а не в контроллер.
Порог 600 — не магия, а калибровка по логу. Положите десять крупных деталей подряд, выпишите показания razmer=, потом десять мелких: между двумя кучками выбирается число с зазором шире разброса. Мой зазор вышел от 540 до 690 — порог 600 сидит посередине. Если фракций станет больше двух, порог удобно вести из монитора: числа из Serial через parseInt кладутся в переменную POROG, и перенастройка не требует перепрошивки — особенно приятно, когда скетч уже зашит в плату на постоянное место жительства.
| Состояние | Воронка D11 | Заслонка D10 | Переход |
|---|---|---|---|
| ждёт | 90° | прошлый бункер | очередь не пуста → снять заказ и черпать |
| черпает | 90 → 45° | прошлый бункер | 350 мс → читать размер |
| меряет | 45° | 30° или 150° по размеру | 300 мс — заслонка доехала → сыпать |
| сыплет | 45 → 90° | свой бункер | 350 мс → ждать; очередь снова не пуста — новый цикл |
Размер на живой плате я мерил дальномером в просвете жёлоба: датчик смотрит поперёк, деталь проходит мимо и занимает часть просвета — показание проседает пропорционально габариту. Пара ИК-фотопрерывателей вдоль жёлоба на разной высоте решает то же без аналоговой возни: первый сработал, второй нет — крупная. В симуляторе размер изображает ползунок A0 — механика автомата от этого не меняется ни на строку: источник числа взаимозаменяем, порог 600 — единственная константа, которую калибруют по своей фракции.
Куда расти после двух бункеров: третья заслонка в каскаде и сортировка по цвету через датчик TCS34725 — но это отдельная эпопея с подсветкой и калибровкой белого, для первого сортировщика хватит и размера. Важнее освоить сам скелет: автомат с очередью — универсальная вещь, на нём живёт и лифт на два этажа, и раздатчик корма с меню.
На стенде видно главное свойство конвейера: серво никогда не едут одновременно. Пока воронка махает, заслонка стоит в выбранном положении; пока заслонка переезжает, воронка держит карман набранным. Это не только про питание — это и про понятность: по Serial-логу видно каждое состояние, и промах автомата находится за один прогон. Если когда-нибудь добавите третью серво — ей просто достанется своя стадия в той же цепочке if.
И практическая мелочь от верстака: просыпающийся мимо кармана песок из фракции — повод почистить ложечку, а не менять код. Карман набирает по краям, и за неделю работы мои углы уехали с 45 на 42 градуса — детали поменялись, механика подстроилась. Константы углов я вынес в начало скетча именно за это: правка одного числа вместо поиска по коду в три часа ночи.
Средняя зона между порогами — готовое место для выбраковки: заслонка в 90 градусов ведёт деталь в третий, средний стакан, и таблица состояний просто растёт строкой. Я не стал добавлять стадию сознательно: у сортировщика гаек нет смысла держать третий контейнер, а вот у сортировщика болтов с шайбами — уже есть. Автомат от этого не меняется ни на структуру, ни на очередь — растёт только список if в состоянии «меряет».
- Роли разделены конвейером: воронка черпает по одной, заслонка направляет, автомат — дирижёр
- Очередь с кнопки держит заказы во время цикла: нажатие не теряется, максимум три — честный отказ вместо бесконечного обещания
- Ходы серво разнесены по стадиям — одновременный пик тока только на старте, питание всё равно отдельным блоком
- Стадии 350/300/350 мс с запасом против скорости 600°/с; полный цикл на бумаге — секунда, реально 2–3 с
- Источник размера взаимозаменяем: дальномер в просвете, ИК-пара, ползунок в симуляторе — автомат не меняется
- 1. Что делает воронка-качели в сортировщике?
- 2. Зачем очередь, если кнопку можно было бы проверять прямо перед циклом?
- 3. Почему автомат едет воронку и заслонку по очереди, а не одновременно?
- 4. Почему очередь ограничена тремя заказами?
- 5. Сколько длится полный цикл автомата на бумаге?
Частые вопросы по теме урока
- Что делает воронка-качели в сортировщике?
- Ход 90 → 45 зачерпывает порцию в карман, обратный ход высыпает её в жёлоб. Углы подбираются под фракцию — у меня М10 просились при 35°, иначе по две за раз. За одно качание отдаёт из бункера ровно одну порцию
- Зачем очередь, если кнопку можно было бы проверять прямо перед циклом?
- Кнопка нажимается, когда удобно человеку, а не когда автомат свободен. Счётчик копит заказы (до трёх), и каждый снимается в состоянии «ждёт» — прямая привязка кнопки к ходу ломала бы середину цикла. Нажатие во время цикла не теряется — заказ дожидается свободного автомата
- Почему автомат едет воронку и заслонку по очереди, а не одновременно?
- Две SG90 в одновременном старте тянут до ампера с лишним — просадка до перезагрузки платы. Программный разнос ходов по стадиям убирает одновременность, кроме первого миллисекунды после включения. Меньше просадка питания и предсказуемее лог — серво не едут в один момент
- Почему очередь ограничена тремя заказами?
- Бункер вмещает ограниченное число деталей, и четвёртый заказ с записью «очередь полна» предсказуемее молчаливого накопления. Ограничение — одно сравнение в обработчике кнопки. Честный отказ лучше бесконечного обещания: бункер конечен
- Сколько длится полный цикл автомата на бумаге?
- Сумма стадий — секунда; с реальным набором деталей и падением в стакан выходит две-три. Пропускная способность упирается в гравитацию, а не в контроллер. Около секунды: 350 + 300 + 350 мс на стадии