Skip to main content

📍 Управление позицией шагового двигателя

Основы позиционирования

Шаговый двигатель — идеальный выбор для точного позиционирования без обратной связи (open-loop).

Почему шаговики?

ПараметрШаговыйDC + энкодерСервопривод
Точность1.8° / 200 шаговЗависит от энкодера0.1°
Цена💵💵💵💵💵💵
Удержание позиции✅ Без тока❌ Нужен ток
Open-loop⚠️

Расчёт перемещения

Шаги → линейное перемещение

Для ходового винта с шагом $p$ (pitch):

$$ \text{Перемещение} = \frac{\text{Шаги} \times p}{\text{Шагов на оборот}} $$

Пример: NEMA17 (200 шагов) + винт T8 (шаг 8 мм):

$$ \text{Перемещение за 1 шаг} = \frac{8\ \text{мм}}{200} = 0.04\ \text{мм} $$$$ \text{Точность} = \frac{0.04}{16} = 0.0025\ \text{мм} = 2.5\ \text{мкм} $$

Алгоритм трапецеидального профиля

Резкий старт/стоп вызывает пропуск шагов. Решение — плавное ускорение:

Скорость
    │      ╱‾‾‾‾‾‾‾‾‾‾‾‾╲
    │     ╱              ╲
    │    ╱                ╲
    │   ╱                  ╲
    │──╱────────────────────╲──► Время
      Разгон  Круиз   Торможение

Код трапецеидального профиля

class TrapezoidalMotion {
  float maxSpeed;      // шагов/сек
  float acceleration;  // шагов/сек²
  
public:
  void moveTo(long targetPosition) {
    long distance = abs(targetPosition - currentPosition);
    
    // Расстояние для разгона до maxSpeed
    float accelDistance = (maxSpeed * maxSpeed) / (2 * acceleration);
    
    if (distance < 2 * accelDistance) {
      // Треугольный профиль (не успеваем набрать maxSpeed)
      accelDistance = distance / 2;
    }
    
    float decelStart = distance - accelDistance;
    
    for (long step = 0; step < distance; step++) {
      float speed;
      
      if (step < accelDistance) {
        // Разгон
        speed = sqrt(2 * acceleration * step);
      } else if (step < decelStart) {
        // Круиз
        speed = maxSpeed;
      } else {
        // Торможение
        speed = sqrt(2 * acceleration * (distance - step));
      }
      
      speed = max(speed, minSpeed);  // Минимальная скорость
      
      doStep();
      delayMicroseconds(1000000 / speed);
    }
  }
};

S-образный профиль (Jerk-limited)

Ещё плавнее — ограничиваем рывок (jerk):

Ускорение
    │    ╭──────╮
    │   ╱        ╲
    │──╱──────────╲──────╱──╮
    │                   ╱    ╲
    │                  ╰──────╯
    └─────────────────────────► Время

Используется в ЧПУ-станках и 3D-принтерах (Marlin, Klipper).


Closed-loop: добавляем энкодер

Для высоконагруженных применений добавляем обратную связь:

             ┌──────────────┐
    Цель ───►│   Контроллер │──► Шаговый
             │   (PID)      │    двигатель
             └──────┬───────┘       │
                    │               │
                    └───── Энкодер ◄┘

Преимущества closed-loop

  • ✅ Обнаружение пропуска шагов
  • ✅ Работа с переменной нагрузкой
  • ✅ Меньший ток (только нужный момент)
  • ✅ Бесшумность

Практический пример: CNC-контроллер

#include <AccelStepper.h>

AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

void setup() {
  stepper.setMaxSpeed(1000);      // шагов/сек
  stepper.setAcceleration(500);   // шагов/сек²
}

void loop() {
  // Команда: переместиться на 10 мм (при шаге 0.04 мм)
  long steps = 10.0 / 0.04;  // 250 шагов
  
  stepper.moveTo(steps);
  
  while (stepper.distanceToGo() != 0) {
    stepper.run();  // Неблокирующий вызов
  }
}

Связанные материалы