Кормушка для кота с кнопкой была гордостью недели: жмёшь — заслонка отъезжает, отсыпается горсть. Схема работала ровно месяц, а потом серво начала пропускать позиции, гудеть и открывать заслонку сама, без приглашений. Вскрытие показало облизанные зубья шестерён. Причина сидела в моём же скетче: положения я выбрал «по красоте» — write(0) и write(180) — и на каждом переключении серво на мгновение билась о механический упор в крайних точках с полным перегрузом. Два удара в секунду не убивают сразу, но месяц таких ударов — это сотни тысяч перегруженных тактов подряд.
В этом уроке соберём рычаг с двумя рабочими положениями так, чтобы он жил годами: с отступом от упоров, с одной кнопкой на оба состояния и с пониманием, что серво — не реле. Она держит позицию и греется, если её заставлять; она доезжает за некоторое время, а не щёлкает; и она считает удары об упоры наравне с вами. Всё это умещается в скетч на тридцать строк и в пару правил, которые я сформулирую ниже.
Такой рычаг — рабочая лошадь бытовой автоматики: задвижки, щеколды, стрелки-указатели, стрелочные переводы макетов. Код у него беднее, чем у шагов из следующего по списку урока про кнопки, зато поведение честнее: две станции, между ними ничего. Если вы уже собирали связку ручка-серво, здесь найдёте ту же пару, но с характером переключателя, а не регулятора: задача не «насколько открыть», а «открыто или закрыто» — и код это отражает буквально: одна переменная, два угла, один if.
Два положения и отступ от упоров
Правило отступа я вывел на той самой кормушке: рабочие углы сидят на 15-20 градусов вглубь от каждого края. У 0° и 180° в корпусе серво стоят механические упоры; прижатое к упору серво продолжает гонять мотор каждые 20 мс с током до 650-700 мА — греется, визжит, изнашивает зубья. На 20° и 160° контур спокойно находит цель и замолкает: мотор стоит, ток падает до десятков миллиампер, и серво может стоять так хоть день. Отступ — это не перестраховка, а разница между «работает месяц» и «работает год».
Про время приезда: серво едет не мгновенно, 140 градусов при лёгкой заслонке занимают 250-400 мс. Нажатие через сто миллисекунд после приказа не сломает ничего — контур просто перенаправится с полпути, — но ход станет дёрганым, а счёт нажатий потеряет смысл. Правило простое: рычаг в пути — кнопка отдыхает; в коде ниже для этого стоит пауза после каждого переключения, и она там не только ради дребезга.
Второе, что стоит понять до кода: серво — плохое реле, но отличный рычаг. Позицию она держит сама: пока контур жив, вал сопротивляется толчкам без всяких ваших усилий. Момент SG90 — около 1,8 кг·см; на плече в два сантиметра это почти четыреста грамм на конце качалки — для щеколды и заслонки за глаза. Если задаче нужен щелчок «включил-выключил» с нулевым потреблением и без позиции, серво не про то; она про положения, которые надо занимать и удерживать, — и в этом её сила.
#include <Servo.h> Servo srv;int zakryto = 20; // рабочее положение, отступ от упораint otkryto = 160; // и второеint sostoyanie = 0; // 0 - закрыто, 1 - открытоint lastKnop = 1; // прошлое состояние кнопки void setup() { srv.attach(11); pinMode(2, INPUT_PULLUP); srv.write(zakryto); // при загрузке всегда уходим в закрытое Serial.begin(9600); Serial.println("Заслонка закрыта");} void loop() { int knop = digitalRead(2); if (knop == 0 && lastKnop == 1) { // фронт: одно нажатие - один такт if (sostoyanie == 0) { sostoyanie = 1; srv.write(otkryto); Serial.println("Заслонка открыта"); } else { sostoyanie = 0; srv.write(zakryto); Serial.println("Заслонка закрыта"); } delay(300); // глушим дребезг и спешку } lastKnop = knop;}Скелет скетча вам знаком по прошлому уроку: фронт нажатия плюс пауза. Новое — переменная sostoyanie, которая и есть рычаг в цифровом виде: каждое нажатие перебрасывает её между нулём и единицей, а ветвление превращает состояние в приказ. Углы лежат в двух переменных, и смена механики не трогает логику: поняли, что заслонке нужно 45 и 135, — правите два числа сверху, остальное стоит на месте. Структура повторяется на любую пару положений: заслонка, шторка, стрелка — меняются только числа и надписи в Serial.
srv.write(zakryto) в setup() — строка, которую я когда-то пропустил и поплатился. После подачи питания серво стоит там, где её застал выключатель, и ждёт первого приказа; не отдав его, вы получаете лотерею: рычаг может встретить вас в любом месте хода. Явный приказ в setup() делает старт предсказуемым: перезагрузка всегда возвращает механизм в закрытое положение, и рассинхрон «нарисовано одно, стоит другое» невозможен по построению. Три сотни миллисекунд паузы после фронта здесь не только про дребезг: они дают рычагу время уехать, прежде чем вы успеете нажать снова.
Отладка у переключателя одноструйная: Serial печатает только смены состояния, и по ним всегда видно, что думает скетч. Рычаг стоит не там? Смотрите последнюю строку: противоречит механике — виновата переменная, совпадает — смотрите механику. Один поток событий вместо сырья из сотен опросов — побочный бонус того, что команда отдаётся только на фронте. Если строк нет вовсе — проверьте, что pinMode объявлен в setup() до первого опроса: вход без подтяжки читает воздух и фронты не приносит.
| Задача | Положения | Нюанс из практики |
|---|---|---|
| Заслонка кормушки | 20° и 160° | состояние видно издалека; открытие привязано к времени кормления |
| Щеколда двери-купе | 30° и 90° | направляющая съедает трением 10-15° хода — берите запас |
| Стрелка-флажок статуса | 10° и 170° | чем размашистее ход, тем заметнее смена состояния |
| Стрелочный перевод макета | 45° и 135° | переводу нужен точный приезд: не шлите новый приказ раньше времени |
| Шторка таймлапса | 25° и 155° | позиции подбираются по кадру, а не по градусам на бумаге |
Рычаг — это плечо, и вес на его конце умножается на длину: двести граммов на качалке в пять сантиметров требуют момент в килограммо-сантиметр — больше половины силы SG90. Отсюда практическое правило: чем тяжелее и длиннее рычаг, тем шире отступ от упоров и тем важнее заканчивать ход, не доходя до пределов серво. Люфт качалки дописывает свои градусы: положение, которое в коде одно, на валу плавает в пределах мёртвой зоны — для переключателя это нормально, для транспортира нет.
Почему кнопка, а не тумблер? Состояние живёт в переменной и не может разойтись с положением серво: что в переменной, то и на валу. Тумблер из урока про toggle тоже рабочий вариант, но после перезагрузки платы серво встанет туда, куда прикажет setup(), а тумблер останется где-то на щитке — и вы получите вилку «нарисовано одно, стоит другое». С кнопкой и инициализацией закрытым состоянием рассинхрон не существует по построению: перезагрузка всегда возвращает рычаг в известное положение, сколько раз плата ни перезапускалась.
Монтажные мелочи, которые сберегают позиции: качалку на вал надевайте с плотной посадкой — сорванные шлицы возвращаются люфтом именно отсюда; длинный печатный рычаг усильте, потому что непропечатки прогибаются и добавляют градусы мёртвого хода, которых в коде нет. Один раз подёргайте рычаг рукой после сборки: посадка, которая ходит, вылезет именно в первый день.
Сравните со скетчем из урока про шаги: там мы гуляли по всему ходу, и каждая точка была равноправной; здесь — две конечные станции, и между ними всё или ничего. Для переключателя это и надо: не «насколько открыть», а «открыто или закрыто». Если между положениями захочется плавного хода с замедлением, берётся он из урока про плавное движение и приклеивается к этому скетчу без переделок: меняется только способ заказа угла, логика состояний остаётся.
Опыты в симуляторе: поменяйте положения на 45 и 135 и посмотрите, как меняется характер рычага; добавьте в обе ветки печать millis() — увидите реальное время приезда и сможете подобрать паузу под свою механику; наденьте качалку так, чтобы в закрытом положении рычаг смотрел перпендикулярно корпусу — оба положения получат одинаковый запас, и ход станет симметричным. На живой плате: подержите серво в положении под лёгким грузом — ровный тихий гул и тёплый корпус в норме, обжигающий — нет; если рычаг подрагивает в покое, разбор у меня в отдельном уроке. И держите в голове бюджет: удержание под нагрузкой — единственный режим, где мелкая серво тянет сотни миллиампер постоянно; счёт ведёт урок про ток проекта, а если кормушка живёт далеко от USB, расклад по питанию лежит в уроке про внешний блок.
- 1. Почему положения 0° и 180° для нагруженного рычага — плохой выбор?
- 2. На сколько градусов от края я отступаю, выбирая рабочие положения?
- 3. Что произойдёт после srv.detach() в положении без нагрузки?
- 4. Кнопка-переключатель: контакт дребезжит. Что увидим без паузы после фронта?
- 5. Груз постоянно давит на рычаг: после detach() вал уводит. Правильное решение?
- Рабочие положения — с отступом 15-20° от 0° и 180°: у упоров серво бьётся с перегрузом
- Одна кнопка ведёт оба положения: переменная-состояние плюс фронт нажатия
- Состояние в переменной надёжнее тумблера: перезагрузка всегда возвращает рычаг в известное положение
- Серво держит позицию сама; detach() снимает гул и нагрев, но не противостоит толчкам
- Плечо умножает вес: момент SG90 — около 1,8 кг·см, тяжёлый рычаг съедает запас
Частые вопросы по теме урока
- Почему положения 0° и 180° для нагруженного рычага — плохой выбор?
- За крайними углами стоят упоры: прижатая к ним серво продолжает гонять мотор с перегрузом каждые 20 мс, греется и облизывает зубья — месяц-другой, и шестерни кончаются. Серво бьётся о механические упоры и изнашивает шестерни
- На сколько градусов от края я отступаю, выбирая рабочие положения?
- 15-20 градусов вглубь от каждого края: контур находит цель и замолкает, ток падает, упоры живут своей жизнью, а механика не испытывает перегрузов. на 15-20
- Что произойдёт после srv.detach() в положении без нагрузки?
- detach() прекращает импульсы: контур засыпает, гул и нагрев уходят, а вал держится трением редуктора — пока его никто не толкает. Серво замолчит и остынет, вал останется на месте
- Кнопка-переключатель: контакт дребезжит. Что увидим без паузы после фронта?
- Звон контакта выглядит как серия фронтов: состояние перебросится дважды, и рычаг выйдет там же, где стоял. Пауза 150-300 мс после фронта это лечит. Двойное переключение туда-обратно
- Груз постоянно давит на рычаг: после detach() вал уводит. Правильное решение?
- Если механика толкает вал, контур должен жить: серво остаётся на службе и держит позицию, а detach применяется только к разгруженным рычагам. Оставить серво держать позицию, без detach