Карусель для съёмки мелочи под макро-объективом я собрал за вечер, а вот подбирать скорость к ней замучился: для бликов на металле нужен ползучий темп, для обзора сцены — бодрый, и идеал подбирается только на живой модели. Перепрошивать скетч на каждый эксперимент надоело после пятой заливки. Спасла ручка: темп перестал быть константой и стал регулятором на корпусе.
Идея механически простая. В millis-скелете из прошлого урока пауза STEP_MS — константа. Пусть её диктует потенциометр: analogRead на A0, map(raw, 0, 1023, 40, 2) — и пауза между градусами гуляет от 40 до 2 мс. Обратите внимание на порядок границ: fromLow больше fromHigh — map это разрешает и разворачивает шкалу, чтобы правый край ручки означал быстрее, как в любом нормальном пульте.
Направление отдано кнопке на D2: нажатие переворачивает знак dir — вал разворачивается, не останавливаясь. Вместе получается живой пульт хода: вал едет непрерывно, темп подстраивается рукой, разворот — по щелчку. Это другой расклад ролей, чем в уроке про ручное управление: там ручка командовала углом, здесь — скоростью.
Пауза вместо скорости
Скорость в градусах в секунду считается в одну строку: 1000 разделить на паузу в миллисекундах. Линейка выглядит так: 40 мс — 25°/с и полный ход 180° за 7,2 секунды; 15 мс — 67°/с, ход за 2,7 с; 10 мс — 100°/с, за 1,8 с; 5 мс — 200°/с, за 0,9 с; 2 мс — бумажные 500°/с, за 0,36 с. Числа полезно проговорить вслух, потому что они расставляют границы применения: таймлапсу нужен левый край, обзору комнаты — середина, быстрее в этой схеме делать почти нечего.
Почему «почти нечего»: потолок механики SG90 — 0,1 секунды на 60°, то есть 600°/с, как считает урок про скорость серво. Бумажные 500°/с при паузе 2 мс упираются в этот потолок почти вплотную — вал отстаёт от команд, и на ходу вылезает дрожь, знакомая по уроку про помехи и дёрганье. Мой рабочий минимум паузы — 5 мс: серво успевает доехать до промежуточного угла, движение остаётся чистым, а быстрее всё равно не получится.
Числа линейки удобно примерить к своему механизму до пайки: платформа с камерой на 25°/с превращает тряску редуктора в ровное скольжение, а карусель витрины на тех же 25°/с выглядит засыпающей — там просится середина. Пауза — единственная ручка этой схемы, и шкала у неё линейная: вдвое короче пауза — вдвое быстрее ход. Линейность хороша для калибровки на глаз: половина хода ручки всегда означает половину скорости, и положение ползунка становится предсказуемым.
Перестановку границ map стоит запомнить как приём вообще: map(raw, 0, 1023, 40, 2) — это уже инверсия, без лишних вычитаний. Там, где привычно хочется написать 1023 - raw перед map, достаточно поменять местами последние два аргумента. Приём работает и в обратную сторону — растянуть рабочий диапазон ручки, если датчик шумит у края: границы снимаются с реальных показаний, как в стрелочном приборе из двенадцатой тройки.
#include <Servo.h> Servo srv; const int PIN_BTN = 2; // направлениеconst int PIN_POT = A0; // темп unsigned long tStep = 0;unsigned long tBtn = 0;int angle = 0;int dir = 1;int lastBtn = HIGH; void setup() { srv.attach(11); pinMode(PIN_BTN, INPUT_PULLUP); Serial.begin(9600); Serial.println("A0 tempo 2-40 ms, D2 napravlenie");} void loop() { unsigned long now = millis(); int b = digitalRead(PIN_BTN); if (lastBtn == HIGH && b == LOW && now - tBtn > 100) { dir = -dir; tBtn = now; Serial.print("rev: dir="); Serial.println(dir); } lastBtn = b; int msPerDeg = map(analogRead(PIN_POT), 0, 1023, 40, 2); if (now - tStep >= msPerDeg) { angle = angle + dir; if (angle >= 180) { angle = 180; dir = -1; } if (angle <= 0) { angle = 0; dir = 1; } srv.write(angle); tStep = now; }}Ключ к живости пульта — в том, что пауза перечитывается на каждом шаге, а не один раз в setup(). Ручку крутанули — и уже следующий градус уезжает с новой паузой: темп меняется прямо в движении, вал не останавливается ни на миллисекунду. В for-версии такое невозможно в принципе: пауза там зашита в delay внутри прокрутки, и её смена либо дожидается конца хода, либо ломает сам цикл.
Кнопка работает по фронту HIGH-LOW с повторным интервалом 100 мс — двойная защита от дребезга: фронт ловит само нажатие, интервал не даёт подпрыгнувшим уровням сосчитать вторым нажатием. Флип делается строкой dir = -dir: минус единица становится единицей и обратно. На упорах 0 и 180 стоит автоповорот — тем же приёмом, что и в скелете прошлого урока, поэтому пульт нельзя сломать ни ручкой, ни кнопкой.
| Пауза на градус | Скорость | Полный ход 180° | Где уместно |
|---|---|---|---|
| 40 мс | 25°/с | 7,2 с | таймлапс, макросъёмка — ползучий темп |
| 15 мс | 67°/с | 2,7 с | обзор сцены, качели камеры |
| 5 мс | 200°/с | 0,9 с | верх чистого хода: вал ещё успевает |
| 2 мс | 500°/с | 0,36 с | бумага: механика в 600°/с отстаёт, вал дрожит |
Пульт в работе: шум, крайние края, Serial
Один грабель я поймал через день: на самом быстром краю ход покрывался мелкой рябью. Причина — шум АЦП: показания ручки гуляют на 5-10 единиц, и на паузе 2 мс эти единицы — заметная доля. Лечение — читать ручку не каждый шаг, а по собственному таймеру раз в 50 мс, в отдельной переменной: пауза перестаёт дрожать, а отклик ручки остаётся мгновенным. Мой выбор — отдельная метка tPot и чтение раз в 50 мс, две строки поверх скелета.
Если темп хочется вести не только ручкой, но и словами из монитора, пригодится разбор чисел из Serial через parseInt: скетч читает команду, кладёт её в ту же msPerDeg — и пульт получает второй вход. Логика в loop не меняется: источник паузы может быть любым, лишь бы это было число от 2 до 40. У сервоприводов непрерывного вращения скорость командуется иначе — шириной импульса вокруг 1500 мкс, это разобрано в уроке про серво 360, — но millis-скелет там не нужен вовсе: валу не к чему ехать шагами.
Автоповорот на упорах заслуживает отдельного взгляда: в пульте с кнопкой он обязателен. Представьте, что ход развернули кнопкой ровно на упоре 180: dir стал единицей, а идти вверх уже некуда — без прижима скетч бесконечно писал бы 180. Строка if (angle >= 180) { angle = 180; dir = -1; } страхует и это: упор всегда разворачивает, и следующий градус уходит в нужную сторону. Тот же приём держит левый край, и вместе пара условий закрывает весь диапазон при любой комбинации ручки и кнопки.
На реальной плате к пульту стоит приучить крайние края ручки: у дешёвых потенциометров на 10 кОм крайние 20 единиц прыгают, и пауза на упорах скачет. Спасает тот же приём, что и в стрелочном приборе — снять границы с реальных показаний и не полагаться на номиналы: map(raw, 12, 1010, 40, 2) с записанными числами конкретной ручки. Калибровка занимает пять минут, зато левый край перестаёт прыгать между 38 и 40 мс.
Стоит ли читать ручку чаще, чем раз в 50 мс? Я пробовал и потактовое чтение, и опрос через 20 мс — глаз разницы не видит, зато рябь на быстрых краях видна отлично. Пятьдесят опросов в секунду — не магия, а здравый смысл: движение руки занимает сотни миллисекунд, и АЦП с запасом успевает за кистью. Свободные миллисекунды loop лучше отдать соседям по скетчу, чем четвёртому замеру одной и той же ручки.
И про название переменных: msPerDeg — лучшее имя, которое я придумал для этой паузы за все проекты. Оно читается как формула «миллисекунд на градус» и не даёт перепутать паузу с углом в три часа ночи. STEP_MS из константного скелета здесь сменило имя не зря: переменная стала живой, и её смысл стоит держать на виду прямо в коде.
- Скорость считается из паузы: 1000 / мс на градус; 40 мс — 25°/с, 5 мс — 200°/с
- map(raw, 0, 1023, 40, 2) — инверсия границ без вычитаний: правый край ручки становится быстрым
- Пауза перечитывается на каждом шаге — темп меняется на лету, вал не останавливается
- Рабочий минимум паузы — 5 мс: механика SG90 в 600°/с не успевает за бумажными 500°/с при 2 мс
- Кнопка разворота — по фронту с повторным интервалом 100 мс; dir = -dir без условий
- 1. Что вернёт map(512, 0, 1023, 40, 2)?
- 2. Пауза 10 мс на градус — какая скорость в градусах в секунду?
- 3. Почему при паузе 2 мс вал реальной серво может пойти с дрожью?
- 4. Как кнопка разворачивает направление хода в скетче урока?
- 5. Что даёт чтение потенциометра раз в 50 мс по отдельному таймеру?
Частые вопросы по теме урока
- Что вернёт map(512, 0, 1023, 40, 2)?
- 512 стоит ровно посередине 0-1023, а шкала гуляет от 40 до 2: середина даёт 21 мс. Инверсия границ работает как любая пропорция, просто направление обратное. Около 21 — середина шкалы между 40 и 2
- Пауза 10 мс на градус — какая скорость в градусах в секунду?
- Формула 1000 / мс на градус: 1000 / 10 = 100°/с. Проверка в уме: полный ход 180° займёт 1,8 секунды — 180 шагов по 10 мс. 100°/с — 1000 делить на паузу
- Почему при паузе 2 мс вал реальной серво может пойти с дрожью?
- Механика SG90 едет максимум 600°/с, а команды при 2 мс просят 500°/с — на пределе. Вал отстаёт от каждой цели, и ход покрывается дрожью. Рабочий минимум — 5 мс. Бумажные 500°/с упираются в механический потолок SG90 — 600°/с, вал отстаёт от команд
- Как кнопка разворачивает направление хода в скетче урока?
- Одна строка dir = -dir: единица становится минус единицей и обратно. Никаких перепрошивок целей — шаг просто идёт в другую сторону от текущего угла. Переворачивает знак dir строкой dir = -dir
- Что даёт чтение потенциометра раз в 50 мс по отдельному таймеру?
- Шум АЦП в 5-10 единиц на паузе 2 мс — заметная доля, оттого рябь. Опрос раз в 50 мс усредняет скачки, отклик ручки остаётся мгновенным: 50 замеров в секунду хватает движению руки. Глушит рябь паузы от шума АЦП на быстрых краях