Урок 29. StepMotor — библиотека управления шаговыми двигателями в системе Ардуино. Библиотека прерывания по таймеру 1 TimerOne.

Шаговый привод

В уроке рассказываю о моей библиотеке StepMotor, предназначенной для управления униполярными и биполярными шаговыми двигателями.

Предыдущий урок     Список уроков     Следующий урок

Предыдущий урок был посвящен подключению униполярных шаговых двигателей к плате Ардуино и стандартной библиотеке Stepper.

 

На мой взгляд, библиотека Stepper может быть использована только для демонстрации и проверки работы двигателей. В практических приложениях применение этой библиотеки крайне ограничено из-за ряда недостатков:

  • при управлении двигателем функции Stepper подвешивают программу, забирают все вычислительные ресурсы микроконтроллера;
  • в один момент времени может работать только один двигатель;
  • не поддерживаются все основные режимы коммутации обмоток;
  •  ограничены функциональные возможности.

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

 

StepMotor  - библиотека управления шаговыми двигателями в системе Ардуино.

От стандартной библиотеки Stepper мою библиотеку отличает:

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

Загрузить библиотеку StepMotor.h можно по этой ссылке. Как установить написано в нескольких предыдущих уроках, например, в уроке 9.

Описание класса StepMotor.

Я привожу только public методы.

class StepMotor {

  public:
    StepMotor(byte pinA, byte pinB, byte pinC, byte pinD); // конструктор
    void  control();  // управление, метод должен вызываться регулярно с максимальной частотой коммутации фаз
    void  step(int steps);  // инициирует поворот двигателя на заданное число шагов
    void  setMode(byte stepMode, boolean fixStop);  // задает режимы коммутации фаз и остановки
    void  setDivider(int divider);  // установка делителя частоты для коммутации фаз
    int readSteps();  // чтение оставшихся шагов
}

Описание методов класса StepMotor.

void  control()

Метод должен вызываться регулярно в параллельном процессе. Частота вызова метода определяет частоту коммутации фаз двигателя. Делитель частоты, устанавливаемый функцией setDivider, делит именно частоту вызова control().

// обработчик прерывания
void  timerInterrupt() {
  myMotor.control(); // управление двигателем
}

void  step(int steps)

Инициирует поворот двигателя на заданное число шагов. При работе двигателя в полу шаговом режиме имеется в виду не физические шаги двигателя, а полушаги. Аргумент с положительным значением вызывает вращение против часовой стрелки, отрицательный аргумент приводит к вращению двигателя по часовой стрелке.

Запустив вращение двигателя функцией step(), например

myMotor.step(400);  // сделать 400 шагов против часовой стрелки

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

myMotor.step(0);  // остановить двигатель

Можно задать новое число шагов, не дожидаясь остановки двигателя. Например, для непрерывного вращения следует периодически посылать команду с большим числом шагов

myMotor.step(32000);  // постоянное вращение

О том, что двигатель остановился, программа может узнать с помощью функции  readSteps().

void  setMode(byte stepMode, boolean fixStop)

Метод задает режим коммутации фаз двигателя и состояние остановленного двигателя.

stepMode – режим управления двигателем.

Значение stepMode Режим
0 Шаговый
1 Полу шаговый
2 Между шаговый

О режимах коммутации  обмоток шаговых двигателей можно посмотреть по этой ссылке.

fixStop – определяет режим остановленного двигателя.

Значение fixStop Состояние двигателя в остановленном состоянии
false С обмоток снимается напряжение, двигатель разблокирован
true Через обмотки продолжает течь ток удержания, ротор находится в фиксированном положении

myMotor.setMode(0, false);  // шаговый режим, без фиксации при остановке

myMotor.setMode(1, true);  // полу  шаговый режим, с фиксацией ротора при остановке

void  setDivider(int divider)

Метод задает коэффициент деления частоты вызова функции control(), а значит, определяет скорость вращения двигателя. Для расчетов можно использовать следующую формулу:

Rpm = 60 000  /   ( divider  * Tcontrol * Nдвигателя  )

  • Rpm – скорость вращения в оборотах в минуту;
  • Tcontrol – период вызова метода control() в мс;
  • Nдвигателя – число шагов двигателя на полный оборот.

Для полу шагового режима формула несколько меняется.

Rpm = 30 000  /   ( divider  * Tcontrol * Nдвигателя  ).

Например,

myMotor.setDivider(21);     // делитель частоты 21 (при прерывании 1 мс период коммутации фаз 21 мс)

означает, что фазы будут коммутироваться через каждые 21 мс. Это соответствует скорости вращения:

Rpm = 60 000 / ( 21 * 1 * 48 ) = 59,5 об./мин или примерно один оборот в сек.

int readSteps()

Метод позволяет узнать количество шагов, оставшихся до остановки двигателя. Если он возвращает 0, то это означает, что двигатель остановился.

if (myMotor.readSteps() == 0)  {

// двигатель остановился

}

 

Примеры использования библиотеки StepMotor.

Библиотеку необходимо загрузить и установить (можно посмотреть в уроке 9).

Для проверки примеров я использовал драйвер и двигатель PM35S-048 из предыдущего урока. У двигателя 48 шагов на оборот.

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

В следующем уроке подключу через этот же драйвер более мощный двигатель.

Вот пример скетча программы, которая постоянно вращает двигатель.

// программа управления шаговым двигателем с помощью библиотеки StepMotor
// двигатель вращается постоянно против часовой стрелке со скоростью 60 об. в мин
 
#include <MsTimer2.h>
#include <StepMotor.h>

StepMotor myMotor(10, 11, 12, 13);  // создаем объект типа StepMotor, задаем выводы для фаз

void setup() {
  MsTimer2::set(1, timerInterrupt); // задаем период прерывания по таймеру 1 мс
  MsTimer2::start();               // разрешаем прерывание по таймеру
  myMotor.setMode(0, false);  // шаговый режим, без фиксации при остановке
  myMotor.setDivider(21);     // делитель частоты 21 (при прерывании 1 мс период коммутации фаз 21 мс)
}

void loop() {
  myMotor.step(1000);
}

//-------------------------------------- обработчик прерывания 1 мс
void  timerInterrupt() {
  myMotor.control(); // управвление двигателем
}

В цикле loop() надо периодически вызывать метод step() не позволяя, двигателю остановиться. Если число шагов в аргументе функции step() задавать большим, например,  30000, то вызывать метод достаточно раз в несколько секунд. Все остальное время можно использовать для других задач.

Вот скетч аналога программы из предыдущего урока. Двигатель делает 5 оборотов против часовой стрелки, затем пауза 1 сек. Еще 5 оборотов по часовой стрелки, пауза 1 сек и так в бесконечном цикле.

// программа управления шаговым двигателем с помощью библиотеки StepMotor
// двигатель вращается против часовой срелки 5 оборотов, пауза 1 сек,
// 5 оборотов по часовой стрелке, пауза 1 сек, и так в бесконечном цикле
#include <MsTimer2.h>
#include <StepMotor.h>

StepMotor myMotor(10, 11, 12, 13);  // создаем объект типа StepMotor, задаем выводы для фаз

unsigned int timeCounter; // счетчик времени
byte md;  // режим: 0 - вращение против ч.с., 1 - пауза, 2 - вращение против ч.с., 3 - пауза

void setup() {
  MsTimer2::set(1, timerInterrupt); // задаем период прерывания по таймеру 1 мс
  MsTimer2::start();               // разрешаем прерывание по таймеру
  myMotor.setMode(0, false);  // шаговый режим, без фиксации при остановке
  myMotor.setDivider(21);     // делитель частоты 21 (при прерывании 1 мс период коммутации фаз 21 мс)
  md= 0;  // начальный режим
  myMotor.step(240);  // начальный запуск
}

void loop() {

  // управление вращением двигателя
  if (md == 0)  {
    // пять оборотов против часовой стрелки
    if (myMotor.readSteps() == 0) { md=1; timeCounter=0; }
  }
  else if (md == 1) {
    // пауза 1 сек
    if (timeCounter >= 1000)  { md=2; myMotor.step(-240); }
  }
  else if (md == 2) {
    // пять оборотов по часовой стрелке
    if (myMotor.readSteps() == 0) { md=3; timeCounter=0; }   
  }
  else {
    // пауза 1 сек
    if (timeCounter >= 1000)  { md=0; myMotor.step(240); }   
  }

}

//-------------------------------------- обработчик прерывания 1 мс
void  timerInterrupt() {
  myMotor.control(); // управление двигателем
  timeCounter++; // счетчик времени
}

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

Библиотека StepMotor отсчитывает время переключения фаз от периода вызова функции control(). Функция control() обычно вызывается прерыванием от таймера. Период прерывания определяет скорость вращения двигателя по формуле, описанной выше:

Rpm = 60 000  /   ( divider  * Tcontrol * Nдвигателя  ).

Видно, что зависимость скорости вращения от периода вызова метода control() нелинейная, с гиперболической зависимостью. Например, для двигателя из предыдущего урока (48 шагов на оборот) и периода вызова метода control() 1 мс получим следующие значения скоростей.

divider Скорость вращения, об. / мин
1 1250
2 625
3 417
4 312
5 250
6 208
7 179
8 156
. . . . . .

При малых значениях делителя divider скорость вращения задается со значительным шагом.  При увеличении коэффициента divider шаг уменьшается. Т.е. если необходима плавная регулировка скорости, следует уменьшить период вызова control() и увеличить значение делителя. А библиотека MsTimer2 позволяет формировать прерывание с минимальным периодом 1 мс. Для прерываний с меньшим периодом существует другая библиотека управления таймером.

 

Библиотека TimerOne.

Библиотека использует таймер 1 контроллера Ардуино и позволяет формировать прерывания с периодом от 1 мкс до 8,4 сек. Дискретность установки времени - 1 мкс.

Кроме того она управляет режимами ШИМ на выводах микроконтроллера, но сейчас мы будем использовать TimerOne только для формирования временных интервалов.

Работать с библиотекой достаточно просто. Загрузить ее можно по этой ссылке TimerOne-r11.zip. Как установить, написано в уроке 9.

Методы TimerOne.

void initialize(long microseconds)

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

Аргумент microseconds задает период таймера в мкс. По умолчанию задана 1 сек.

Timer1.initialize(250);  // инициализация таймера 1, период 250 мкс

void setPeriod(long microseconds)

Метод устанавливает период таймера в диапазоне 1 мкс … 8,4 сек.

Timer1. setPeriod (250);  // период 250 мкс

void start()

Метод запускает таймер с нового периода. Следует использовать, если таймер был остановлен.

void stop()

Метод останавливает таймер.

void restart()

Перезапускает таймер с нового периода.

void attachInterrupt(void (*isr)(), long microseconds)

Метод вызывает функцию с заданным периодом прерывания.

Timer1.attachInterrupt(timerInterrupt, 250);  // задаем обработчик прерываний функции timerInterrupt

void detachInterrupt()

Метод запрещает прерывание.

Timer1. detachInterrupt();  // запретить прерывание от таймера 1

unsigned long read()

Метод позволяет считать текущее время таймера.

void pwm(char pin, int duty, long microseconds)

Генерирует сигнал ШИМ на выводе, заданном аргументом pin. На Ардуино могут использоваться выводы 9 и 10. Скважность задается аргументом duty в диапазоне 0 … 1023.

void setPwmDuty(char pin, int duty)

Задает значение ШИМ на выводе.

void disablePwm(char pin)

Выключает режим ШИМ для вывода.

С использованием библиотеки TimerOne программа постоянного вращения двигателя выглядит так.

// программа управления шаговым двигателем с помощью библиотеки StepMotor
// двигатель вращается постоянно против часовой стрелке со скоростью 60 об. в мин
 
#include <TimerOne.h>
#include <StepMotor.h>

StepMotor myMotor(10, 11, 12, 13);  // создаем объект типа StepMotor, задаем выводы для фаз

void setup() {
  Timer1.initialize(250);  // инициализация таймера 1, период 250 мкс
  Timer1.attachInterrupt(timerInterrupt, 250);  // задаем обработчик прерываний
  myMotor.setMode(0, false);  // шаговый режим, без фиксации при остановке
  myMotor.setDivider(83);     // делитель частоты 83
}

void loop() {
  myMotor.step(1000);
}

//-------------------------------------- обработчик прерывания 1 мс
void  timerInterrupt() {
  myMotor.control(); // управление двигателем
}

Время периода прерывания по таймеру нельзя задавать слишком маленьким, иначе программа будет заниматься только обработкой прерываний. Для Arduino UNO R3 в простых приложениях я бы ограничил его значением 100 мкс. При более сложных задачах лучше период таймера увеличить.

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

 

В нескольких последующих уроках будем использовать библиотеку StepMotor для управления как униполярными, так и биполярными шаговыми двигателями. В следующем уроке напишем программу для драйвера шагового двигателя на Ардуино и научимся управлять им от компьютера.

Предыдущий урок     Список уроков     Следующий урок

33 комментария на «Урок 29. StepMotor — библиотека управления шаговыми двигателями в системе Ардуино. Библиотека прерывания по таймеру 1 TimerOne.»

  1. У Вас на сайте отличные статьи, материал исчерпывающий и доходчиво преподнесен!

  2. Здравствуйте, а этой библиотекой можно управлять например 4-мя шаговыми двигателями через отдельные драйвера посредством сигналов step и dir?

    • Здравствуйте. Этой библиотекой можно управлять несколькими двигателями, но с простыми драйверами-ключами. Для STEP/DIR драйверов есть другая библиотека уроки 34, 35. Ей также можно управлять несколькими двигателями.

  3. Добрый день, уважаемый автор, Эдуард!
    Добрый день, уважаемый коллеги!

    Во-первых, хочу поздравить всех с праздником и пожелать успехов в творчестве, хорошего самочувствия, оптимизма и всего самого наилучшего! Пусть сбываются все Ваши мечты и планы. А теперь по теме данного урока №29. Спасибо автору Эдуарду за его старание, за его компетенцию и достаточно высокий уровень профессионализма. Но, к моему сожалению, не всё так сладко, Эдуард! Так, например, во-первых, Ваша основная программа, которой Вы посвятили урок №31 не проходит компиляцию. При выполнении компиляции Arduino IDE выдает целую серию ошибок. Отсюда вывод: программа сырая, не проверенная и не прошла отладку автором перед её демонстрацией. Это ни есть хорошо, уважаемый Эдуард! Во-вторых, после прохода по приведенной в уроке ссылке и скачивании основной Вашей программы русские буквы в комментариях — не читаются. Видимо, шрифт выбран какой-то специфический, который вместо руских букв прописывает какие-то иероглифы. В результате текст комментариев — не читаемый и автоматически превращается в муссор. Спасибо за понимание! Буду благодарен автору урока Эдуарду, если он услышит мои пожелания и внесет необходимые корректировки в текст программы и урока. Спасибо всем! С уважением, Валерий.

    • Валерий! Спасибо за добрые слова.
      Что касается остального. Программы тщательно проверены, и не мной одним. Если выдает ошибки при компиляции, значит у вас в системе что-то не так. Может быть не установлена библиотека, может очень старая версия Arduino IDE, может файлы находятся в каталоге названным русскими буквами…
      Иероглифы вместо русских вам показывает браузер. Можете установить другую кодировку и все будет русскими буквами. Но Вы же не в браузере собираитесь программировать. Загрузите файл в Arduino IDE и все будет нормально. Скетчи программ созданы в Arduino IDE и сохраняют исходную кодировку кириллических символов.

      • О, спасибо за такой быстрый ответ. Вот не могу понять, у меня гибридный двигатель 57HS7630A4 (nema 23), драйвер на микросхеме TB6600 и плата arduino Uno R3. Я использую интерфейсы контроллера DIR, Step и GND. Не подскажите, как мне программировать функцию StepMotor myMotor? Там же 4 переменных…

        • Не понял вопрос. STEP, DIR это сигналы? Какие 4 переменные? Что у вас за задача?

          • STEP, DIR — это входы на драйвере, которые соединяются с цифровыми выводами на arduino. У Вас функция StepMotor(byte pinA, byte pinB, byte pinC, byte pinD) задействует 4 пина на ардуино.

          • Анатолий. Посмотрите уроки 34 и 35. Урок 29 и библиотека StepMotor для простых драйверов-ключей. Для STEP/DIR драйверов есть другая библиотека.

    • Извиняюсь, это из стандартной библиотеки. Нашел. Я новичок и не знаю, что стандартно, а что нет. Не плохо было бы упомянуть об этом.

  4. STEP — Вход драйвера для шаговых импульсов (рабочим является передний фронт, длительность > 10мкс)
    DIR — Вход драйвера для управления направлением вращения вала шагового двигателя

    Задача пока запустить гибридный шаговый двигатель.

  5. Здравствуйте Эдуард.
    для знакомства с библиотекой пробую использовать мотор 28BYJ-48-5V
    команда MT.step(10) — работает. но почему то эти 10 шагов двигатель делает со скоростью примерно 3-4 шага в секунду… хотя судя по его характеристикам он должен делать 2048 шагов (оборот) — за 4 секунды.
    не знаете, в чем причина такого его поведения?

    • Здравствуйте! Скорость вращения двигателя зависит от: времени периода прерывания, значения делителя, числа шагов двигателя на оборот, режима переключения фаз. Проверяйте все составляющие.

  6. Извините, а здесь нет ошибки?
    if ( _mode == 0 ) {

    switch (_stepPhase) {
    case 0:
    digitalWrite(_pinA, HIGH);
    digitalWrite(_pinB, LOW);
    digitalWrite(_pinC, LOW);
    digitalWrite(_pinD, LOW);
    break;
    case 1:
    digitalWrite(_pinA, LOW);
    digitalWrite(_pinB, HIGH);
    digitalWrite(_pinC, LOW);
    digitalWrite(_pinD, LOW);
    break;
    case 2:
    digitalWrite(_pinA, LOW);
    digitalWrite(_pinB, LOW);
    digitalWrite(_pinC, HIGH);
    digitalWrite(_pinD, LOW);
    break;
    case 3:
    digitalWrite(_pinA, LOW);
    digitalWrite(_pinB, LOW);
    digitalWrite(_pinC, LOW);
    digitalWrite(_pinD, HIGH);
    break;
    }
    }
    Просто биполярник у меня не заводится с таким кодом.
    Завелся с таким:
    if ( _mode == 0 ) {

    switch (_stepPhase) {
    case 0:
    digitalWrite(_pinA, HIGH);
    digitalWrite(_pinB, LOW);
    digitalWrite(_pinC, HIGH);
    digitalWrite(_pinD, LOW);
    break;
    case 1:
    digitalWrite(_pinA, LOW);
    digitalWrite(_pinB, HIGH);
    digitalWrite(_pinC, HIGH);
    digitalWrite(_pinD, LOW);
    break;
    case 2:
    digitalWrite(_pinA, LOW);
    digitalWrite(_pinB, HIGH);
    digitalWrite(_pinC, LOW);
    digitalWrite(_pinD, HIGH);
    break;
    case 3:
    digitalWrite(_pinA, HIGH);
    digitalWrite(_pinB, LOW);
    digitalWrite(_pinC, LOW);
    digitalWrite(_pinD, HIGH);
    break;
    }
    }
    в соответствии с: http://robocup.idi.ntnu.no/wiki/images/c/c6/PL15S020.pdf

    • Я проверил. Все скетчи из уроков работают. Может у вас схема драйвера другая? Или обмотки двигателя подключены по другому?

      • да вроде обычная схема подключения шагового биполярного двигателя от CD-Rom через L293D.

        • Я не знаю этого драйвера. Значит у него другая логика, чем у L298. На L298 все работает. Посмотрите урок 33. В нем я подключаю биполярный двигатель.

          • http://myrobot.ru/wiki/index.php?n=Projects.MyDRIVER

            Брал схему подключения с этого сайта.

          • Посмотрите внимательно схему подключения в уроке 33. Входы драйвера IN1, IN2, IN3, IN4 подключены соответственно к выводам управления фазами A, C, B, D.

  7. Здравствуйте! Проверьте меня. Все пересмотрел…
    Вот схема как подключено:
    https://cloud.mail.ru/public/5Z2t/hM9MeTETm
    Результат:
    https://cloud.mail.ru/public/KH2Y/E8rjBNaZL
    КОД:
    #include

    StepMotor myMotor(8, 9, 12, 13); // создаем объект типа
    ….
    void setup(){

    MsTimer2::set(1, timerInterrupt); // задаем период прерывания по таймеру 1 мс
    MsTimer2::start(); // разрешаем прерывание по таймеру

    myMotor.setMode(0, false); // шаговый режим, без фиксации при остановке
    myMotor.setDivider(50);
    ….
    }

    void loop(){

    if (myMotor.readSteps() == 0)
    {
    myMotor.setDivider(52-abs(xPosition));
    myMotor.step(-0.5*xPosition);
    }


    }

    void timerInterrupt() {
    myMotor.control(); // управление двигателем
    timeCounter++; // счетчик времени
    }

    • Здравствуйте!
      Попробуйте сначала просто вращать двигатель. Потом движение до заданной точки без изменения скорости.

  8. Эдуард, приветствую! Как правильно вставить в код строки, если нужна остановка двигателя по фотодатчику?

    StepMotor myMotor(10, 11, 12, 13); // создаем объект типа StepMotor, задаем выводы для фаз
    const int pinPhoto = 9; // Назначаем вывод 9 фотодатчику ID103
    const int ENA = 8; // Input ENA,ENB драйвера L298N подключены к выводу 8 Ардуино

    unsigned int timeCounter; // счетчик времени
    byte md; // режим: 0 — вращение против ч.с., 1 — пауза, 2 — вращение по ч.с., 3 — пауза

    void setup() {
    pinMode(pinPhoto, INPUT); // Объявляем назначение PIN-ам

    MsTimer2::set(1, timerInterrupt); // задаем период прерывания по таймеру 1 мс
    MsTimer2::start(); // разрешаем прерывание по таймеру
    myMotor.setMode(2, false); // между шаговый режим, без фиксации при остановке
    myMotor.setDivider(21); // делитель частоты 21 (при прерывании 1 мс период коммутации фаз 21 мс)
    md= 0; // начальный режим
    myMotor.step(576); // начальный запуск

    • Здравствуйте!
      Вы не написали какой активный уровень у датчика. Если 1, то

      void loop() {
      if( digitalRead(pinPhoto) == HIGH) myMotor.step(0);
      else myMotor.step(100);
      }

      Двигатель вращается, если состояние вывода pinPhoto имеет низкий уровень и останавливается, если вывод находится в высоком уровне.

  9. Добрый день, Эдуард! Да, я немного не правильно сформулировал вопрос. Задача простая: открывать и закрывать жалюзи по уровню освещенности ФД. Не суть важно, что на ФД, 0 или 1. Допустим, при «0» мотор крутится по часовой стрелке на 576 оборотов, при «1» — против.

    • Здравствуйте!
      Надо выделить 2 события переход состояния датчика из 0 в 1 и из 1 в 0. И на эти события вызывать myMotor.step(576) и myMotor.step(-576). Если хотите, откройте тему на форуме сайта, я помогу с программой. В комментариях тяжело общаться на объемные темы.

  10. Строку
    const int ENA = 8; // Input ENA,ENB драйвера L298N подключены к выводу 8 Ардуино
    можно закомментировать, она в этом случае не нужна

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *