Arduino

Шаговый двигатель: управление через драйвер ULN2003

Введение

В отличие от DC-мотора и серво, шаговый двигатель перемещается дискретными, точно определёнными шагами, что даёт точный контроль положения без необходимости в обратной связи от датчика положения — за это приходится платить более сложной электрической схемой управления через драйвер (обычно ULN2003 в составе типичного комплекта для маленьких шаговых двигателей 28BYJ-48). В статье разберём базовое управление шаговым двигателем через библиотеку Stepper.h.

Концепция

Шаговый двигатель 28BYJ-48 (типичный для стартовых наборов) имеет несколько обмоток, которые драйвер ULN2003 последовательно активирует в определённом порядке для поворота вала на один шаг — библиотека Stepper.h инкапсулирует эту последовательность активации обмоток за простым интерфейсом, принимающим количество шагов для поворота и скорость вращения в оборотах в минуту.

Пример кода

// firmware/src/main.cpp — управление шаговым двигателем через драйвер ULN2003
#include <Stepper.h>

const int STEPS_PER_REVOLUTION = 2048; // для 28BYJ-48 с учётом внутреннего редуктора

Stepper stepperMotor(STEPS_PER_REVOLUTION, 8, 10, 9, 11); // порядок пинов важен для правильной последовательности фаз

void setup()
{
    Serial.begin(9600);
    stepperMotor.setSpeed(10); // оборотов в минуту
    delay(100);
    Serial.println("READY");
}

void loop()
{
    static String inputBuffer = "";
    while (Serial.available() > 0) {
        char c = Serial.read();
        if (c == 'n') {
            processCommand(inputBuffer);
            inputBuffer = "";
        } else if (inputBuffer.length() < 64) {
            inputBuffer += c;
        }
    }
}

void processCommand(String command)
{
    command.trim();
    if (command.startsWith("STEPPER:STEPS:")) {
        // Положительное значение — поворот в одну сторону, отрицательное — в обратную
        const int steps = command.substring(14).toInt();
        stepperMotor.step(steps); // ВНИМАНИЕ: блокирующий вызов на время всего поворота
        Serial.print("ACK:STEPPER:STEPS:");
        Serial.println(steps);
    } else if (command.startsWith("STEPPER:SPEED:")) {
        const int speed = constrain(command.substring(14).toInt(), 1, 15);
        stepperMotor.setSpeed(speed);
        Serial.print("ACK:STEPPER:SPEED:");
        Serial.println(speed);
    }
}

Пояснения к коду

Stepper stepperMotor(STEPS_PER_REVOLUTION, 8, 10, 9, 11) — конструктор библиотеки принимает количество шагов на полный оборот (специфично для конкретной модели двигателя и его внутреннего редуктора, для 28BYJ-48 обычно 2048 при использовании всех четырёх фаз через половинный шаг) и четыре номера пинов, подключённых к драйверу ULN2003 — порядок этих пинов должен точно соответствовать порядку обмоток, иначе двигатель может дёргаться на месте или вращаться рывками вместо плавного движения, что нужно либо проверить по документации конкретного модуля, либо подобрать эмпирически.

Критичная деталь, явно отмеченная в комментарии — stepperMotor.step(steps) является блокирующим вызовом, полностью останавливающим выполнение loop() на всё время, необходимое для совершения запрошенного количества шагов на заданной скорости — в отличие от неблокирующего паттерна управления серво из статьи 286, библиотека Stepper.h в своей стандартной реализации не предоставляет встроенного неблокирующего API, что нужно явно учитывать при проектировании прошивки, объединяющей шаговый двигатель с другими задачами, требующими постоянного внимания (обработка команд, опрос других датчиков).

Подводные камни

  • Блокирующая природа stepperMotor.step() — главное архитектурное отличие от ранее разобранных серво и DC-моторов, и при необходимости одновременно обрабатывать другие задачи во время длительного поворота шагового двигателя (множество шагов на низкой скорости может занять заметное время) нужна либо более продвинутая библиотека с неблокирующим API (например, AccelStepper), либо явная ручная реализация шагового управления через прямое переключение пинов с использованием millis(), аналогично неблокирующим паттернам из предыдущих статей.
  • Недостаточное питание для шагового двигателя через сам Arduino — аналогично предупреждению про DC-моторы и серво, драйвер ULN2003 обычно требует отдельного источника питания для самого двигателя (типично 5В с достаточным током), не идущего напрямую через стабилизатор напряжения самой платы Arduino, который не рассчитан на такую нагрузку.
  • Неверный порядок подключения пинов фаз двигателя к конструктору Stepper проявляется как дрожание двигателя на месте без реального вращения, или вращение с заметными, неравномерными рывками вместо плавного движения — это частая первая проблема при первом подключении шагового двигателя, легко диагностируемая визуально (двигатель явно «пытается» двигаться, но не поворачивается нормально).
  • Накопление погрешности позиционирования при множественных относительных перемещениях (step(positive_value) затем step(negative_value) много раз подряд) без периодической явной привязки к абсолютной нулевой позиции (например, через дополнительный концевой выключатель или оптический датчик начального положения) — поскольку шаговый двигатель сам по себе не имеет обратной связи о реальном текущем положении, для проектов, где важна точная абсолютная позиция в течение долгого времени работы, нужен отдельный механизм периодической калибровки нулевой позиции, не предоставляемый автоматически самой библиотекой управления шагами.