Флажок-указатель на стене мастерской — конструкция, где я впервые пожалел, что серво нельзя отпустить. SG90 переводила стрелку между четырьмя надписями раз в час, а всё остальное время стояла под импульсами: тихо гудела, чуть заметно подрагивала и съедала батарейный отсек из четырёх АА за неделю. Рог держать было нечем, но и отпустить его я не умел — казалось, что отключить серво можно только выдернув провод из макетки.
На самом деле команда есть, и это detach(). В уроке про три функции библиотеки attach, write и detach прошли одним списком, без физики — там detach был просто строкой в таблице методов. Здесь разберём его всерьёз: что происходит с импульсами и мотором, почему рог после отпускания можно провернуть пальцем, сколько миллиампер это экономит и когда detach ломает конструкцию — например, у рычага, который держит груз. Команда из одного слова — без выдёргивания проводов и выключателей на линии.
Заодно протестируем поведение в симуляторе: движок книги не рисует провисший рог — оговорю это честно заранее, — зато покажет переключение импульсов и возврат к записанному углу в Serial. На реальной плате всё то же самое проверяется рукой: отпущенный рог подкручивается без сопротивления, пристёгнутый — нет.
Прежде чем крутить рог руками, разберёмся, почему серво нельзя выключить на манер лампы. Снять питание с красного провода можно, но тогда замрут и все соседи по шине, а при повторной подаче контур проснётся рывком к записанному углу. detach() аккуратнее: он глушит только импульсы одного объекта, оставляя плату и остальную схему жить как жили.
Импульсы кончились — момент тоже
Серво — замкнутая система, мы разбирали её в уроке про анатомию: плата сравнивает длительность импульса с напряжением потенциометра на валу и крутит мотор, пока расхождение не уйдёт в зону нечувствительности. Чтобы было с чем сравнивать, библиотека Servo шлёт импульс каждые двадцать миллисекунд — весь срок жизни объекта, от attach() до detach(). Этот ритм мы раскладывали по микросекундам в уроке про импульсы 1000-2000 мкс. Прервите ритм — и сравнивать станет не с чем: контур без опорного импульса слеп.
detach() снимает ритм. На сигнальном пине тишина, внутри серво плате не с чем сравнивать, драйвер мотора заперт — ток через обмотки не идёт. Рог остаётся где стоял, но момент удержания исчезает: вал проворачивается пальцем с лёгким трением шестерён редуктора, через плечо давить не нужно. Под нагрузкой — весом рычага, натяжением резинки — конструкция, которая секунду назад держалась, аккуратно съезжает вниз. Рука при этом чувствует лёгкие щелчки — шестерни идут в обратную сторону теми же парами, ничего не клинит.
Экономия ощутимая. Стоящая SG90 без нагрузки тянет десятки миллиампер, под нагрузкой — сотни, и всё это чистый расход, пока серво пристёгнута. В уроке про ток удержания мы считали, во что оборачивается угол, простоявший под напряжением сутки: тёплый корпус и севший аккумулятор. Есть бонус и со стороны платы: когда отсоединены все серво-объекты, таймер Timer1 освобождается, и пины 9 и 10 снова отдают analogWrite — это прямо из справки по библиотеке. В моей кормушке отсоединение на простой экономило почти всю батарею: серво работала десять секунд в день, а списывала сутки.
Отдельный вопрос — сколько серво вообще живёт на одной плате. На Uno библиотека держит до двенадцати объектов, и detach() здесь работает экономистом по счёту: пристёгивать привод на время команды и отпускать после. Так делают на роботах с четырьмя-пятью серво: потребление в простое падает с сотен миллиампер до десятков, и аккумулятор доживает до конца смены.
#include <Servo.h> // Ручка A0 задаёт угол, кнопка D2 пристёгивает и отпускает серво.// Порядок из справки: attach - write - detach. Servo horn; const int BTN = 2; int lastBtn = HIGH;int attached = 0;int lastDeg = 90; // последний записанный уголunsigned long t = 0; void setup() { pinMode(BTN, INPUT_PULLUP); Serial.begin(9600); Serial.println("A0: ugol, D2: attach/detach");} void loop() { int nowBtn = digitalRead(BTN); if (lastBtn == HIGH && nowBtn == LOW) { // фронт нажатия if (attached == 0) { horn.attach(11); horn.write(lastDeg); // серво сразу поедет к записанному углу attached = 1; Serial.print("attach: vozvrat k "); Serial.println(lastDeg); } else { horn.detach(); attached = 0; Serial.println("detach: impulsy snyaty"); } } lastBtn = nowBtn; if (attached == 1 && millis() - t >= 20) { // темп библиотеки: 50 Гц t = millis(); lastDeg = map(analogRead(A0), 0, 1023, 0, 180); horn.write(lastDeg); }}В скетче два места, которые обычно пропускают. Первое — horn.write(lastDeg) сразу после attach(): библиотека помнит последний записанный угол и после повторного пристёгивания отправит серво именно туда, минуя текущее положение ручки. Если за время отпуска ручку сместили, серво дёрнется к старому углу, а на следующем цикле — к новому: двойной рывок, который легко принять за поломку. Я держу lastDeg в переменной и печатаю его в Serial, чтобы скачок был видимым и предсказуемым. Без хранения угол возвращался бы туда, куда ручка смотрела минуту назад, — так я и поймал двойной рывок в первый раз.
Второе — темп записи. В серво пишем не каждый проход loop(), а раз в двадцать миллисекунд, как делает сама библиотека: чаще смысла нет, импульсы всё равно выравниваются по своему периоду. Порог в двадцать миллисекунд здесь не строгость, а вежливость: внутри серво всё равно живёт свой ритм. Тот же приём стоит в серво-переключателе с двумя положениями, где detach прижился естественно: рычаг дёрнулся, дошёл до упора, отпустился — до следующей команды. Между командами серво не тратит ни миллиампера.
Есть и третья выгода, не электрическая: отпущенный рог можно выставить рукой. На репетиции сцены с флажками я двигал стрелки в положения без единой строки кода — пристегнул, поправил пальцем, отпустил. Редуктор при обратном ходе слегка трещит, но не ломается: механика любит осторожность, а не силу — тонкости посадки и люфтов разобраны в уроке про рычаги и качалки.
Отпускать или держать
| Сценарий | detach или держать | Почему |
|---|---|---|
| Указатель, флажок, шторка без нагрузки | Отпускать | Держать нечего: трение в редукторе само фиксирует рог |
| Рычаг с грузом вверх, качалка с пружиной | Держать | Без импульсов момент пропадает — конструкция съезжает под весом |
| Кормушка между дозами | Отпускать | Серво работает раз в час: ток удержания за день не окупается |
| Рулевая модель, манипулятор под грузом | Держать | Позиция — часть функции: отпустил — курс потерян |
Отпускание в симуляторе
Модель в симуляторе повторяет скетч один в один: ручка двигает рог, кнопка D2 пристёгивает и отпускает, Serial печатает каждое переключение. Одно движение симулятор не покажет принципиально: провисший рог, который крутится пальцем. Движок не эмулирует момент удержания — после detach рог просто замирает на месте и перестаёт реагировать на ручку, а прощупать его руками можно только на реальной макетке. Ни один скетч этого не изменит — свойство железа, а не программы.
Что стоит проверить в модели: отпустите серво и покрутите ручку — рог стоит, потому что write() больше не вызывается. Пристегните обратно — серво вернётся к записанному углу lastDeg, а на следующем тике догонит ручку: тот самый двойной рывок из разбора, теперь видимый в цифрах. И жмите кнопку в момент, когда ручка крутится: скетч не потеряет ни нажатие, ни угол. А таймер записи на millis() вместо delay здесь работает как на плате — кнопка нажимается сколько угодно раз, скетч не запирается.
Serial в модели печатает три сообщения: attach с углом возврата, detach с пометкой об отпускании и тишину в промежутке — импульсы сняты, писать некуда. Это же поведение удобно проверять на плате: жмёте — серво обмякает, жмёте ещё раз — возвращается рывком к lastDeg. Если рывок выглядит страшно, вернитесь к таблице: возможно, вашему механизму нужно держать, а не отпускать.
- detach() снимает импульсы каждые 20 мс: драйвер внутри серво запирается, момент удержания пропадает, рог проворачивается пальцем
- Стоящая серво тратит десятки миллиампер, под нагрузкой — сотни: detach обнуляет расход в ноль
- После повторного attach() серво едет к последнему записанному углу — держите его в переменной, чтобы рывок был предсказуем
- Когда отсоединены все серво-объекты, Timer1 свободен: пины 9 и 10 снова отдают analogWrite
- Отпускайте только механизм, который держит себя сам: рычаг с грузом после detach съезжает без торможения
- 1. Рог после detach(). Что с ним?
- 2. Серво держит рычаг с грузом в верхней точке. Что будет после detach()?
- 3. Серво отпустили, ручку сместили на 40 градусов, потом attach(). Куда поедет рог?
- 4. При каком условии пины 9 и 10 снова дают analogWrite?
- 5. Почему после detach() исчезает мелкая дрожь рога?
Частые вопросы по теме урока
- Рог после detach(). Что с ним?
- Импульсы сняты, драйвер заперт, момент удержания исчез. Осталось трение шестерён — рог крутится рукой без усилия. Пристёгнутый рог так не даётся: контур мгновенно компенсирует смещение. Проворачивается пальцем с лёгким трением редуктора
- Серво держит рычаг с грузом в верхней точке. Что будет после detach()?
- Позиция осталась в переменных библиотеки, но силы за ней больше нет: мотор обесточен, и груз поворачивает рог куда хочет. Сначала опустите рычаг в безопасное положение, потом отпускайте. Рычаг съедет вниз: момент удержания пропал
- Серво отпустили, ручку сместили на 40 градусов, потом attach(). Куда поедет рог?
- Библиотека помнит последний write(): после attach() серво отправляется туда мгновенно, и лишь следующий цикл ведёт её к ручке. Отсюда двойной рывок: сначала к старому углу, затем к актуальному. Сначала прыгнет к последнему записанному углу
- При каком условии пины 9 и 10 снова дают analogWrite?
- Timer1 делится между Servo и ШИМ на пинах 9/10. Пока пристёгнут хотя бы один объект, analogWrite на этих ногах недоступен — отсоедините все объекты разом. Когда отсоединены все серво-объекты: Timer1 свободен
- Почему после detach() исчезает мелкая дрожь рога?
- Дрожь — это работа контура коррекции на живых импульсах каждые 20 мс. Нет импульсов — нет коррекции и нет дрожи; заодно уходит и тихий гул редуктора на простое. Импульсы больше не приходят: контуру нечего корректировать