Шаговый двигатель TMC2208 не меняет скорость вращения линейно

Это мой первый проект, работающий с шаговыми двигателями, поэтому у меня может быть немного неуверенное представление об электронной стороне проекта. Я пытаюсь создать простое устройство с 4 кнопками, подключенными к драйверу шагового двигателя/двигателю NEMA 17, с возможностью увеличивать/уменьшать частоту вращения, переключать направление вращения двигателя и запускать двигатель, удерживая кнопку. (ЖК-экран также включен для вывода)

Я заметил, что при изменении оборотов моего мотора, т.е. с 3,2 на 3,1 и на 3,0, скорость мотора очень медленная, затем при установке на 3,0 внезапно сильно увеличивается, но снижается при установке Обороты снизились до 2,8 или около того. Это происходит несколько раз в разных интервалах оборотов, которые я также тестировал.

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

Ниже приведен код, который я использовал для запуска с драйвером TMC2208. (Извините, если это слишком длинно, но я включил комментарии, чтобы задокументировать мой мыслительный процесс при программировании Arduino.)

Включен список оборотов в минуту и задержек импульсов

об/мин pulse_delay (мкс)
1 37500 (слишком быстро)
2 18750 (слишком быстро)
2.25 16666.6667 (работает)
2.5 15000 (работает)
3 12500 (работает)
5 7500 (работает)
10 3759 (работает)
// Код работы шагового двигателя с драйвером TMC2208 и кодом ЖК-дисплея
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <Stepper.h>
// Определить контакты для драйвера шагового двигателя
#define stepPin 7  // определение вывода для шага
#define dirPin 8   // определение контакта направления
#define enPin 9    // определение PIN-кода для включения ^^ Нужно ли мне это?

// Определить кнопки для скорости и направления
#define buttGo 10      // определение PIN-кода для ввода кнопки
#define buttSwitch 11  // определение контакта для ввода кнопки
#define incSpeed 12    // определяем кнопку для увеличения скорости
#define decSpeed 13    // определение кнопки для изменения скорости

// Определить распиновку ЖК-дисплея
const int en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3;  // Распиновка адаптера
const int i2c_addr = 0x27;                                                 // Распиновка адаптера
LiquidCrystal_I2C lcd(i2c_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);

/* Formulas for determining RPM
Assuming one step per pulse, delay is: wait = (μs/min)*(1/rpm)*(1/pulses per revolution) - overhead
Overhead = extra time it takes to run digitalWrite twice and loop
RPM = (steps per second)/(steps per revolution) * (60 seconds/minute)
step angle = 1.8 degrees ; 200 steps per revolution ; .005 revolutions per step
Solving for Steps Per Second: SPS = (RPM)/((REV_P_STEP)*(60 sec/min))

According to Notes: 
  C0 = 15 * M_PI ; Motor X Circumference
  XSPR = 1 * (200 * 11) ; Motor X Steps per Rotation
  dVx = dS * (XSPR/C0) ; Desired X-Axis time from mm/s to x-axis steps/sec (dS represents desired x-axis speed)
  Assuming we use a button/knob, each increment/decrement would change the mm/s by 0.1  
  So, to get the necessary pulse delay, increment/decrement by:
    dVx = 0.1 * (1 * (200 * 11))/(15*M_PI)

  Example: If we have an initial target rpm of 10, that gives us a dS of 10
  As such, dVx which is our speed in steps will be 10 * (1 * (200 * 11)) / (15 * M_PI) 
  All of these variables are set globally
  wait = (microsecondsPminute)*(1/RPM)*(1/pulsesPrevolution) - overhead
  rpm / 60 = rps
  60 / rpm = spr
  (60 / rpm)/360 = spd
  ((60 / rpm)/360) * 1.8 = sps
*** 
Frequency = (RPM)/((Resolution/360)*60)
Resolution =  360/(Steps/Revolution)
For us, Resolution = 360/(200); so Resolution = 1.8**
Frequency = (RPM)/(0.005 * 60) = RPM/(0.3)
*
T_inc = incremental torque produced with each microstep
T_hfs = holding torque (full-step operation)
SDR = step division ratio (number of microsteps per full step)

T_inc = T_hfs * sin(90/SDR)
T_inc = 0.14 * sin(90/256)
T_inc = 0.00085902385
*/
float resolution = 1.8;
float rpm = 10;
float pulse_delay;  // Временное начальное значение, которое изменится после установки void
bool buttonState = false;
bool onState = false;

// Настраиваем типы вывода
void setup() {
  Serial.begin(9600);  // Измените эту скорость передачи данных на любую скорость, на которой работает ЖК-экран - обычно должно быть 9600
  // Настраиваем ЖК-экран
  pulse_delay = setRPM(rpm, resolution);
  lcd.begin(16, 2);  // устанавливаем количество столбцов и строк ЖК-дисплея:
  lcd.setCursor(0, 0);
  lcd.print("Current RPM: ");
  lcd.setCursor(12, 0);
  lcd.print(rpm);
  lcd.setCursor(0, 1);
  lcd.print("MOVE RIGHT");

  // Устанавливаем кнопки
  pinMode(buttSwitch, INPUT);
  pinMode(buttGo, INPUT);
  pinMode(incSpeed, INPUT);
  pinMode(decSpeed, INPUT);

  // Устанавливаем инициалы
  pinMode(enPin, OUTPUT);
  digitalWrite(enPin, HIGH);  // деактивируем драйвер
  pinMode(dirPin, OUTPUT);
  digitalWrite(dirPin, HIGH);
  pinMode(stepPin, OUTPUT);
  digitalWrite(enPin, LOW);  // активирует драйвер
}

// Число оборотов в секунду должно быть указано с точностью до десятых
// Запускаем код непрерывно
void loop() {
  // Чтение нажатых кнопок
  int pressSwitch = digitalRead(buttSwitch);
  int pressGo = digitalRead(buttGo);
  int pressInc = digitalRead(incSpeed);
  int pressDec = digitalRead(decSpeed);
  pulse_delay = setRPM(rpm, resolution);  //pulse_delay = (об/мин * 200)/60;

  if (pressSwitch == HIGH)  // Перемещает двигатель вправо (против часовой стрелки)
  {
    if (buttonState == 0) {
      digitalWrite(dirPin, LOW);  // задаем направление вращения двигателя
      buttonState = 1;
      lcd.setCursor(0, 1);
      lcd.print("MOVE  LEFT");
      delay(500);
    } 
    else {
      if (buttonState == 1) {
        digitalWrite(dirPin, HIGH);  // задаем направление вращения двигателя
        buttonState = 0;
        lcd.setCursor(0, 1);
        lcd.print("MOVE RIGHT");
        delay(500);
      }
    }
  }

  if (pressGo == HIGH)  // Двигает двигатель
  {
    digitalWrite(stepPin, HIGH);  // Это изменение от НИЖНЕГО к ВЫСОКЕМУ - это то, что создает "Rising Edge" поэтому easydriver знает, когда нужно сделать шаг.
    delayMicroseconds(pulse_delay);
    digitalWrite(stepPin, LOW);
  }

  if (pressInc == HIGH)  // Увеличивает число оборотов в минуту
  {
    rpm = rpm + 0.1;
    delay(150);
    lcd.setCursor(12, 0);
    lcd.print(rpm);
  }

  if (pressDec == HIGH)  // Уменьшает число оборотов в минуту
  {
    rpm = rpm - 0.1;
    delay(150);
    lcd.setCursor(12, 0);
    lcd.print(rpm);
  }
}

// Функция для получения времени задержки импульса на основе введенного значения частоты вращения
float setRPM(float rpm, float resolution) {
  /*
  float temper = ((60 / rpm) / 360);
  float pulsetime = ((temper * resolution) * 1000000);
  1 rpm = 37500 us delay (too fast)
  2 rpm = 18750 us delay (too fast)
  2.25 rpm = 16666.6667 us delay
  2.5 rpm = 15000 us delay
  3 rpm = 12500 us delay
  5 rpm = 7500 us delay
  10 rpm = 3750 us delay
  */
  unsigned int pulsetime = (60000000 / (1600 * rpm));
  return pulsetime;  // Преобразуется в микросекунды
}

, 👍2

Обсуждение

Во время движения (pressGo == HIGH) ваша высокая часть пульса имеет переменную длительность. Как вы контролируете нижнюю часть? -- Измеряли ли вы формируемый сигнал, например, частотомером или осциллографом?, @the busybee

Я быстро составил таблицу, и выяснилось, что переход от 3,0 до 3,1 в «об/мин» пересекает «границу 255/256» в «pulse_delay». Возможно, вы захотите временно изменить свой скетч, чтобы напрямую настроить «pulse_delay», чтобы проверить это подробно., @the busybee

@thebusybee Мне не удалось достать частотомер или осциллограф, но я использовал мультиметр, просто чтобы проверить, возникла ли проблема с настройкой моего оборудования - а я не думаю, что она есть . Я измерю сгенерированный сигнал, чтобы проверить это! Что вы подразумеваете под пересечением границы 255/256? Я изменил свойpulse_delay непосредственно в операторе pressGo if, и, похоже, он работает нормально, если изменить его напрямую. Спасибо вам за быстрый ответ!, @jonathan

pulse_delay — это float, но вы вызываете с его помощью delayMicroсекунды(). Эта функция ожидает unsigned int согласно [документации](https://www.arduino.cc/reference/en/language/functions/time/delaymicroсекунды/), поэтому значение преобразуется. unsigned int использует несколько байтов, а значения от 0 до 255 "используют" только младший байт. Начиная с 256, следующие байты имеют значения, отличные от 0. Это то, что я имею в виду под «границей 255/256». -- Какой диапазон значений вы проверяли конкретно?, @the busybee

Спасибо за разъяснения по поводу байта "лимит". Я преобразовал свою переменную pulse_delay в unsigned int, поскольку для меня имеет смысл использовать ее при использовании функции delayMicroсекунды(). Я также переключился на уравнение, определенное в моей функции setRPM, на уравнение, определенное в этом [посте](https://arduinoprosto.ru/q/1338/understanding-the-relationship-of-pulse-and-stepper -об/мин). Используя это уравнение, я конкретно проверил обороты 3, 5 и 10., @jonathan

Это изменение нормально, поскольку оно проясняет работу для будущего читателя, скорее всего, для вас самих через несколько недель. ;-) -- Когда я спросил протестированный диапазон, я имел в виду диапазон pulse_delay, который вы проверили. Шаговый двигатель работает так, как ожидалось? Я бы начал с какого-то высокого значения (для низких оборотов, например 2000 мкс) и уменьшил его до некоторого низкого значения (для высоких оборотов, например 100 мкс). **Если** есть проблема с упомянутой границей, вы все равно увидите эту пилообразную передаточную функцию, повторения которой хорошо соответствуют кратным 256. Возможно, вы захотите [отредактировать] свой вопрос с помощью таблицы., @the busybee

Извините за путаницу - я проверил задержку импульсов от 37500 до 3750 мкс и заметил, что он работал как ожидалось вплоть до низкого значения задержки около 18750 мкс, которое соответствует частоте вращения 2. Шаговый ход перестает работать, как ожидалось, после Я начинаю достигать более низких оборотов/более высоких задержек импульсов. Я также добавил к исходному вопросу таблицу задержек импульсов и оборотов в минуту, которую я проверил. Что касается пилообразных передаточных функций, вы имеете в виду напряжение, отображаемое на осциллографе? Я считаю, что существует проблема с точным движением на более низких скоростях., @jonathan

Я не могу воспроизвести свою таблицу, я не знаю, где мои расчеты ошиблись. Так что забудьте о границе 255/256. -- Во всяком случае, я не могу воспроизвести и ваши ценности. Например, для об/мин=2,5, темп=(60/об/мин)/360=0,0666... и пульс_задержка=(темпер*1,8)*1000000=120000. Но вы указываетеpuls_delay=15000. Убедитесь, что результаты источника и таблицы совпадают и верны., @the busybee


2 ответа


Лучший ответ:

2

В документации говорится:

В настоящее время наибольшее значение, обеспечивающее точную задержку, — 16383; большие значения могут привести к очень короткой задержке.

Значение, присвоенное delayMicroсекунды(), похоже, обрабатывается операцией по модулю 16384. Это означает, что значение делится на 16384, а остаток от этого деления представляет собой эффективную задержку в микросекундах.

Если мы применим это к задержкам в вашей таблице, мы получим:

желаемая частота вращения расчетная задержка (мкс) эффективная задержка (мкс) эффективная частота вращения
1 37500 4732 7.9
2 18750 2366 15,8
2.25 16666 16666 2.25
2.5 15000 15000 2.5
3 12500 12500 3
5 7500 7500 5
10 3750 3750 10

Одно из возможных решений представлено в той же документации:

Для задержек, превышающих несколько тысяч микросекунд, вместо этого следует использовать delay().

Возможно, вы захотите использовать обе функции, в зависимости от необходимой задержки.


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

Возможно, вы захотите разделить период импульса на две половины и взять под контроль и эту низкую фазу.

,

Спасибо за ответ! Что касается второй части вашего ответа, вы имели в виду, что я должен разделить оператор запуска двигателя pressGo на StepPin High, (задержка 1/2 импульса), StepPin Low, (задержка 1/2 импульса)? if (pressGo == ВЫСОКИЙ) { digitalWrite(stepPin, ВЫСОКИЙ); задержкамикросекунды (pulse_delay); digitalWrite(stepPin, LOW); задержкамикросекунды (pulse_delay); } Вот мой код для справки. Я также включил еще один оператор if, так что если мойpulse_delay больше, чем 16384 для функцииdelayMicroсекунды, вместо этого он использует задержку()., @jonathan

@jonathan Да, ты можешь пойти по этому пути. Просто имейте в виду, что время «разворота» в настоящее время и в целом не определено. Возможно, вы захотите измерить его, например, с помощью осциллографа или аналогичного устройства. Пока она невелика по сравнению с задержками, вы можете игнорировать ее. Или вы это учитываете для низкой фазы., @the busybee


1

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

// Код работы шагового двигателя с драйвером A4988 и кодом ЖК-дисплея
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <Stepper.h>
// Определить контакты для драйвера шагового двигателя
#define stepPin 7  // определение вывода для шага
#define dirPin 8   // определение контакта направления
#define enPin 9    // определение PIN-кода для включения ^^ Нужно ли мне это?

// Определить кнопки для скорости и направления
#define buttGo 10      // определение PIN-кода для ввода кнопки
#define buttSwitch 11  // определение контакта для ввода кнопки
#define incSpeed 12    // определяем кнопку для увеличения скорости
#define decSpeed 13    // определение кнопки для изменения скорости

// Определить распиновку ЖК-дисплея
const int en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3;  // Распиновка адаптера
const int i2c_addr = 0x27;                                                 // Распиновка адаптера
LiquidCrystal_I2C lcd(i2c_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);

/* Formulas for determining RPM
Assuming one step per pulse, delay is: wait = (μs/min)*(1/rpm)*(1/pulses per revolution) - overhead
Overhead = extra time it takes to run digitalWrite twice and loop
RPM = (steps per second)/(steps per revolution) * (60 seconds/minute)
step angle = 1.8 degrees ; 200 steps per revolution ; .005 revolutions per step
Solving for Steps Per Second: SPS = (RPM)/((REV_P_STEP)*(60 sec/min))

According to John Craig: 
  C0 = 15 * M_PI ; Motor X Circumference
  XSPR = 1 * (200 * 11) ; Motor X Steps per Rotation
  dVx = dS * (XSPR/C0) ; Desired X-Axis time from mm/s to x-axis steps/sec (dS represents desired x-axis speed)
  Assuming we use a button/knob, each increment/decrement would change the mm/s by 0.1  
  So, to get the necessary pulse delay, increment/decrement by:
    dVx = 0.1 * (1 * (200 * 11))/(15*M_PI)

  Example: If we have an initial target rpm of 10, that gives us a dS of 10
  As such, dVx which is our speed in steps will be 10 * (1 * (200 * 11)) / (15 * M_PI) 
  All of these variables are set globally
  wait = (microsecondsPminute)*(1/RPM)*(1/pulsesPrevolution) - overhead
  rpm / 60 = rps
  60 / rpm = spr
  (60 / rpm)/360 = spd
  ((60 / rpm)/360) * 1.8 = sps
*** 
*Assume from measured diameter with calipers that it is 12 mm (Check 11.85 as well)
To get Spacings: V_extruder / RPM_rod 
V_extruder = M_PI * Diam_extruder * RPM_extruder (in (mm*rev)/min) 
RPM_rod = 13 rpm (set from original bought parts)
V_extruder = M_PI * 12 mm * X 
Spacing = (M_PI * 12 * X) / 9.64507699 RPM 
Spacing = 3.90863773 * X
For Spacings of 3 mm... 0.76753084 rev/min ~= 0.77 rev/min
For Spacings of 5 mm... 1.27921807 rev/min ~= 1.28 rev/min
For Spacings of 7 mm... 1.79090529 rev/min ~= 1.79 rev/min
*
y = 1.0715x + 2.6653
y = Voltage Setting
x = RPM
For Voltage Setting of 13, RPM = 9.64507699

T_inc = incremental torque produced with each microstep
T_hfs = holding torque (full-step operation)
SDR = step division ratio (number of microsteps per full step)

T_inc = T_hfs * sin(90/SDR)
T_inc = 0.14 * sin(90/256)
T_inc = 0.00085902385
*/
float resolution = 1.8;
float rpm = 3;
unsigned int pulse_delay;  // Временное начальное значение, которое изменится после установки void
bool buttonState = false;
bool onState = false;

// Настраиваем типы вывода
void setup() {
  Serial.begin(9600);  // Измените эту скорость передачи данных на любую скорость, на которой работает ЖК-экран - обычно должно быть 9600
  // Настраиваем ЖК-экран
  pulse_delay = setRPM(rpm);
  lcd.begin(16, 2);  // устанавливаем количество столбцов и строк ЖК-дисплея:
  lcd.setCursor(0, 0);
  lcd.print("Current RPM: ");
  lcd.setCursor(12, 0);
  lcd.print(rpm);
  lcd.setCursor(0, 1);
  lcd.print("MOVE RIGHT");

  // Устанавливаем кнопки
  pinMode(buttSwitch, INPUT);
  pinMode(buttGo, INPUT);
  pinMode(incSpeed, INPUT);
  pinMode(decSpeed, INPUT);

  // Устанавливаем инициалы
  pinMode(enPin, OUTPUT);
  digitalWrite(enPin, HIGH);  // деактивируем драйвер
  pinMode(dirPin, OUTPUT);
  digitalWrite(dirPin, HIGH);
  pinMode(stepPin, OUTPUT);
  digitalWrite(enPin, LOW);  // активирует драйвер
}

// Число оборотов в секунду должно быть указано с точностью до десятых
// Запускаем код непрерывно
void loop() {
  // Чтение нажатых кнопок
  int pressSwitch = digitalRead(buttSwitch);
  int pressGo = digitalRead(buttGo);
  int pressInc = digitalRead(incSpeed);
  int pressDec = digitalRead(decSpeed);
  pulse_delay = setRPM(rpm);  //pulse_delay = (об/мин * 200)/60;

  if (pressSwitch == HIGH)  // Перемещает двигатель вправо (против часовой стрелки)
  {
    if (buttonState == 0) {
      digitalWrite(dirPin, LOW);  // задаем направление вращения двигателя
      buttonState = 1;
      lcd.setCursor(0, 1);
      lcd.print("MOVING  LEFT");
      delay(500);
    } else {
      if (buttonState == 1) {
        digitalWrite(dirPin, HIGH);  // задаем направление вращения двигателя
        buttonState = 0;
        lcd.setCursor(0, 1);
        lcd.print("MOVING RIGHT");
        delay(500);
      }
    }
  }
// Обе следующие функции перемещают двигатель, но переключаются между микросекундами и миллисекундами с соответствующей задержкой.
  if (pressGo == HIGH && pulse_delay >= 16383)  // Двигает двигатель
  {
      digitalWrite(stepPin, HIGH);  // Это изменение от НИЖНЕГО к ВЫСОКЕМУ - это то, что создает "Rising Edge" поэтому easydriver знает, когда нужно сделать шаг.
      delay(pulse_delay/1000);
      digitalWrite(stepPin, LOW);
  }

  if (pressGo == HIGH && pulse_delay < 16383)
  {
      digitalWrite(stepPin, HIGH);  // Это изменение от НИЖНЕГО к ВЫСОКЕМУ - это то, что создает "Rising Edge" поэтому easydriver знает, когда нужно сделать шаг.
      delayMicroseconds(pulse_delay);
      digitalWrite(stepPin, LOW);
  }

  if (pressInc == HIGH)  // Увеличивает число оборотов в минуту
  {
    rpm = rpm + 0.01;
    delay(150);
    lcd.setCursor(12, 0);
    lcd.print(rpm);
  }

  if (pressDec == HIGH)  // Уменьшает число оборотов в минуту
  {
    rpm = rpm - 0.01;
    delay(150);
    lcd.setCursor(12, 0);
    lcd.print(rpm);
  }
}

// Функция для получения времени задержки импульса на основе введенного значения частоты вращения
float setRPM(float rpm) {
  /*
  float temper = ((60 / rpm) / 360);
  float pulsetime = ((temper * resolution) * 1000000);
  1 rpm = 37500 us delay (too fast)
  2 rpm = 18750 us delay (too fast)
  2.25 rpm = 16666.6667 us delay
  2.5 rpm = 15000 us delay
  3 rpm = 12500 us delay
  5 rpm = 7500 us delay
  10 rpm = 3750 us delay
  */
  unsigned int pulsetime = (60000000 / (1600 * rpm));
  return pulsetime;  // Преобразуется в микросекунды
}

,