📍 Управление позицией шагового двигателя
Основы позиционирования
Шаговый двигатель — идеальный выбор для точного позиционирования без обратной связи (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(); // Неблокирующий вызов
}
}
Связанные материалы
- 📚 Микрошаги — Повышение плавности
- 📚 Драйверы шаговых двигателей — TMC2209, A4988
- 📚 NEMA17 — Популярный типоразмер
- 📚 Энкодеры — Датчики положения
- 📚 PID-регулятор — Урок 2.3 — Основы PID
