Шаговый двигатель 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; // Преобразуется в микросекунды
}
@jonathan, 👍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
Для справки для будущих читателей, у которых возникали подобные проблемы при использовании шагового двигателя 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; // Преобразуется в микросекунды
}
- Шаговый двигатель не работает с платой A4988
- Справка по библиотеке AccelStepper - Одновременное управление двигателем
- Как позволить шаговому двигателю вращаться постоянно?
- Как правильно использовать микрошаговый драйвер с шаговым двигателем более низкого напряжения
- arduino 28BYJ-48 stepper только жужжит, не вращается
- 2 шаговых драйвера с регулировкой скорости
- Драйвер шагового двигателя не работает должным образом
- Как изменить направление шагового двигателя с помощью библиотеки AccelStepper?
Во время движения (
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