Окно в курятнике я открывал шаговиком: рама на тросе, трос на валу 28BYJ-48, между платой и мотором — привычная связка с ULN2003. Скетч был короткий: поднять раму поворотом вала на девяносто градусов и оставить до вечера. Скорость я выставил 15 оборотов в минуту — потолок, который мы вымеряли в уроке про скорость и позиционирование: с пустым блоком мотор его держал. На столе всё выглядело прилично, а на раме, натянутой под собственным весом, каждый старт начинался со щелчка — редуктор перескакивал зуб через зуб, и рама вздрагивала так, что птицы шарахались по клетке.
Диагноз лежал в первой строке скетча. Пятнадцать оборотов в минуту на 2048 шагах — это 512 шагов в секунду, один шаг каждые 1,95 миллисекунды, и именно на эти полторы-две миллисекунды приходятся самые тяжёлые мгновения всего хода: обмотке надо нарастить ток, шестерням редуктора — стронуться с места, тросу — выбрать слабину. На пятом шаге всё это уже позади, а на первом — нет. Библиотека Stepper.h разгона не умеет вовсе: setSpeed означает едь с первого такта, промежуточных скоростей у неё нет.
Здесь соберём разгон руками: посчитаем, что происходит в первые полсотни шагов, напишем свою подачу фаз с плавной сменой интервала и зеркальным торможением к цели, а в симуляторе потрогаем ручку скорости и кнопки старта. В следующем уроке отдадим эту работу библиотеке AccelStepper — и честно сравним, что она делает лучше самоделки, а что нет.
Что происходит в первые полсотни шагов
На старте против мотора работают три вещи сразу. Первая — статическое трение: сдвинуть покоящийся редуктор 1:64 с пластиковыми шестернями труднее, чем потом крутить, и это видно прямо на анатомии мотора, где шестерни посажены плотно, без компенсаторов. Вторая — инерция: ротор и весь стек шестерён нужно разогнать с нуля, а через редуктор масса нагрузки отражается на мотор в квадрате передаточного числа. Третья — индуктивность обмотки: ток не прыгает до рабочих 90-100 миллиампер мгновенно, магнитному полю нужно время нарастить.
Сложим с темпом. При 15 об/мин импульсы идут каждые 1,95 мс — быстрее, чем полю и механике нужно на первые шаги. Ротор отстаёт от поля всё сильнее и в какой-то момент перестаёт догонять вовсе: мотор гудит и дрожит, а вал стоит. Это срыв шагов, и у него коварная черта, которую мы разбирали в уроке про срыв и момент: программа честно отсчитывает шаги, счётчик позиции идёт, а вал про эти шаги не знает. Позиция с этого момента врёт молча — ни флага, ни сообщения в Serial.
Рецепт стар как форумы: паузу между импульсами резать от шага к шагу. Стартуем с тихого темпа, где каждая обмотка успевает, — и плавно подходим к рабочему. Торможение — тот же разгон, прочитанный справа налево: интервал растёт обратно, и вал доезжает до цели без удара об упор. Всё вместе называется профилем скорости, рисуется он трапецией: подъём, плато, спуск.
Разгон дельтой: интервал вместо RPM
Разгон живёт в интервале между шагами, и мерить его честнее в микросекундах. Стартовый интервал — 5900 мкс, это те же спокойные 5 об/мин: шаг каждые 5,85 миллисекунды, темп, при котором в стартовые мгновения ничего не щёлкает. Потолок — 2200 мкс, около 13,3 об/мин: до лотерейных пятнадцати остаётся запас. Дельта — два процента текущего интервала за шаг: с 5,9 до 2,2 миллисекунды такой разгон доезжает за полсотни шагов, это треть секунды.
Почему микросекунды, а не delay(). Аргумент delay() целочисленный: интервал 2,2 миллисекунды через него не задать — дробь уйдёт в пол, и мотор будет шагать то через 2, то через 3 мс, промах до трети интервала на самом быстром участке. delayMicroseconds(2200) точен до нескольких микросекунд. А millis-таймер хорош для медленных темпов — мы так кормили слайдер, — но при интервалах короче десяти миллисекунд в дело вступает собственная задержка внутри step(), поэтому здесь честнее водить фазы самим, без библиотеки.
Тормозной путь равен разгонному: если разгон занял полсотни шагов, начинать тормозить нужно за те же полсотни до цели. С потолком 2,2 мс это сотня миллисекунд торможения — глазу кажется мгновением, а механике хватает: удар об упор исчезает, шестерни перестают щёлкать. Для коротких ходов, где полсотни шагов не набирается, профиль складывается в треугольник — половина пути разгон, половина торможение.
// Свои фазы вместо библиотеки: полный шаг, разгон и торможение на интервалах.// Пины IN1..IN4: 8, 10, 9, 11 — тот же порядок, что и у Stepper.h.const int PINS[4] = {8, 10, 9, 11};const int PHASE[4][4] = { {1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 1}, {1, 0, 0, 1}}; const unsigned int IVSTART = 5900; // старт, мкс: 5 об/минconst unsigned int IVMIN = 2200; // потолок, мкс: 13,3 об/минconst long NRAMP = 50; // шагов на разгон и на торможение int phaseIdx = 0; void coilStep(int dir) { phaseIdx = phaseIdx + dir; if (phaseIdx > 3) { phaseIdx = 0; } if (phaseIdx < 0) { phaseIdx = 3; } for (int i = 0; i < 4; i++) { digitalWrite(PINS[i], PHASE[phaseIdx][i]); }} // Ход на steps шагов: разгон NRAMP шагов, плато, торможение NRAMP шаговvoid turnRamp(long steps, int dir) { long nRamp = NRAMP; if (nRamp * 2 > steps) { nRamp = steps / 2; } // короткий ход: треугольник unsigned int iv; for (long stepNo = 0; stepNo < steps; stepNo++) { coilStep(dir); if (stepNo < nRamp) { // разгон: интервал тает по линейке iv = IVMIN + (unsigned long)(IVSTART - IVMIN) * (nRamp - stepNo) / nRamp; } else if (steps - stepNo <= nRamp) { // торможение: зеркально iv = IVMIN + (unsigned long)(IVSTART - IVMIN) * (nRamp - (steps - stepNo)) / nRamp; } else { iv = IVMIN; // плато } delayMicroseconds(iv); } for (int i = 0; i < 4; i++) { // обесточили: механика держит сама digitalWrite(PINS[i], LOW); }} void setup() { for (int i = 0; i < 4; i++) { pinMode(PINS[i], OUTPUT); } Serial.begin(9600); Serial.println("Okno: 90 gradusov s razgom");} void loop() { unsigned long t0 = millis(); turnRamp(512, 1); Serial.print("hod: "); Serial.print(millis() - t0); Serial.println(" ms"); delay(4000); turnRamp(512, -1); Serial.println("nazad"); delay(4000);}В скетче три места, которые проговорим. Первое — таблица фаз: четыре строки по четыре бита, каждая строка — положение обмоток в полном шаге, две катушки из четырёх под током. Порядок пинов тот же, к которому мы приучили Stepper.h: 8, 10, 9, 11. Светодиоды на плате ULN2003 бегут парами — если побежали не парами, таблица побита, и вал будет дёргаться на месте с гулом.
Второе — линейка разгона. Я записал её линейной интерполяцией: на шаге номер k из пятидесяти интервал равен стартовому минус соответствующая доля разницы. Классический вариант с процентной дельтой — iv = iv - iv / 50 — работает так же, но линейку проще читать и править: захотите растянуть разгон на сотню шагов — меняете одну константу. Треугольный ход считается той же функцией: nRamp просто прижимается к половине маршрута.
Третье — финал хода: после последнего шага все четыре пина падают в ноль, и ток с 240 мА удержания спадает почти до нуля. Окну держаться ничем не нужно — трос и редуктор держат раму сами. Подробно о цене удержания мы говорили в уроке про отключение катушек; здесь приём встроен прямо в конец turnRamp.
| Старт | Интервал | Что происходит | Куда годится |
|---|---|---|---|
| Сразу на 15 об/мин | 1,95 мс с первого шага | Гул, дрожь, щелчки редуктора; счётчик идёт, вал может стоять | Никуда: лотерея уже в первом обороте |
| Сразу на 5 об/мин | 5,9 мс | Тихо и надёжно, но оборот длится 12 секунд | Заслонки и краны, где скорость безразлична |
| Разгон 5 → 13,3 об/мин | 5,9 → 2,2 мс, полсотни шагов | Старт без щелчков, рабочий темп за треть секунды | Слайдеры, столики, всё с грузом |
| Разгон плюс торможение | зеркально обратно | Вал доезжает мягко, без удара об упор | Приводы с упорами и тросами |
Разгон в симуляторе
Симулятор двигателя в книге умеет Stepper.h — setSpeed и блокирующий step() на циферблате, — а delayMicroseconds и собственные фазы он не показывает, говорю честно заранее. Поэтому модель разгона собрана на millis-таймере, как в слайдерном уроке: мотор всегда готов на потолке подачи, а реальный темп держит таймер, интервал которого ползёт от 40 миллисекунд к целевому. Разгон здесь виден по печати iv=, а не по стрелке циферблата.
Ручка A0 задаёт потолок, кнопка D2 — старт и пауза, тумблер D3 — направление. Каждое нажатие старта возвращает интервал на 40 мс: мотор начинает с тихого хода даже при ручке на упоре — так же ведёт себя и настоящий скетч выше. Смотрите на пары iv= и tgt=: разница между ними тает шаг за шагом — это и есть трамплин.
Попробуйте два старта подряд: один с ручкой в нуле, второй на потолке. В обоих случаях первые шаги идут с одного и того же интервала — меняется только цель разгона. Именно так выглядит плавная смена интервала из заголовка урока: без магии, арифметика на одну строку внутри loop().
- 15 об/мин = 512 шагов/с = 1,95 мс на шаг: в первые мгновения поле и механика не успевают — отсюда щелчки и срыв
- Разгон — это интервал, а не RPM: старт с 5,9 мс, потолок 2,2 мс (13,3 об/мин), линейка на полсотни шагов
- delayMicroseconds точен до микросекунд; delay() съедает дробь — интервалы с дробной частью через него не задают
- Тормозной путь равен разгонному: за полсотни шагов до цели интервал растёт обратно; короткий ход складывается в треугольник
- Срыв шагов не поднимает флагов: счётчик идёт, вал стоит — спасает репер в нулевой точке
- После хода обесточивайте фазы: минус 240 мА удержания, если механика держит себя сама
- 1. Почему старт сразу на 15 об/мин срывает шаги, хотя сам темп мотору по силам?
- 2. Один шаг при 15 об/мин и 2048 шагах на оборот — это сколько времени?
- 3. Интервал режут на два процента за шаг: с 5,9 мс до потолка 2,2 мс. Сколько примерно шагов займёт разгон?
- 4. Как понять, когда начинать торможение к цели?
- 5. Что происходит со счётчиком позиции после срыва шагов?
Частые вопросы по теме урока
- Почему старт сразу на 15 об/мин срывает шаги, хотя сам темп мотору по силам?
- На старте статическое трение выше рабочего, а инерция шестерён максимальна. При шаге в 1,95 мс полю и механике не хватает этих миллисекунд — ротор срывается. Статическое трение и инерция редуктора: в первые шаги ротор не успевает за полем
- Один шаг при 15 об/мин и 2048 шагах на оборот — это сколько времени?
- 60 000 мс / (15 × 2048) = 1,95 мс. 5,9 мс — это 5 об/мин, стартовый интервал разгона. Около 1,95 мс
- Интервал режут на два процента за шаг: с 5,9 мс до потолка 2,2 мс. Сколько примерно шагов займёт разгон?
- Двухпроцентная дельта съедает разницу за полсотни шагов — треть секунды. Пятьсот шагов было бы расточительством, пять — не разгон, а рывок. Около полусотни
- Как понять, когда начинать торможение к цели?
- Профиль симметричен: сколько шагов разгонялись, столько же тормозим. Для коротких ходов профиль складывается в треугольник — пополам на разгон и торможение. Тормозной путь равен разгонному: начинать за те же полсотни шагов до цели
- Что происходит со счётчиком позиции после срыва шагов?
- Срыв не поднимает флагов: программа отсчитывает шаги, вал про них не знает. Единственное лекарство — репер в нулевой точке. Продолжает считать: позиция молча уезжает от реальной