GyverStepper. Обсуждение библиотеки

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
В самом простом виде в прерывании таймера вызывать тики обоих шаговых моторов.
Гм.. Не въехал..
Вот мой обработчик прерывания движка азимута
Код:
ISR(TIMER1_COMPA_vect) {
  bool flag_break = false;
  int32_t check = 0;
  // здесь происходит движение мотора
  // если мотор должен двигаться (true) - ставим новый период таймеру
  if (a_stepper.tickManual()) setPeriod(a_stepper.getPeriod());
  else stopTimer();
  // если нет - останавливаем таймер
  if (stat_hal != digitalRead(AZ_HAL_PIN)) flag_hal = true; // проверка на то, что двигатель действительно крутится (датчик холла)
  check = a_stepper.getCurrent() - start_pos;

  if (abs(check)>41 && flag_hal == false) {  // если мотор не крутится, делаем стоп и записываем текущую позицию в EEPROM
          stopTimer();
          a_stepper.brake();
          a_stepper.setCurrent(a_stepper.getCurrent() -(a_stepper.dir*abs(check)));
          flag_break = true;
          flag_error = true;        
  } else {
    flag_error = false;
  }
 
 
  if (a_stepper.ready() || flag_break == true)
  {
    a_stepper.disable();
    digitalWrite(LED_PIN , LOW);
    cur_pos = a_stepper.pos;
    flag = 0;
    WriteBackup();  // запись текущей позиции в EEPROM
  }
}
Я правильно вас понял, что можно сделать вот так - строки
Код:
if (a_stepper.tickManual()) setPeriod(a_stepper.getPeriod());
  else stopTimer()
заменить на
Код:
if (a_stepper.tickManual())  // stepper azimut
{
   setPeriod(a_stepper.getPeriod()); 
  }
else if (e_stepper.tickManual())  // stepper elevation
{
   setPeriod(e_stepper.getPeriod()); 
}
else 
{
  stopTimer();
}
Как к этому отнесётся таймер, если к нему прилетят ОБА тика ОДНОВРЕМЕННО?
Или во время движения двигателя азимута, прилетит тик старта двигателя элевации.
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,281
1,306
Москва
Таймер останавливать не надо. Пусть всегда идет.
Я могу путать, давно не следил за этой библиотекой, но в самом тике есть проверка надо дергать мотором или нет, т.е. условно можно сдлеть так:
C++:
ISR(TIMER1_COMPA_vect) {  
a_stepper.tick();
b_stepper.tick();
}
Тут надо разделить понятия таймер и тики мотора - это два независимых процесса, так во всяком случае раньше было, как сейчас я не знаю, так что вполне могу ошибаться.
Попробую объяснить.
Как было раньше: тик мотора должен вызываться как можно чаще. Сама функция тика следит за временем и если с момента последнего тика проходило достаточно времени до следующего тика, то выполнялся степ мотора, если нет, то просто выход из функции. Там же, внутри , реализованы были ускорения и торможения. Т.е. если грубо тик говорит объекту мотора "дерни пин степ, если надо" и все.
Тоже самое будет для другого мотора - они будут одновременно обрабатывать свои моторы, и независимо друг от друга.
Тут еще надо понимать, что если мотор на полном шаге может дергать пином степ 1000 раз, в сек, то вызывать функцию тика 1000 раз в сек будет мало, т.к. при это не будет плавности разгона и торможение (скорости будут примерно такие 1000 раз в се, 500 раз в сек, т.к. на второй тик будет рано еще дергать степ, а на второй как раз, а это и будет 500 раз в сек.) а вот если дергать 5000 раз в сек, то будет уже что то приличное.

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

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
@Старик Похабыч,
Я правильно понимаю, что на одном таймере управлять двумя ШД не получится?
И какое же тогда решение может быть?
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,281
1,306
Москва
Если для управления ускорением и торможением мотора менять длительность таймера, то да, будет 1 тайме - 1 мотор.
Если просто дергать тик мотора, а ускорение и торможение повесить на библиотеку мотра, то можно подключить больше 1 мотора, 2, 3, точно потянет.
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
Если просто дергать тик мотора,
А где это делать? В обработчике прерывания таймера, как вы предлагали выше, или где-то в loop?
И где и как тогда реализовать отслеживание сигнала с датчика холла? Вернее - с двух датчиков.
Это принципиально, т.к. датчика положения выходного вала мотора у меня нет.
Азимут и элевация тупо запоминаются в EEPROM.
а ускорение и торможение повесить на библиотеку мотра,
А это как реализовать? И чем сиё может грозить? Потери плавности движения не будет?

P.S. А может всё-таки возможно как-то выкрутиться в "моём" варианте? Как-то сделать "очередь" для моторов.
Мне не принципиально иметь одновременное вращение обоих моторов. Несколько секунд могу и подождать.

P.P.S. Случаем, вы не про этот вариант из примеров библиотеки говорите? -
Код:
// крутим мотор туда-сюда плавно с ускорением
// для синхронного многоосевого движения используй встроенный планировщик траекторий
// папка с примерами Planner и Planner2

#include "GyverStepper.h"
// подключим три мотора
// у первого и второго управление EN не подключаем
GStepper stepper1(100, 2, 3);
GStepper stepper2(100, 4, 5);
GStepper stepper3(100, 6, 7, 8);

void setup() {
  // мотор 1 просто вращается
  stepper1.setRunMode(KEEP_SPEED);
  stepper1.setSpeed(300);

  // мотор 2 будет делать sweep по проверке tick
  stepper2.setRunMode(FOLLOW_POS);
  stepper2.setMaxSpeed(1000);
  stepper2.setAcceleration(300);

  // мотор 3 будет перемещаться на случайную позицию
  stepper3.setRunMode(FOLLOW_POS);
  stepper3.setMaxSpeed(1000);
  stepper3.setAcceleration(300);
  stepper3.autoPower(true);
  stepper3.enable();
}

void loop() {
  // первый мотор
  stepper1.tick();
 
  // второй крутим туды-сюды (-1000, 1000)
  if (!stepper2.tick()) {
    static bool dir;
    dir = !dir;
    stepper2.setTarget(dir ? -1000 : 1000);
  }

  // третий по таймеру
  // будет отключаться при остановке
  stepper3.tick();
  static uint32_t tmr;
  if (millis() - tmr > 5000) {   // каждые 5 секунд
    tmr = millis();
    stepper3.setTarget(random(0, 2000));  // рандом 0-2000
  }
}
Да, и ещё - может мне стОит прикрутить в свой проект что-то из GPlanner2?
Правда, пока не вкуриваю, что вообще такое "многоосевой станок" и похож ли он на мой "станок". :)
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,281
1,306
Москва
1) Пример похож на то что я описывал. В нем вместо прерывания по таймеру сам цикл loop , пустой цикл может крутиться с частотой 200кГц.

2) Датчики холла я бы считывал по прерыванию на пинах 2 и 3 (аппаратно)

3) Если одновременное движение двух моторов не требуется, то можно и поочередно делать движение моторов. Будет что то типа этого в прервании:
если положение мотора 1 не соотв движению то
{
шевелить мотором номер 1 с нужным ускорением
}
иначе
{
если положение мотора 2 не соотв движению то
{
шевелить мотором номер 2 с нужным ускорением
}
}
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
1) Пример похож на то что я описывал. В нем вместо прерывания по таймеру сам цикл loop , пустой цикл может крутиться с частотой 200кГц.
Увы, loop совсем не пустой.. В нём кнопки, энкодер, дисплей, serial для приёма данных от Orbitron.
Сильно сомневаюсь, что всё это не будет тормозить движки..
2) Датчики холла я бы считывал по прерыванию на пинах 2 и 3 (аппаратно)
И тут "в пролёте".. На этих пинах энкодер на EncButton. Датчики холла на A0-A1. "Цифровых" пинов не хватило на всё..
Даже D6-D7 задействованы для прерывания компаратора (сохранение позиции при аварийном отключении питания при движении моторов).
3) Если одновременное движение двух моторов не требуется, то можно и поочередно делать движение моторов. Будет что то типа этого в прервании:
если положение мотора 1 не соотв движению то
{
шевелить мотором номер 1 с нужным ускорением
}
иначе
{
если положение мотора 2 не соотв движению то
{
шевелить мотором номер 2 с нужным ускорением
}
}
Чёт плохо воспринимается на родном языке.. :)
Это вот так? -
Код:
if (a_stepper.tickManual())
{
    setPeriod(a_stepper.getPeriod());
}
else
{
    if (e_stepper.tickManual())  setPeriod(e_stepper.getPeriod());
}
Даже лучше, наверное, вот так -
Код:
 if (a_stepper.tickManual()) 
  { 
    setPeriod(a_stepper.getPeriod());
  }
  else if (e_stepper.tickManual()) 
  {
    setPeriod(e_stepper.getPeriod());
  }
  else 
  {
    stopTimer();
  }
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
Упс... Забыл один интересный момент.
Если во время движения мотора азимута прилетит старт на мотор элевации, то попав в прерывание этот "старт" тупо погибнет..
Ну и наоборот тоже самое..
Надо изобретать какой-то "буфер" для хранения "отложенных стартов"..
 

Старик Похабыч

★★★★★★★
14 Авг 2019
4,281
1,306
Москва
Так то прерывания можно повесить на любые пины, но обработка их будет чуть сложнее, чем аттачинтеррапт - они будут вызываться только на изменение состояния пина. Если считывать датчик холла в прерывании по таймеру , то можно потерять в скорости перемещения. Но не думаю, что тут очень важно незначительное снижение скорости.

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

VAF

✩✩✩✩✩✩✩
15 Июл 2023
50
1
Москва
Если вы используете двухядерную плату, то наиболее простое решение разделить моторы по ядрам. Я так резделил шаговый мотор и Енкодер. Все получилось. Для системы с раздельным управление двух независимых объектов - это логично.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@KovAlex, Подскажите по двигателю - сколько там шагов на оборот? Описание на Али об этом умолчало. Редукцию написали, а шаги нет. И еще - какой у него крутящий момент? И что за 4 провода? На картинке подписаны иероглифами, я с китайским не дружу.
 

vortigont

★★★★★★✩
24 Апр 2020
1,021
542
Saint-Petersburg, Russia
Увы.. Одноядрёная Arduino Nano...
не нужен 2х ядерный процессор, нужно учиться разбивать задачи на мелкие неблокирующие кусочки.
Вам даже таймер не нужен с прерываниями, ибо вот так делать нельзя!

C++:
ISR(TIMER1_COMPA_vect) {
  if (stat_hal != digitalRead(AZ_HAL_PIN)) flag_hal = true; // проверка на то, что двигатель действительно крутится (датчик холла)
  check = a_stepper.getCurrent() - start_pos;

  if (abs(check)>41 && flag_hal == false) {  // если мотор не крутится, делаем стоп и записываем текущую позицию в EEPROM
  }
 
  if (a_stepper.ready() || flag_break == true)
  {
    a_stepper.disable();
    digitalWrite(LED_PIN , LOW);
    WriteBackup();  // запись текущей позиции в EEPROM
  }
}
все эти чтения пинов и записи в еепром в обработчике прерываний делать НЕЛЬЗЯ! Дабы потом не задаваться вопросом "а что будет если прилетит новое прерывание пока я все еще туплю и пытаюсь что-то там доделать в предыдущем".

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

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
Вот пример как можно управлять пятью моторами
Проект интересный, но он имеет одно существенное отличие - в нём используются серво-двигатели. В моём - шаговые...
Почти на 100% уверен, что если не использовать таймер, а крутить всё (моторы, дисплей, клавиатуру, энкодер и опрос датчиков) в одном loop,
то моторы будут банально дёргаться.
Т.е. ни о каком плавном разгоне/останове, да и собственно ровном движении речи быть не может.

Поправьте меня, если ошибаюсь.

P.S. Да, я согласен, что нагружать прерывания всяческими "допами" - плохое решение, но по-другому пока не получается..
Кстати, сейчас как раз обкатываю этот вариант (с "монструозным" обработчиком прерывания). Пока серьёзных замечаний нет..
Но всё же буду потихоньку пытаться выбрасывать оттуда всё, что не связано с работой таймера.
К сожалению, я довольно слабый программист, поэтому приходится тратить много времени на правку несуразностей.
Да и правильное решение найти мне не всегда по силам.. :(
 
Изменено:

vortigont

★★★★★★✩
24 Апр 2020
1,021
542
Saint-Petersburg, Russia
нагружать прерывания "допами" - плохое решение, но по-другому пока не получается. Кстати, сейчас как раз обкатываю этот вариант (с "монструозным" обработчиком прерывания).
Ну если устраивает заведомо ущербный подход - дерзайте, что можно тут сказать. Таймер и прерывания это не "серебрянная пуля".
С какой частотой у вас идут импульсы на моторы на максимальной скорости и на какую частоту настроен таймер? Судя по описанию на али моторчики тянут около 3 кГц, что не так уж и много. Но с плавным стартом я не знаю как вы выкручиваетесь. Вы так "допишетесь" до того что у вас обработчик прерываний от таймера иногда будет не успевать за тиками этого таймера и вы будете либо пропускать "шаги" либо у вас будет лагать остальная периферия. Хотя с таким понижающим редуктором 1:144 это врядли будет заметно, что через луп делать, что через пропущенные прерывания. Хороший код будет и с лупом нормально работать, плохому никакие прерывания не помогут. А потом из этого возникают безумные идеи про "одно ядро процессора на каждый мотор".
Шаги мотора вы всё равно не считаете, точность вам не нужна у вас редуктор - используйте ШИМ и не придется писать ненужности в обработчике прерываний от таймера. Управляйте только положеним и оборотами от сенсоров, для этого вполне будет достаточно ленивого опроса пинов.
Для еще более нормального управления несколькими шаговыми моторами с плавными разгонами и прочим нужно взять нормальный контроллер, у которого есть выделенный ШИМ движёк, который умеет сам менять скважность/частоту.
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
С какой частотой у вас идут импульсы на моторы на максимальной скорости и на какую частоту настроен таймер?
Если честно, понятия не имею. Этим занимается Gyver Stepper.
Но с плавным стартом я не знаю как вы выкручиваетесь.
Аналогично
Вы так "допишетесь" до того что у вас обработчик прерываний от таймера
Сейчас там собственно ничего и не осталось
Код:
ISR(TIMER1_COMPA_vect) {
  if (a_stepper.tickManual()) 
  { 
    setPeriod(a_stepper.getPeriod());
  }
  else if (e_stepper.tickManual()) 
  {
    setPeriod(e_stepper.getPeriod());
  }
  else 
  {
    stopTimer();
  } 
}
Одновременно два старта не передаю. Если один из движков занят, второй ожидает в очереди. Вообщем - кто первый, того и тапки. :)
Шаги мотора вы всё равно не считаете,
Их считает библиотека.
А я их забираю в виде градусов и сохраняю в EEPROM по каждому достижению нужной позиции.
точность вам не нужна
Нууу.. Смотря что подразумевать под этим.
Конечно, доли градуса и даже 1-2 градуса на выходном валу, в моём случае, погоды не сделают. Но 5 градусов всё же надо.
Управляйте только положеним и оборотами от сенсоров
Нет сенсоров! Только датчик Холла в моторчике. По нему позицию не особо вычислишь..
Его я использую в качестве "контроллёра" движения двигателя. Если импульсов нет, зачит что-то пошло не так.
Останавливаю движение, возвращаю в stepper позицию, на которой импульсы пропали и пишу в EEPROM.
Как-то так вкратце.
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
Возник ещё такой вопрос - возможно ли назначить одни и те же пины (STEP, DIR и ENABLE) для управления двумя драйверами (А4988) моторов?
Т.е. вот так -
Код:
GStepper2<STEPPER2WIRE> a_stepper(5760, 11, 10, 12);
GStepper2<STEPPER2WIRE> e_stepper(5760, 11, 10, 12);
Конфликта в GStepper2 не получится?
А собственно драйверы подключить через мультиплексор. Например, CD4053. (Schematic_Step-motor-rotator1)
Естественно, моторы одновременно работать НЕ будут.

Или ещё вариант - пины step и dir на драйверах запараллелить, а управлять только сигналом enable? (Schematic_Step-motor-rotator_2)
Вот так -
Код:
GStepper2<STEPPER2WIRE> a_stepper(5760, 5, 8, 9);
GStepper2<STEPPER2WIRE> e_stepper(5760, 5, 8, 10);
Schematic_Step-motor-rotator1.pngSchematic_Step-motor-rotator_2.png

Код:
GStepper2<STEPPER2WIRE> a_stepper(5760, 5, 8, 9);
GStepper2<STEPPER2WIRE> e_stepper(5760, 5, 8, 10);
Предыдущая "копия" GStepper держит "свой" DIR.
И получается так, что если предыдущее направление было "по часовой", а текущее "против", текущий поворот выполняется в ложном направлении..
Никак не могу одолеть.. :(
Пробовал играться stepper.dir, но похоже, он только для чтения.
Пробовал читать пин DIR и если там "0", записывать "1". Предположил, что активный драйвер(gstepper) не может "преодолеть 0", но увы - тоже не прокатило..
Пробовал играться stepper.reverse(false/true), но так и не понял, что эта функция делает. Результат тоже нулевой..

Реально ли как-то преодолеть этот момент? Или без правки gstepper не получится?
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,281
1,306
Москва
После того как переключили EN попробуйте уже с нужной копией GStepper 2 раза дернуть DIR , это должно вернуть последнее нужное направление.
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
попробуйте уже с нужной копией GStepper 2 раза дернуть DIR
Не понял.. Как дёрнуть DIR? Так что ли?
void stepper_turn(int st) {
if (st == 0 && a_stepper.getStatus() == 0 && TruAzim != ComAzim)
{
a_stepper.enable();
a_stepper.dir = a_stepper.dir;
a_stepper.dir = a_stepper.dir;

a_flag_hal = false;
a_stepper.setTargetDeg(ComAzim*-1,ABSOLUTE);
az_cur_pos = a_stepper.getCurrent();
az_start_pos = az_cur_pos;
az_stat_hal = digitalRead(AZ_HAL_PIN);
setPeriod(a_stepper.getPeriod());
startTimer();
}
 

KovAlex

✩✩✩✩✩✩✩
9 Фев 2022
25
0
a_stepper.dir =~a_stepper.dir;
Не прокатило.. :( Всё также "сшибается", если на одном из моторов меняется "направление" (уменьшить/увеличить).
Видимо, в библиотеке dir "приколочен" к пину и в моём случае он получается "общий" для всех "клонов".
 
Изменено:

Старик Похабыч

★★★★★★★
14 Авг 2019
4,281
1,306
Москва
Библиотека может считывать состояние пина перед изменением, но это было странно. Тогда можно попробовать сохранить состояние направления для каждого мотора и потом его восстановить.