Однажды я решил разогнать плавный ход по-честному: поставил паузу 1 мс на градус и ждал ракету. Вал не ускорился, зато скетч стал злее к остальным задачам — loop крутился, как заведённый, а серво ехала с тем же лицом. Разговор тогда закончился на мануале, а вывод стоит отдельного урока: у серво есть свой ритм, и наш скетч его не выбирает.
Серво не слушает write() напрямую. Она слушает импульсы, которые библиотека Servo.h рисует на пине каждые 20 миллисекунд — 50 раз в секунду. В исходниках библиотеки за это отвечает константа REFRESH_INTERVAL со значением 2000 — микросекунд на кадр. На каждом кадре электроника серво пересматривает цель; всё, что скетч успел записать между кадрами, живёт в памяти платы до ближайшего кадра и больше ни на что не влияет.
В уроке про подключение SG90 частота 50 Гц упоминалась как факт из паспорта; здесь посмотрим на неё как на границу проектирования. Посчитаем, сколько команд реально доезжает при паузах 2, 5, 20 и 40 мс, проверим счётчиком в симуляторе — и разберёмся, зачем вообще существует нижняя граница паузы.
Кадр 20 мс: арифметика потерь
Считать удобнее в кадрах. Пауза 40 мс — новые цели каждые два кадра: 25 целей в секунду, и каждая доезжает спокойно. Пауза 20 мс — ровно один кадр: 50 целей в секунду, идеальная пара скетчу и серво. Пауза 5 мс — вы пишете 200 целей в секунду, а кадров всего 50: три из четырёх команд серво никогда не увидит, она прочитает лишь последний записанный угол на момент кадра.
При 1-2 мс потолок тот же: 50 кадров. Легенда «пауза 1 мс — значит 1000°/с» не переживает встречи с механикой SG90: 0,1 секунды на 60° — это 600°/с максимум, да и усилителю серво нужен целый кадр, чтобы вообще что-то понести. Фактический эффект ультракороткой паузы: вал сглатывает промежуточные градусы и едет рывками, а loop теряет сотни проходов в секунду на бессмысленные записи.
Откуда взялся именно 20 мс: аналоговые серво проектировались под кадр 50 Гц, и их усилитель буквально ждёт импульс с таким интервалом. Цифровые модели гоняют внутренний контур быстрее — у быстрых серво он доходит до 333 Гц, — но входной кадр чаще всего остаётся обычным, и гнаться за частотой команд нет смысла. Библиотека позволяет поднять частоту правкой REFRESH_INTERVAL — на форуме arduino.cc этим занимались любители 100 Гц для полётных контроллеров, — но на аналоговой серво выигрыша я не увидел: кадр жёстче, чем кажется.
Механику потерь легко увидеть на бумаге: вообразите конвейер из 50 слотов в секунду, каждый слот — один кадр. write() кладёт угол на ленту в любой момент, но забирает его электроника серво только на слоте. Быстрый скетч успевает положить четыре угла между слотами — доедет последний положенный, три предыдущих уйдут непрочитанными. Слот работает всегда, даже когда серво стоит: усилитель ждёт импульс и в покое, отсюда и ток удержания, о котором говорит отдельный урок.
#include <Servo.h> Servo srv; const int PIN_POT = A0; unsigned long tStep = 0;unsigned long tReport = 0;int angle = 0;int dir = 1;int writes = 0; void setup() { srv.attach(11); Serial.begin(9600); Serial.println("A0: pauza 1-40 ms; schetchik write za sekundu");} void loop() { unsigned long now = millis(); int msPerDeg = map(analogRead(PIN_POT), 0, 1023, 40, 1); if (now - tStep >= msPerDeg) { angle = angle + dir; if (angle >= 180) { angle = 180; dir = -1; } if (angle <= 0) { angle = 0; dir = 1; } srv.write(angle); writes = writes + 1; tStep = now; } if (now - tReport >= 1000) { Serial.print("pauza="); Serial.print(msPerDeg); Serial.print(" ms write="); Serial.print(writes); Serial.print("/s ugol="); Serial.println(angle); writes = 0; tReport = now; }}Счётчик writes считает ровно то, что уходит в write() за секунду, — и это число не совпадает с числом кадров, которые видит серво. При паузе 40 мс счётчик покажет около 25, при 20 мс — около 50, а при 1 мс — под 900, и ни одна из этих девятисот целей не доедет быстрее, чем в варианте с 20 мс. Разница между написанным и увиденным — и есть урок этой главы: кадр живёт на стороне библиотеки и серво, а не в нашем скетче.
Ширину импульса внутри кадра библиотека хранит с точностью до микросекунды, и тонкая подстройка угла доступна через writeMicroseconds — это разобрано в уроке про импульсы 1000-2000 мкс. Но гранулы ширины не отменяют гранулы времени: кадр всё равно 20 мс, точнее станет угол, а не скорость. Если нужен по-настоящему свой ритм импульсов — это путь урока про Timer1 без библиотеки, где кадр рисуется руками.
Кстати, кадр не зависит от загрузки loop: импульсы Servo.h рисует прерывание от Timer1, и даже забитый скетч его не сдвинет. Зато millis-задачи вокруг от тяжёлой печати страдают первыми — пульсацию хода пульта я однажды полдня ловил, а виноват был Serial.println в каждом проходе. Разница примечательная: частоту кадров вы не контролируете вовсе, а распределение своих миллисекунд между задачами — полностью.
| Пауза на градус | Целей в секунду | Видит серво | Что на вале |
|---|---|---|---|
| 40 мс | 25 | все — по одному на два кадра | плавно, с запасом на доезд |
| 20 мс | 50 | все — идеальная пара кадру | плавно, максимум полезного темпа |
| 5 мс | 200 | каждую четвёртую — 50/с | на глаз то же, что при 20 мс |
| 1-2 мс | 500-900 | те же 50/с, промежутки сглатываются | рывки, дрожь, loop занят зря |
Симулятор считает команды, плата — кадры
Симулятор в книге честно показывает команды: каждая write меняет угол на циферблате, счётчик печатает их количество в секунду. Реальная серво видит не больше 50 целей — кадры. Эта разница и есть демонстрация: покрутите ручку в левый край, где счётчик выдаёт сотни записей в секунду, и вспомните, что на настоящей плате из них доедут максимум полсотни. Серво в симуляторе доехала бы мгновенно; вал настоящей SG90 ограничен ещё и механикой 0,1 с на 60°.
Счётчик полезен и на реальной плате — он показывает цену скетча для loop. Каждая лишняя запись — это пересчёт ширины и проход цикла; при паузе 1 мс loop почти не остаётся свободным, и соседи по скетчу начинают голодать. Заодно счётчик отучает мерить скорость глазами: в симуляторе серво отзывается мгновенно, на плате вал догоняет за 0,1 секунды на 60° — обе цифры честные, каждая про своё. Моё правило после того разгона: пауза на градус — от 5 мс, а желание ехать быстрее решается не частотой команд, а выбором серво и редуктора под задачу.
Кадр 20 мс объясняет и ещё одну привычную картину: почему при нескольких серво скетчи спокойно крутят их из одного loop. Библиотека раскладывает импульсы каналов внутри одного и того же периода — на Uno таймер Timer1 тянет до 12 серво с кадром 20 мс у каждого. Упираемся мы обычно не в каналы, а в питание: урок про несколько серво на Uno считает токи раньше, чем подключается вторая серво.
Соберём правила главы в три строки. Первое: пауза между градусами от 5 мс — короче нет смысла, кадр всё равно 20 мс. Второе: скорость упирается в механику, а не в частоту команд. Третье: счётчик write/s — быстрый способ увидеть, сколько работы скетч раздаёт зря. С ними millis-скелет из первого урока и пульт из второго перестают быть рецептом — становятся понятной системой, где у каждого числа есть владелец. Владелец частоты кадров — библиотека; ваша собственность — пауза между градусами и то, что живёт между ними.
- Servo.h рисует импульс каждые 20 мс — REFRESH_INTERVAL 2000 мкс; серво видит максимум 50 целей в секунду
- Пауза 5 мс даёт 200 целей/с, но серво прочтёт 50: три из четырёх команд теряются между кадрами
- Скорость упирается в механику 0,1 с на 60° и кадр, а не в частоту write
- На Uno Timer1 обслуживает до 12 серво; реальный предел ставит питание, не каналы
- Счётчик write/s в Serial — мгновенный диагноз перегруза loop и зряшных записей
- 1. Сколько раз в секунду Servo.h выдаёт импульс на пин серво?
- 2. При паузе 5 мс скетч шлёт 200 целей в секунду. Сколько из них реально увидит серво?
- 3. Что на самом деле ограничивает скорость вала при паузе 1-2 мс?
- 4. Сколько серво обслужит Servo.h на Uno с одним Timer1?
- 5. Что даёт writeMicroseconds с точностью до микросекунды?
Частые вопросы по теме урока
- Сколько раз в секунду Servo.h выдаёт импульс на пин серво?
- Кадр задан константой REFRESH_INTERVAL 2000 мкс: импульс каждые 20 мс, то есть 50 Гц. Скетч может писать чаще — серво прочтёт цель только на ближайшем кадре. 50 раз — кадр каждые 20 мс
- При паузе 5 мс скетч шлёт 200 целей в секунду. Сколько из них реально увидит серво?
- Кадров 50 в секунду, и на каждом серво читает текущее значение ширины. Промежуточные записи живут в памяти платы до кадра и видны только последними — отсюда потери три из четырёх. Около 50 — последний угол на момент каждого кадра
- Что на самом деле ограничивает скорость вала при паузе 1-2 мс?
- Двойной потолок: усилитель серво ждёт кадр 50 Гц, а редуктор и мотор не едут быстрее 600°/с. Ни частота команд, ни их количество этот потолок не двигают. Механика 0,1 с на 60° и кадр 20 мс
- Сколько серво обслужит Servo.h на Uno с одним Timer1?
- Каналов до 12 на таймер, импульсы всех серво раскладываются внутри одного периода 20 мс. Практический предел ставит питание: токи растут быстрее, чем каналы кончаются. До 12 каналов — по константе SERVOS_PER_TIMER
- Что даёт writeMicroseconds с точностью до микросекунды?
- Гранулы ширины импульса — это про точность угла, не про скорость: временной кадр REFRESH_INTERVAL остаётся 20 мс. Быстрее вал от этого не едет. Тонкую подстройку угла — но кадр остаётся 20 мс