Плавное увеличение скорости вращения шагового двигателя

laser
Offline
Зарегистрирован: 20.02.2014

Добрый день!

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

Код программы:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <AccelStepper.h>

LiquidCrystal_I2C lcd(0x3F, 16, 2); //SCL - A5, SDA - A4

AccelStepper stepper(1, 0, 1); // 0 - STEP, 1 - DIR

int dSteps[6] = {1, 2, 5, 10, 25, 50}; // Набор шагов для регулировки скорости
int dStep = dSteps[0]; // Начальный шаг регулировки
int SPEED = 200; // Начальная скорость вращения двигателя шаг/с = 60 об/мин
int button = 1021; // Ниодна из кнопок не нажата
int state = 0; // Состояние дисплея: 0 - Standby, 1 - Working
int count = 0; // Счетчик для изменения шага

void setup()
{

  // initialize the LCD
  lcd.begin();
  lcd.clear();

  // Turn on the blacklight and print a message.
  lcd.backlight();
  Standby(); // При запуске на экране отображается набор Standby

  stepper.setMaxSpeed(SPEED);
  stepper.setAcceleration(50);
}

void loop() {
  ButtonRelease();
  if (state == 1) {
    stepper.setSpeed(SPEED);
    stepper.run();
  }
}

void Standby() { // Режим ожидания - шаговик стоит
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("STANDBY");
  lcd.setCursor(0, 1);
  lcd.print("Press START");
  SPEED = 200;
  dStep = dSteps[0];
  count = 0;
}

void Working() { // Режим работы - шаговик вращается
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Speed RPM: ");
  lcd.print(SPEED * 0.3);
  lcd.setCursor(0, 1);
  lcd.print("Step RPM: ");
  lcd.print(dStep * 0.3);
}

void ButtonRelease() // Отслеживание нажатия кнопок
{
  int newButton = GetKeyValue();
  if (button != newButton) {
    button = newButton;
    switch (button) {
      case 1:
        if (state == 0)
        {
          Working();
          state = 1;
        }
        else if (state == 1)
        {
          Standby();
          state = 0;
        }
        break;
      case 2:
        count++;
        if (count > 5) count = 0;
        dStep = dSteps[count];
        Working();
        break;
      case 3:
        SPEED = SPEED + dStep;
        Working();
        break;
      case 4:
        SPEED = SPEED - dStep;
        if (SPEED < 100) SPEED = 100;
        Working();
        break;
    }
  }
}

int GetKeyValue()          // Функция, устраняющая дребезг
{
  static int   count1;
  static int   oldKeyValue; // Переменная для хранения предыдущего значения состояния кнопок
  static int   innerKeyValue;

  // Здесь уже не можем использовать значение АЦП, так как оно постоянно меняется в силу погрешности
  int actualKeyValue = GetButtonNumberByValue(analogRead(0));  // Преобразовываем его в номер кнопки, тем самым убирая погрешность

  if (innerKeyValue != actualKeyValue) {  // Пришло значение отличное от предыдущего
    count1 = 0;                            // Все обнуляем и начинаем считать заново
    innerKeyValue = actualKeyValue;       // Запоминаем новое значение
  }
  else {
    count1 += 1;                           // Увеличиваем счетчик
  }

  if ((count1 >= 10) && (actualKeyValue != oldKeyValue)) {
    oldKeyValue = actualKeyValue;         // Запоминаем новое значение
  }
  return    oldKeyValue;
}
// 1 - 746; 2 - 514; 3 - 343; 4 - 146; 5 - 1; покой - 1021
int GetButtonNumberByValue(int value)    // Новая функция по преобразованию кода нажатой кнопки в её номер
{
  //int values[6] = {1023, 724, 482, 133, 309, 0};
  int values[6] = {1021, 746, 514, 146, 343, 0};
  int error     = 100;                     // Величина отклонения от значений - погрешность
  for (int i = 0; i <= 5; i++) {
    // Если значение в заданном диапазоне values[i]+/-error - считаем, что кнопка определена
    if (value <= values[i] + error && value >= values[i] - error) return i;
  }
  return -1;                              // Значение не принадлежит заданному диапазону
}
andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

А если отключить функцию Working() эффект сохраняется?

laser
Offline
Зарегистрирован: 20.02.2014

andriano пишет:

А если отключить функцию Working() эффект сохраняется?

Закомментировал Working в функции ButtonRelease() и остановок больше нет.

Но как тогда сделать, чтобы на lcd обновлялась информация о текущей скорости?

sadman41
Offline
Зарегистрирован: 19.10.2016

Как минимум - не делать clear, не перепечатывать "Speed RPM" и пр, выводить только цифры в смещенную позицию

laser
Offline
Зарегистрирован: 20.02.2014

sadman41 пишет:

Как минимум - не делать clear, не перепечатывать "Speed RPM" и пр, выводить только цифры в смещенную позицию

то есть с помощью setCursor вставать на место печатаемых цифр?

sadman41
Offline
Зарегистрирован: 19.10.2016

Типа того. И после цифр еще пару пробелов печатать.

laser
Offline
Зарегистрирован: 20.02.2014

sadman41 пишет:

Типа того. И после цифр еще пару пробелов печатать.

сделал так:

void ButtonRelease() // Отслеживание нажатия кнопок
{
  int newButton = GetKeyValue();
  if (button != newButton) {
    button = newButton;
    switch (button) {
      case 1:
        if (state == 0)
        {
          Working();
          state = 1;
        }
        else if (state == 1)
        {
          Standby();
          state = 0;
        }
        break;
      case 2:
        count++;
        if (count > 5) count = 0;
        dStep = dSteps[count];
        lcd.setCursor(10, 1);
        lcd.print(dStep * 0.3);
        lcd.print("  ");
        break;
      case 3:
        SPEED = SPEED + dStep;
        lcd.setCursor(11, 0);
        lcd.print(SPEED * 0.3);
        lcd.print("  ");
        break;
      case 4:
        SPEED = SPEED - dStep;
        if (SPEED < 100) SPEED = 100;
        lcd.setCursor(11, 0);
        lcd.print(SPEED * 0.3);
        lcd.print("  ");
        break;
    }
  }
}

Но все равно дергается при нажатии. Если в кейсах убрать строки с lcd, то тогда не дергается, но и на экран не вывести текущие параметры

sadman41
Offline
Зарегистрирован: 19.10.2016

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

Суть-то проблемы вы поняли? Без этого двигаться дальше вы не сумеете.

...и не использовали бы вы выходы 0/1 для степпера. Вот сейчас понадобится выводить отладочные сообщения в Serial - что вы получите?

laser
Offline
Зарегистрирован: 20.02.2014

sadman41 пишет:

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

Суть-то проблемы вы поняли? Без этого двигаться дальше вы не сумеете.

...и не использовали бы вы выходы 0/1 для степпера. Вот сейчас понадобится выводить отладочные сообщения в Serial - что вы получите?

Поможет ли библиотека ArduinoThread?

sadman41
Offline
Зарегистрирован: 19.10.2016

Поможет в чем? Вы суть проблемы поняли? Если нет, то можете прикладывать все библиотеки подряд. И подорожник еще.

laser
Offline
Зарегистрирован: 20.02.2014

sadman41 пишет:

Поможет в чем? Вы суть проблемы поняли? Если нет, то можете прикладывать все библиотеки подряд. И подорожник еще.

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Наполовину правильно поняли. Поставьте вместо вывода на дисплей простой delay(500); и понаблюдайте.

laser
Offline
Зарегистрирован: 20.02.2014

sadman41 пишет:

Наполовину правильно поняли. Поставьте вместо вывода на дисплей простой delay(500); и понаблюдайте.

По сути происходит то же самое, что и при выводе на дисплей - задержка.

sadman41
Offline
Зарегистрирован: 19.10.2016

Ну вот, вывод отсюда какой? Мешает не печать на LCD, а задержка на ее время. Если междушаговый интервал у вас меньше, чем время печати, то хоть тресни - ничего не поделаете. Т.е. можете, но придется жить без библиотеки, писать общение по I2C вручную... Или как-то делить команды - установка курсора, потом шаг, потом печать куска, потом шаг и т.д.

Надо мерять, сколько занимает вывод в дисплей и пр. и др. Для этого вам Serial на выходах 0/1 и пригодится - будете миллис писать туда перед началом печати на LCD и после, потом сравнивать с междушаговым интервалом.

laser
Offline
Зарегистрирован: 20.02.2014

Понял, буду искать решение проблемы. Спасибо

sadman41
Offline
Зарегистрирован: 19.10.2016

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

laser
Offline
Зарегистрирован: 20.02.2014

От умножения никак не избавиться, по-моему. Ведь на шаговик подается скорость в шаг/с, а на экран выводится в об/мин. Если только не заводить еще 2 переменные, в которых будет производиться перевод.

laser
Offline
Зарегистрирован: 20.02.2014

В силу скудных знаний в области программирования, пришла на ум идея использовать 2 ардуинки, одна по нажатии кнопок будет отрабатывать действия с шаговиком, вторая выводить на экран, благо все кнопки через один пин подключены. Совсем глупо придумал или есть в этом что-то?

vvadim
Offline
Зарегистрирован: 23.05.2012

есть ещё вариант - три дуни.
одна мотор крутит, вторая экран рисует, третья за кнопками следит.

laser
Offline
Зарегистрирован: 20.02.2014

Сарказм?

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

vvadim
Offline
Зарегистрирован: 23.05.2012

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

vvadim
Offline
Зарегистрирован: 23.05.2012

кстати, у вас нет ограничения скорости по верхнему пределу.

для этой библиотеки больше 1000 может двигатель встать)))

laser
Offline
Зарегистрирован: 20.02.2014

vvadim пишет:

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

так прорисовка занимает 2 строки кода и все равно вызывает кратковременную остановку в работе двигателя

а максимальное значение в setup стоит же и выше него двигатель крутить не будет, только на дисплее значение меняться будет (надо поправить это)

sadman41
Offline
Зарегистрирован: 19.10.2016

Такой костыль не поможет?

... 
        lcd.setCursor(10, 1);
        stepper.run()
        lcd.print(dStep * 0.3);
        stepper.run()
        lcd.print("  ");
        stepper.run()
 ...

 

vvadim
Offline
Зарегистрирован: 23.05.2012

упёртый вы однако.

у вас перерисовывается весь дисплей. а нужно только lcd.print(SPEED * 0.3); или lcd.print(dStep * 0.3);

laser
Offline
Зарегистрирован: 20.02.2014

vvadim пишет:

упёртый вы однако.

у вас перерисовывается весь дисплей. а нужно только lcd.print(SPEED * 0.3); или lcd.print(dStep * 0.3);

я в 6-м комментарии приводил переделанный фрагмент кода. там перерисовываются только цифры

vvadim
Offline
Зарегистрирован: 23.05.2012

ладно, если не можете победить - скачайте библиотеку stepperq и будете выводить что захочется.

её автор на форуме ссылку давал на неё, поищите поиском.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Сколько нужно ардуин что бы закрутить  .... шаговик?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

laser пишет:

andriano пишет:

А если отключить функцию Working() эффект сохраняется?

Закомментировал Working в функции ButtonRelease() и остановок больше нет.

Но как тогда сделать, чтобы на lcd обновлялась информация о текущей скорости?

Собственно, я никогда не использовал AccelStepper, поэтому не знаю, как она работает. Поэтому задал вопрос, ответ на который прояснил для меня ситуацию.

В общем, как выяснилось из Вашего ответа, шаговик может сделать шаг только тогда, когда Вы вызываете stepper.run(). Соответственно, интервал между двумя вызовами не должен превышать минимальной длительности шага (оцените - какая Вам нужна).

Как-то мне нужно было выводить на аналогичный дисплей в условиях, когда нельзя было приостанавливать программу (для вашего случая - это как раз минимальная длительность шага) более чем на 0.3 мс. В то же время я выяснил, что один (!) символ выводится на экран целых 1.5 мс. Создал тему:  http://arduino.ru/forum/apparatnye-voprosy/medlennaya-rabota-liquidcrystali2c

и совместными усилиями удалось увеличить скорость вывода символа практически в 10 раз.

С одним символом - все хорошо, но для вывода строки целиком явно недостаточно времени. Написал класс, который принимает строку и через метод аналогичный stepper.run() постепенно выводит ее по одному символу на экран.

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

 

Есть и другой вариант - сделать, чтобы шаги совершались не по вызову .run, а по таймеру. Возможно, это уже реализовано в какой-либо библиотеке для шаговых двигателей.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Чой-то я наверное не понял: что мешает поюзать тот же TimerOne, и ручками шагать по тикам таймера? Скорость вращения настраивать интервалами между тиками таймера, как вариант. И пофиг, чего там куда выводится - по прерыванию работаем.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Как я писал: таймер - один из вариантов.

А уж сам таймер будет выдавать сигнал на предопределенной ноге или делегирует эту возможность ЦПУ посредством прерывания - дело десятое.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

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

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

DetSimen пишет:

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

Деда, а чего там работать, если по STEP/DIR? Тикнул таймер, дёрнули STEP в высокий, подождали 10 микросекунд, дёрнули step в низкий. Перед началом движения DIR в высокий или низкий, в зависимости от направления вращения. После окончания движения - EN в высокий или низкий, в зависимости от того - нужно ли удержание или нет. Короче, не квадратный многочлен, даже близко.

sadman41
Offline
Зарегистрирован: 19.10.2016

10 много даже. Пять - уже с запасом. A4988 требует удержания состояния в обоих положениях на интервал в 1uS, DRV8824 - 1.9uS, TB6600 - 2,2uS.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

sadman41 пишет:

10 много даже. Пять - уже с запасом. A4988 требует удержания состояния в обоих положениях на интервал в 1uS, DRV8824 - 1.9uS, TB6600 - 2,2uS.

Зато надёжа :) На хоббийных применениях скорости шагания не ахти требуются, так что там те 10 микросекунд - как слону известно что, как правило. Но согласен - смело можно уменьшать.

laser
Offline
Зарегистрирован: 20.02.2014

Скорость я планировал менять в большом диапазоне (от 100 до 1000 об/мин), то есть получается для каждой установленной скорости нужно производить пересчет интервала? И в целом работать без библиотек для шаговика, меняя HIGH и LOW на ногах драйвера? И метод работы шаговика закинуть в прерывания? Тогда в loop() у меня будет крутиться только работа с дисплеем. Правильно понимаю идею?

sadman41
Offline
Зарегистрирован: 19.10.2016

Типа того. Только с разгоном-торможением не выйдет так просто, как с библиотекой. Ну, это, конечно если нужна трапеция в графике движения. Если нет - все проще. ...Wire, по-моему в прерывания не лезет, не блокирует?

laser
Offline
Зарегистрирован: 20.02.2014

Wire использует аналоговые выходы, а, как я понял, прерывания только на цифровых работают.

laser
Offline
Зарегистрирован: 20.02.2014

И возник глупый вопрос. Если шаговик работает по прерываниям, смогу ли я его включать выключать по нажатию кнопки, аналогично тому как это у меня было реализовано до этого?

sadman41
Offline
Зарегистрирован: 19.10.2016

laser пишет:

Wire использует аналоговые выходы, а, как я понял, прерывания только на цифровых работают.

Не могу согласиться, однако к теме данный диспут отношения иметь не будет.

sadman41
Offline
Зарегистрирован: 19.10.2016

laser пишет:

И возник глупый вопрос. Если шаговик работает по прерываниям, смогу ли я его включать выключать по нажатию кнопки, аналогично тому как это у меня было реализовано до этого?

Почему нет? В ISR проверяйте - переменная true? Да - шагаем, нет - выпрыгиваем сразу.

laser
Offline
Зарегистрирован: 20.02.2014

По крайней мере i2c плата подключается к ардуине через аналоговые пины

sadman41
Offline
Зарегистрирован: 19.10.2016

laser пишет:

По крайней мере i2c плата подключается к ардуине через аналоговые пины

Просто к этим входам МК может подключаться встроенное АЦП. А может и не подключаться. К другим АЦП не подключается в принципе. К третьим.. ну, которые A6/A7 - только АЦП подключается.

А "аналоговыми"/"цифровыми" их назвали создатели платформы Ардуино. Чисто для начинающих.

laser
Offline
Зарегистрирован: 20.02.2014

И такой вопрос: что мне нужно почитать, чтобы разобраться с реализацией вращения шаговика по такому принципу?

laser
Offline
Зарегистрирован: 20.02.2014

То есть можно как-то переобозначить пины А4/А5 для i2c на пины, например, D4/D5 и на экран будет все также выводиться?

sadman41
Offline
Зарегистрирован: 19.10.2016

Ключевые слова даны: TimerOne, STEP/DIR/ENABLE управление. Ну, и математику вспомнить для пересчета RPM в Delay.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

laser пишет:

То есть можно как-то переобозначить пины А4/А5 для i2c на пины, например, D4/D5 и на экран будет все также выводиться?

для 328 - нельзя переназначить

laser
Offline
Зарегистрирован: 20.02.2014

Понял. Буду разбираться. Спасибо

laser
Offline
Зарегистрирован: 20.02.2014

Значит вариант только один у меня - использовать прерывания на шаговике

sadman41
Offline
Зарегистрирован: 19.10.2016

laser пишет:

То есть можно как-то переобозначить пины А4/А5 для i2c на пины, например, D4/D5 и на экран будет все также выводиться?

Ничего не надо переобозначать. A4 и A5 - это макросы для выходов #18 и #19. На ардуину посмотрите - там D* до 13-го идут, а потом A*, A0 - на самом деле #14, A1 - #15 и т.д.

laser
Offline
Зарегистрирован: 20.02.2014

Просто из #42 сообщения я понял, что можно i2c навесить на другие ноги - и использовать прерывания на нем. Видимо неправильно понял...