Библиотека AccelStepper: установка, пример кода и плавный разгон
Штатная Stepper.h вела себя прилично ровно до первого серьёзного проекта: каретка на 15 об/мин ехала с рывком, loop() во время движения не замечал ни кнопку стоп, ни датчик края, а попытка выжать из NEMA17 побольше оборотов заканчивалась звенящим валом и пропущенными шагами. Совет с форумов «просто убавьте скорость» лечит симптом: причина в блокирующей библиотеке без разгона, где каждый шаг съедает всё внимание процессора. AccelStepper писалась ровно под эти три боли — движение без блокировок, трапецеидальный разгон и несколько моторов из одного loop().
Ниже — установка в один клик из менеджера библиотек, рабочий скетч для 28BYJ-48 через ULN2003 с честным порядком пинов IN1-IN3-IN2-IN4, вариант для NEMA17 с драйвером A4988, сравнение со Stepper.h по шести критериям и разбор, где блокирует runToPosition() и почему run() дёргают тысячи раз в секунду. Оба скетча крутятся в симуляторе — мотор там виртуальный, а логика кода та же.
Stepper.h против AccelStepper: сравнение без розовых очков
Штатная библиотека из состава Arduino IDE шагает функцией step(): она вернёт управление только когда вал докрутит до заданной позиции. У 28BYJ-48 редуктор 64:1 даёт 2048 полных шагов на оборот, и на скромных 10 об/мин один поворот вала занимает шесть секунд, в которые плата глуха к кнопкам, Serial и датчикам. AccelStepper построена на противоположной идее: вы дёргаете run() на каждом проходе loop(), а она решает, пора ли выдать следующий импульс, и сразу возвращает управление. Разгон встроен в ядро: мотор выезжает на целевую скорость по кривой с заданным ускорением, и вал перестаёт срываться на старте. Что творится внутри счётчика шагов и почему торможение считают от остатка пути — разобрано в уроке про устройство AccelStepper, а здесь — таблица по бытовым критериям.
| Критерий | Stepper.h | AccelStepper |
|---|---|---|
| Блокировка | step() держит loop() до конца движения | run() отрабатывает один шаг и сразу возвращает управление |
| Разгон | Нет: сразу рабочая скорость, вал срывается | Трапеция из setAcceleration(): разгон и мягкое торможение |
| Полушаг | Недоступен: 28BYJ-48 шагает только полным шагом | HALF4WIRE: 4096 позиций на оборот вместо 2048 |
| Несколько моторов | Строго по очереди, каждый ждёт соседа | Параллельно: по run() на каждый мотор в loop() |
| Потолок скорости | Задан delayMicroseconds() внутри библиотеки | Около 4000 шагов в секунду на Uno при 16 МГц |
| Позиции и координаты | Только относительное число шагов | moveTo() и currentPosition(): абсолютные цели, очередь движений |
Потолок в 4000 шагов в секунду — не перестраховка: на каждом шаге библиотека успевает посчитать новую скорость по кривой разгона, и при 16 МГц это честный предел для одного мотора на Uno. Отсюда правило: setMaxSpeed() и setAcceleration() вызывают один раз в setup() — каждая смена параметров пересчитывает профиль с нуля, и мотор дёргает вал, как будто его тянут за рукав. Скорость на лету меняют на границах движений, а не в каждом проходе loop().
Установка и первый скетч: 28BYJ-48 через ULN2003
Ставится библиотека из менеджера: Sketch → Include Library → Manage Libraries, в поиске набираете AccelStepper, автор Mike McCauley, кнопка Install. Оффлайн-вариант — архив с сайта автора, распаковывается в папку libraries/ с сохранением структуры. Самая частая связка для старта — мотор 28BYJ-48 с платой ULN2003 на пяти проводах: питание 5 В и четыре сигнальных провода на пины D8, D10, D9, D11. Порядок идёт крест-накрест, потому что фазы мотора расшиты не по порядку выводов платы: в прямом подключении вал гудит на месте, и это первый затык каждого второго новичка.
#include <AccelStepper.h> // HALF4WIRE — полушаг четырёхфазного мотора: 4096 позиций на оборот.// Порядок пинов IN1, IN3, IN2, IN4 — не опечатка: фазы ULN2003 идут крест-накрестAccelStepper stepper(AccelStepper::HALF4WIRE, 8, 10, 9, 11); void setup() { stepper.setMaxSpeed(500); // полушагов в секунду — комфортный потолок 28BYJ-48 stepper.setAcceleration(250); // полушагов в секунду за секунду stepper.moveTo(4096); // ровно один оборот вала} void loop() { stepper.run(); // выдал шаг — и сразу вернулся в loop() // сюда ставят кнопки, датчики и Serial: loop() не блокируется}Число 4096 в moveTo() — это полушаги: редуктор 64:1 умножается на 32 полушага ротора, и вся математика сводится к счёту до оборота. Заданные 500 полушагов в секунду дают около 7 оборотов в минуту — медленно, зато с полным моментом и без срывов; физический потолок 28BYJ-48 на 5 В — 10-15 об/мин, выше вал срывает шаги. Ждёте быстрее? Это уже территория NEMA17 с драйвером STEP/DIR — про схему, ток по Vref и микрошаги у нас есть отдельный урок про подключение A4988.
NEMA17 через A4988: режим DRIVER и микрошаги
У NEMA17 платы ULN2003 нет — обмотками распоряжается драйвер: Arduino отдаёт ему только импульсы STEP и направление DIR, а делением шага и током занимается железо. Для таких схем у AccelStepper есть режим DRIVER, и вся разница — в единицах: при перемычках на деление 1/16 один оборот вала равен 200 × 16 = 3200 «шагов» в счёте библиотеки, поэтому setMaxSpeed(800) означает 800 микрошагов в секунду, то есть 15 об/мин. Забыли про множитель — и мотор едет в шестнадцать раз медленнее ожидаемого, а счётчик позиций врёт на все микрошаги сразу.
#include <AccelStepper.h> // DRIVER — драйвер сам считает микрошаги, Arduino отдаёт только STEP и DIRAccelStepper motor(AccelStepper::DRIVER, 3, 4); void setup() { motor.setMaxSpeed(800); // микрошагов в секунду при делении 1/16 motor.setAcceleration(400); motor.moveTo(3200); // один оборот NEMA17: 200 шагов × 16 микрошагов} void loop() { motor.run(); if (motor.distanceToGo() == 0) { motor.moveTo(-motor.currentPosition()); // доехали — разворачиваемся }}run, runSpeed и runToPosition: где блокировка, а где свобода
Три вызова путают чаще всего. run() — основной режим: профиль разгона активен, мотор идёт к цели из moveTo(), а loop() между шагами остаётся свободным. runSpeed() — упрощённый: вал крутится с постоянной скоростью без разгона, счёт дешевле, ниша — вращение без позиционных целей вроде вентилятора или кормушки. А вот runToPosition() — блокирующая обёртка: внутри она крутит run() в цикле до достижения цели и вернёт управление только на позиции, так что место ей в setup(), где механизм приводят в исходную точку. Неблокирующая очередь из нескольких движений собирается на связке moveTo() + run() + distanceToGo(): счётчик добрался до нуля — ставите следующую цель; готовый конечный автомат разобран в уроке про очередь движений.
- Мотор стоит — setMaxSpeed() не вызван: по умолчанию скорость нулевая, и run() молчит, хотя moveTo() уже задан
- Вал гудит и не проворачивается — провода ULN2003 подключены по порядку вместо креста IN1, IN3, IN2, IN4
- Старт с рывком и срывом шагов — setAcceleration() забыта или скорость выше, чем тянет питание драйвера
- Скорость плавает и мотор дёргается — setMaxSpeed()/setAcceleration() вызываются в каждом проходе loop() и пересчитывают профиль заново
- Кнопки «перестали работать» — в loop() попал блокирующий runToPosition(): оставьте его для setup() и первичной парковки