На двери моей мастерской висит картонная стрелка на сервоприводе SG90: с одной стороны «Открыто», с другой «Закрыто». Идея родилась за пять минут, и ровно столько же прожил первый скетч: write(0), пауза, write(180), пауза. Серво трещала, как заведённый сверчок, перелетала от края к краю за треть секунды, и стрелка дважды в секунду меняла мнение о том, открыт я или нет. Команды были правильные, углы те самые — не хватало главного: управления временем. Знакомая картина? Она ждёт каждого, кто ставит серво на стол и кормит её командами наугад.
Качание от края до края — у него скучное английское имя sweep — это первый настоящий скетч любого серво-проекта. Им проверяют свежекупленную SG90 перед установкой в механизм, из него вырастают шлагбаумы, радары на HC-SR04 и антенны. Здесь соберём маятник 0–180° на циклах for, разберём, откуда берутся паузы и почему они обязаны быть длиннее времени хода, а заодно научимся замерять фактическую скорость серво через millis(). Считаю, что это лучший второй день после первого знакомства в уроке про подключение SG90: там мы научились приказывать угол, здесь — управлять темпом. Всё работает и в симуляторе ниже — Serial-монитор печатает угол парами ang=57, а настоящая плата понадобится, когда захотите, чтобы стрелка жила на двери, а не в браузере.
Скелет качания: два цикла for и пауза на каждом шаге
Устройство качания прозрачнее некуда: два цикла for, знакомых по уроку про циклы. Первый гонит переменную угла от 0 до 180, второй — в обратную сторону. Внутри каждого шага происходит одно и то же: write(ang) отправляет цель, delay(PAUSE) даёт механике время на шажок, а Serial выводит пару ang=57. Формат «имя=число» выбран не для красоты: Serial Plotter в Arduino IDE строит по таким парам живой график, и качание превращается в аккуратную пилу на экране — мы разбирали эту привычку в уроке про график Serial Plotter.
Шаг я взял 5°, а не по градусу: 37 команд на проход вместо 181, и паузу на шаг можно сделать щедрее — 60 мс. Механике нужно примерно 1,7 мс на градус, значит на шажок в пять градусов хватает и восьми, но запас никогда не мешал: у подгулявшей серво с изношенными шестернями последний градус доезжает лениво, с хрустом. Пауза 800 мс на краях — отдельная строка бюджета: картонная стрелка имеет инерцию, и если не дать ей успокоиться на границе, разворот выглядит как нервный тик. На реальной серво ставьте паузу на краях вдвое длиннее, чем хочется эстетически, — глаз скажет спасибо.
#include <Servo.h> Servo sv; const int PAUSE = 60; // пауза на один шаг, мс void setup() { sv.attach(9); Serial.begin(9600);} void loop() { for (int ang = 0; ang <= 180; ang += 5) { sv.write(ang); Serial.print("ang="); Serial.println(ang); delay(PAUSE); } delay(800); // пауза на левом краю for (int ang = 180; ang >= 0; ang -= 5) { sv.write(ang); Serial.print("ang="); Serial.println(ang); delay(PAUSE); } delay(800); // пауза на правом краю}Тайминги: откуда взяты все эти числа
Посчитаем цикл по цифрам, а не на глаз. SG90 по даташиту проходит 60° примерно за 0,1 с при питании 4,8–6 В — значит, полный ход 180° занимает около 0,3 секунды чистой механики. Мой скетч тратит на проход 37 шагов × 60 мс = 2,2 с: серво доехала до промежуточного угла с запасом почти втрое и спокойно ждёт следующей команды, поэтому движение выглядит плавным, а мотор не пилит туда-сюда. Сожмите паузу до 5 мс — и команды пойдут быстрее механики: write() обгоняет вал, серво жужжит и дрожит, но быстрее почему-то не становится. Это тот самый случай, когда «ускорил» означает «сломал».
Второе следствие пауз неприятнее: качание блокирует loop() наглухо. Полный цикл 0 → 180 → 0 с паузами на краях — это 2 × (2,2 + 0,8) = 6 секунд, в течение которых Arduino не видит ни кнопок, ни датчиков, ни Serial. Для картонной стрелки неважно, для шлагбаума — смертельно: машина подъедет раньше, чем скетч выберется из цикла. Лекарство — переход на millis(), о котором подробно написано в уроке про millis вместо delay; но прежде чем чинить, научимся измерять то, что происходит на самом деле. Кстати, о питании: все эти цифры верны при 4,8–6 В на красном проводе. От 5 В платы серво тянет те же 0,3 секунды на ход, но чуть вяло; от 6 В становится резвее на десятую долю; ниже 4,5 В SG90 начинает сдавать — недоезжает и подвывает. Это первая ниточка, связывающая тайминги с питанием: скорость серво — функция напряжения, и просевшая шина замедляет механику ровно так же, как короткая пауза.
| Пауза на шаг | Время прохода 180° | Что вы увидите |
|---|---|---|
| 5 мс (шаг 5°) | 0,2 с — быстрее механики | серво дрожит и жужжит, вал отстаёт от команд |
| 15 мс (шаг 1°) | 2,7 с | плавно, классика из уроков про плавное движение |
| 60 мс (шаг 5°) | 2,2 с | плавно и с запасом — мой дефолт для тестов |
| 200 мс (шаг 5°) | 7,4 с | торжественно медленно: шлагбаумы и анимации |
Теперь заставим цифры работать на нас. В скетче ниже один и тот же ход 0–180° проходится дважды с разной паузой: 10 мс на каждые два градуса и 4 мс. millis() засекает каждый проход, и в Serial падают пары ms=: у моей эталонной SG90 медленный вариант вышел 1010 мс, быстрый — 420, при расчётных 900 и 360. Реальность стабильно на пять-десять процентов хуже арифметики, и именно поэтому я замеряю, а не верю. Если механизм упирается в упор, время внезапно вырастет вдвое, и в логе появится подозрительная цифра — так серво честно сообщает, что бьётся о преграду каждые 20 мс.
Ручек у качания три, и они независимы. Центр: качание 45–135° не задевает упоры, если механизму не нужен весь ход. Амплитуда задаёт размах, пауза — скорость, причём скорость выше механического предела серво не поднимется никогда: write() назначает только цель, едут серво всегда с одной и той же максимальной скоростью — мы это разбирали в уроке про плавное движение. И маленькая проверка на внимательность: sv.read() сразу после быстрого прохода к нулю вернёт 0 — не фактическое положение вала, а последний заказанный угол, серво не отчитывается о реальности.
Отдельно про картинку в Serial Plotter: пила при шаге 5° выходит ровной и плотной, а при шаге 1° — почти прямой линией с еле заметной лесенкой. Если на графике видны плато в середине хода — серво где-то застревает или питание просело; график качания становится бесплатным диагностом схемы ещё до всяких мультиметров. Я так поймал однажды подтекающий контакт на макетке: пила изредка тупилась ровно в одной точке хода, где серво тянула сильнее всего.
#include <Servo.h> Servo sv; unsigned long t0; // старт очередного хода void setup() { sv.attach(9); Serial.begin(9600);} void loop() { // медленный проход: 10 мс на каждые два градуса t0 = millis(); for (int ang = 0; ang <= 180; ang += 2) { sv.write(ang); delay(10); } Serial.print("ms="); Serial.println(millis() - t0); delay(700); // быстрый проход обратно: 4 мс на шаг t0 = millis(); for (int ang = 180; ang >= 0; ang -= 2) { sv.write(ang); delay(4); } Serial.print("ms="); Serial.println(millis() - t0); delay(700);}Эксперименты для закрепления
Скелет качания — это конструктор из трёх чисел: шаг, пауза и границы. Поиграйте ими прямо в симуляторе, пока рука не запомнила, — на настоящей плате это те же строчки кода, только с хрустом. Первый эксперимент: шаг 1° при паузе 10 мс — движение становится маслянистым, а время прохода почти не меняется, потому что команда и механика снова идут в ногу. Второй: сделайте ход влево медленным (пауза 100 мс), а вправо быстрым (15 мс) — так делают маятники-метрономы, которые в одну сторону ведут линию, а обратно возвращаются незаметно. Третий, самый честный: уберите паузы на краях и посмотрите, как стрелка начинает сновать, — вы своими глазами увидите, зачем нужна была пауза 800 мс.
Четвёртый — для тех, кто добрался до millis(): перепишите медленный проход так, чтобы пауза плавно сокращалась от 40 мс до 10 мс каждые тридцать градусов, и вы получите разгон с ускорением, как у настоящего привода. Пятый — обратная задача: ограничьте скорость так, чтобы ход занимал ровно пять секунд, и проверьте замером; разница между расчётными и фактическими ms= скажет вам больше о вашей серво, чем любой даташит. Эти пять задач закрывают весь урок: циклы, паузы, замер и главное убеждение — серво едет с предельной механической скоростью, какой бы скетч вы ни написали.
- Качание 0–180° — два цикла for с паузой на каждом шаге; пары ang=57 рисуют пилу в Serial Plotter
- SG90 едет около 0,1 с на 60°: пауза на шаг обязана быть длиннее времени хода, иначе серво дрожит
- Мой дефолт для тестов: шаг 5° и delay(60) — проход за 2,2 с с тройным запасом
- Фактическую скорость замеряем millis() до и после хода: даташит оптимистичен на 10–15%
- write() назначает только цель; скорость серво регулируется паузами и ничем больше
- Качание блокирует loop() на весь цикл — для проектов с кнопками переходим на millis()
- 1. Почему пауза между шагами качания обязана быть не короче времени хода?
- 2. SG90 проходит 60° примерно за 0,1 с. Сколько займёт полный ход на 180°?
- 3. Зачем печатать угол парами ang=57, а не произвольным текстом?
- 4. Сколько длится полный цикл 0 → 180 → 0 при шаге 5°, паузе 60 мс на шаг и 800 мс на краях?
- 5. Что вернёт sv.read() сразу после быстрого прохода к 0 из скетча с замером?
Частые вопросы по теме урока
- Почему пауза между шагами качания обязана быть не короче времени хода?
- write() назначает цель, а вал догоняет её с конечной скоростью. Команды быстрее механики — серво пилит и дрожит вместо плавного хода. Серво не успеет доехать до промежуточного угла и начнёт дрожать
- SG90 проходит 60° примерно за 0,1 с. Сколько займёт полный ход на 180°?
- 0,1 с на 60° — значит 0,3 с на 180°. На практике к этой цифре добавляйте 10–15% запаса. Около 0,3 с — ход масштабируется линейно
- Зачем печатать угол парами ang=57, а не произвольным текстом?
- Формат имя=значение понимает Serial Plotter: каждая пара становится рядом графика, и качание видно как пилу. Serial Plotter строит график именно по парам имя=число
- Сколько длится полный цикл 0 → 180 → 0 при шаге 5°, паузе 60 мс на шаг и 800 мс на краях?
- Два прохода по 2,2 с плюс две паузы по 0,8 с: 2 × (2,2 + 0,8) = 6 с. Всё это время loop() заблокирован. Около 6 с
- Что вернёт sv.read() сразу после быстрого прохода к 0 из скетча с замером?
- read() возвращает последний записанный через write() угол, а не реальное положение: серво не отчитывается о факте. 0 — последний заказанный угол