Перейти к содержанию
Материалы раздела

Stepper

Модуль stepper управляет драйверами шаговых двигателей по сигналам STEP и DIR. Он поддерживает до восьми двигателей и строит для каждого перемещения симметричный S-образный профиль разгона и торможения с ограничением ускорения и рывка.

Не подключайте двигатель непосредственно к GPIO. Между платой и двигателем обязателен силовой драйвер шагового двигателя. Объедините земли платы и драйвера и проверьте допустимые уровни входов STEP и DIR.

Единицы измерения и положение

Единицу измерения задаёт приложение. Это может быть оборот, миллиметр или градус. Параметр steps_per_unit связывает выбранную единицу с импульсами STEP. Например, для 200 полных шагов на оборот steps_per_unit = 200; при микрошаге 1/16 — 3200.

Модуль работает без обратной связи от вала. getposition() считает выданные импульсы STEP, а не фактические шаги двигателя. Перегрузка, недостаточный ток или слишком резкий профиль могут привести к пропуску шагов, которого программа не обнаружит. Для абсолютного позиционирования используйте концевик, датчик нуля или энкодер.

Подключение двигателя

instance = stepper.attach(direction_pin, step_pin, [steps_per_unit, min_speed, max_speed, max_acceleration, max_jerk])

Создаёт экземпляр шагового двигателя.

Аргументы:

  • direction_pin — выход GPIO для сигнала DIR;
  • step_pin — выход GPIO от 0 до 31 для сигнала STEP; вывод должен поддерживать RMT;
  • steps_per_unit — число импульсов STEP на единицу, по умолчанию 200;
  • min_speed — начальная и конечная скорость, единиц в минуту, по умолчанию 60;
  • max_speed — максимальная скорость, единиц в минуту, по умолчанию 1000;
  • max_acceleration — предельное ускорение, единиц/с², по умолчанию 2;
  • max_jerk — предельный рывок, единиц/с³, по умолчанию 20.

Все значения должны быть конечными. Скорости, steps_per_unit и max_jerk должны быть положительными; max_acceleration может быть равным нулю. max_speed не может быть меньше min_speed. Модуль также проверяет, можно ли представить минимальный и максимальный периоды средствами RMT.

-- Два двигателя с настройками по умолчанию.
x = stepper.attach(pio.GPIO26, pio.GPIO14)
y = stepper.attach(pio.GPIO12, pio.GPIO13)

-- Ось в миллиметрах: 80 шагов двигателя * 16 микрошагов / 8 мм.
z = stepper.attach(pio.GPIO27, pio.GPIO25, 160, 30, 600, 100, 1000)

Экземпляр занимает один канал RMT. В текущем API нет detach(), поэтому не создавайте экземпляры многократно в цикле: за время работы прошивки можно занять все восемь каналов.

Подготовка перемещения

instance:move(distance, [speed, acceleration, jerk])

Рассчитывает будущее перемещение, но не запускает двигатель. После move() нужно вызвать stepper.start() или stepper.startasync().

Аргументы:

  • distance — относительное перемещение; знак задаёт направление;
  • speed — целевая скорость в единицах в минуту, по умолчанию max_speed;
  • acceleration — ускорение в единицах/с², по умолчанию max_acceleration;
  • jerk — рывок в единицах/с³, по умолчанию max_jerk.

Скорость ограничивается диапазоном от min_speed до max_speed. Ускорение и рывок не могут превышать пределы, заданные в attach(). Нулевое ускорение отключает плавный разгон и торможение: импульсы сразу идут с целевой скоростью.

Расстояние округляется вниз до целого числа шагов. Поэтому перемещение меньше 1 / steps_per_unit не выдаст ни одного импульса. Один профиль может содержать не более 16 777 216 шагов — это защитный предел планировщика.

Обычный профиль состоит из семи участков: нарастание ускорения, постоянное ускорение, спад ускорения, постоянная скорость и три симметричных участка торможения. На коротком расстоянии планировщик автоматически уменьшает пиковую скорость; участки постоянного ускорения и постоянной скорости могут исчезнуть. В конце профиль возвращается к min_speed.

move() заранее рассчитывает форму профиля: границы участков, их длительность, число шагов и общее время. Периоды отдельных импульсов вычисляются с опережением фоновой задачей и помещаются в кольцевой буфер RMT. Поэтому модуль не хранит в памяти готовый массив всех импульсов длинного перемещения, но во время движения RMT получает уже рассчитанные ближайшие интервалы.

Нельзя заменить профиль вызовом move(), пока двигатель движется или фоновая задача ещё формирует его импульсы.

x:move(10)                 -- 10 единиц с предельными параметрами
y:move(-5, 300, 1.5, 15)  -- -5 единиц, 300 ед/мин, a=1,5, j=15

Запуск

stepper.start(instance1, [instance2, …])

stepper.start({instance1, instance2, …})

Запускает ранее подготовленные перемещения и блокирует Lua-задачу до их завершения. Каждый переданный экземпляр должен иметь новый профиль move(). После завершения для следующего запуска снова вызовите move(). Если фоновый расчёт не успевает пополнять RMT, выдача импульсов безопасно прекращается, а блокирующий start() возвращает ошибку опустошения буфера вместо продолжения по старым данным.

Если передано несколько двигателей, модуль выбирает естественное время самого медленного профиля и замедляет остальные так, чтобы они закончили одновременно. При очень коротком ходе синхронизированная средняя скорость отдельной оси может оказаться ниже min_speed; модуль отклонит профиль, если требуемый период нельзя представить средствами RMT.

x:move(10)
y:move(5)
stepper.start(x, y)

-- То же самое через таблицу.
x:move(-10)
y:move(-5)
stepper.start({x, y})

stepper.startasync(instance1, [instance2, …])

stepper.startasync({instance1, instance2, …})

Запускает ту же синхронизированную группу, но возвращает управление сразу. Состояние можно проверять через isrunning(). Ошибку, возникшую уже после возврата из startasync(), вернуть в вызывающую Lua-задачу невозможно; раннюю остановку можно заметить по ненулевому остатку getdistance().

Остановка

stepper.stop([instance1, instance2, …])

stepper.stopasync([instance1, instance2, …])

Останавливает перечисленные двигатели. Без аргументов останавливает все работающие двигатели. stop() ждёт завершения фоновой обработки, stopasync() возвращает управление сразу.

Это аварийная остановка без рассчитанного торможения: RMT прекращает выдачу импульсов немедленно, а оставшаяся часть профиля отменяется. Для штатного торможения задавайте конечную точку через move() и дожидайтесь завершения S-профиля.

Состояние и координаты

instance:isrunning()

Возвращает true, пока RMT выдаёт импульсы для экземпляра, иначе false.

instance:getdistance()

Возвращает оставшееся расстояние последнего подготовленного перемещения. Значение уменьшается по реально выданным импульсам STEP, а не по степени заполнения программного буфера. После аварийного stop() показывает неисполненный остаток отменённого профиля.

instance:getposition()

Возвращает программную относительную позицию в выбранных единицах. Позиция изменяется на фронтах STEP и не подтверждает фактическое положение вала.

instance:setposition(position)

Устанавливает программную позицию, например после выхода на датчик нуля. Во время движения вызов отклоняется.

axis = stepper.attach(pio.GPIO26, pio.GPIO14, 3200, 6, 300, 20, 200)

-- После процедуры поиска нуля.
axis:setposition(0)

axis:move(2.5, 120, 10, 100)
stepper.startasync(axis)

while axis:isrunning() do
  print(axis:getposition(), axis:getdistance())
  tmr.delayms(50)
end