2

This is my first project working with stepper motors, so I may have a bit of a shaky understanding of the electronic side of the project. I'm trying to create a simple device with 4 buttons connected to a stepper motor driver/NEMA 17 motor, with the ability to increase/decrease RPM, switch motor spin direction, and run the motor with the button being held down. (LCD screen included for output as well)

I'm noticing that when changing the RPM on my motor, i.e. from 3.2 to 3.1 to 3.0, the speed of the motor is really slow, then when set to 3.0, suddenly increases a lot, but goes down when setting the RPM back down to 2.8 or so. This occurs multiple times along different intervals of RPMs that I have tested as well.

I think that there may be an issue with the method with which I am calculating the pulse delay given the RPM in my function setRPM() at the end of my code or with the way that I am micro-stepping, which I am doing because I need extremely precise movement.

Included below is the code that I used to run with my TMC2208 driver. (Sorry if it is rather long, but I included comments to document my thought process when programming the Arduino.)

Included list of RPMs and pulse delays

rpm pulse_delay (us)
1 37500 (too fast)
2 18750 (too fast)
2.25 16666.6667 (works)
2.5 15000 (works)
3 12500 (works)
5 7500 (works)
10 3759 (works)
// Stepper motor run code with TMC2208 driver and LCD code
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <Stepper.h>
// Define Pins for Step Motor Driver
#define stepPin 7  // define pin for step
#define dirPin 8   // define pin for direction
#define enPin 9    // define pin for enable ^^ Do I need this?

// Define Buttons for Speed and Direction #define buttGo 10 // define pin for button input #define buttSwitch 11 // define pin for button input #define incSpeed 12 // define button for increasing speed #define decSpeed 13 // define button for decreeasing speed

// Define LCD pinout const int en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3; // Adapter Pinout const int i2c_addr = 0x27; // Adapter pinout 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; // Temporary starting value that will change after void setup bool buttonState = false; bool onState = false;

// Set up output types void setup() { Serial.begin(9600); // Change this baud rate to whatever rate the LCD screen runs on - should generally be 9600 // Set up LCD screen pulse_delay = setRPM(rpm, resolution); lcd.begin(16, 2); // set up the LCD's number of columns and rows: lcd.setCursor(0, 0); lcd.print("Current RPM: "); lcd.setCursor(12, 0); lcd.print(rpm); lcd.setCursor(0, 1); lcd.print("MOVE RIGHT");

// Sets up buttons pinMode(buttSwitch, INPUT); pinMode(buttGo, INPUT); pinMode(incSpeed, INPUT); pinMode(decSpeed, INPUT);

// Establish initials pinMode(enPin, OUTPUT); digitalWrite(enPin, HIGH); // deactivate driver pinMode(dirPin, OUTPUT); digitalWrite(dirPin, HIGH); pinMode(stepPin, OUTPUT); digitalWrite(enPin, LOW); // activates driver }

// Revolutions per second should be able specified to the tenths // Run code to continual void loop() { // Read Buttons Being Pressed int pressSwitch = digitalRead(buttSwitch); int pressGo = digitalRead(buttGo); int pressInc = digitalRead(incSpeed); int pressDec = digitalRead(decSpeed); pulse_delay = setRPM(rpm, resolution); //pulse_delay = (rpm * 200)/60;

if (pressSwitch == HIGH) // Moves motor Right (Counter-Clockwise) { if (buttonState == 0) { digitalWrite(dirPin, LOW); // sets direction of the motor turning buttonState = 1; lcd.setCursor(0, 1); lcd.print("MOVE LEFT"); delay(500); } else { if (buttonState == 1) { digitalWrite(dirPin, HIGH); // sets direction of the motor turning buttonState = 0; lcd.setCursor(0, 1); lcd.print("MOVE RIGHT"); delay(500); } } }

if (pressGo == HIGH) // Moves motor { digitalWrite(stepPin, HIGH); // This LOW to HIGH change is what creates the "Rising Edge" so the easydriver knows when to step. delayMicroseconds(pulse_delay); digitalWrite(stepPin, LOW); }

if (pressInc == HIGH) // Increases RPM { rpm = rpm + 0.1; delay(150); lcd.setCursor(12, 0); lcd.print(rpm); }

if (pressDec == HIGH) // Decreases RPM { rpm = rpm - 0.1; delay(150); lcd.setCursor(12, 0); lcd.print(rpm); } }

// Function to get pulse delay time based off of inputted RPM value 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; // Converts to microseconds }

jonathan
  • 86
  • 7

2 Answers2

2

The documentation says:

Currently, the largest value that will produce an accurate delay is 16383; larger values can produce an extremely short delay.

The value given to delayMicroseconds() seems to be processed by a modulo 16384 operation. That means that the value is divided by 16384, and the remainder of this division is the effective delay in microseconds.

If we apply this to the delays in your table, we get:

desired rpm calculated delay (µs) effective delay (µs) effective rpm
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

One possible solution is presented in the same documentation:

For delays longer than a few thousand microseconds, you should use delay() instead.

You might want to consider to use both functions, depending on the necessary delay.


And then there is the other issue with the time when the output is low. Currently, it is determined by the time the code takes to quit loop(), do its internal looping, call loop() and your statements until the pin goes high again.

You might want to divide the pulse period in two halves and take control of this low phase, too.

the busybee
  • 2,408
  • 9
  • 18
2

For reference for any future readers who have had similar problems using a TMC2208 Stepper Motor - I have attached my annotated code to this answer for your use. In the annotation, I also include equations that can be used to determine spacings for a coiling setup.

// Stepper motor run code with A4988 driver and LCD code
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <Stepper.h>
// Define Pins for Step Motor Driver
#define stepPin 7  // define pin for step
#define dirPin 8   // define pin for direction
#define enPin 9    // define pin for enable ^^ Do I need this?

// Define Buttons for Speed and Direction #define buttGo 10 // define pin for button input #define buttSwitch 11 // define pin for button input #define incSpeed 12 // define button for increasing speed #define decSpeed 13 // define button for decreeasing speed

// Define LCD pinout const int en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3; // Adapter Pinout const int i2c_addr = 0x27; // Adapter Pinout 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 (mmrev)/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; // Temporary starting value that will change after void setup bool buttonState = false; bool onState = false;

// Set up output types void setup() { Serial.begin(9600); // Change this baud rate to whatever rate the LCD screen runs on - should generally be 9600 // Set up LCD screen pulse_delay = setRPM(rpm); lcd.begin(16, 2); // set up the LCD's number of columns and rows: lcd.setCursor(0, 0); lcd.print("Current RPM: "); lcd.setCursor(12, 0); lcd.print(rpm); lcd.setCursor(0, 1); lcd.print("MOVE RIGHT");

// Sets up buttons pinMode(buttSwitch, INPUT); pinMode(buttGo, INPUT); pinMode(incSpeed, INPUT); pinMode(decSpeed, INPUT);

// Establish initials pinMode(enPin, OUTPUT); digitalWrite(enPin, HIGH); // deactivate driver pinMode(dirPin, OUTPUT); digitalWrite(dirPin, HIGH); pinMode(stepPin, OUTPUT); digitalWrite(enPin, LOW); // activates driver }

// Revolutions per second should be able specified to the tenths // Run code to continual void loop() { // Read Buttons Being Pressed int pressSwitch = digitalRead(buttSwitch); int pressGo = digitalRead(buttGo); int pressInc = digitalRead(incSpeed); int pressDec = digitalRead(decSpeed); pulse_delay = setRPM(rpm); //pulse_delay = (rpm * 200)/60;

if (pressSwitch == HIGH) // Moves motor Right (Counter-Clockwise) { if (buttonState == 0) { digitalWrite(dirPin, LOW); // sets direction of the motor turning buttonState = 1; lcd.setCursor(0, 1); lcd.print("MOVING LEFT"); delay(500); } else { if (buttonState == 1) { digitalWrite(dirPin, HIGH); // sets direction of the motor turning buttonState = 0; lcd.setCursor(0, 1); lcd.print("MOVING RIGHT"); delay(500); } } } // Both of the next functions move the motor, but switches between Microseconds and Milliseconds in delay time to fit. if (pressGo == HIGH && pulse_delay >= 16383) // Moves motor { digitalWrite(stepPin, HIGH); // This LOW to HIGH change is what creates the "Rising Edge" so the easydriver knows when to step. delay(pulse_delay/1000); digitalWrite(stepPin, LOW); }

if (pressGo == HIGH && pulse_delay < 16383) { digitalWrite(stepPin, HIGH); // This LOW to HIGH change is what creates the "Rising Edge" so the easydriver knows when to step. delayMicroseconds(pulse_delay); digitalWrite(stepPin, LOW); }

if (pressInc == HIGH) // Increases RPM { rpm = rpm + 0.01; delay(150); lcd.setCursor(12, 0); lcd.print(rpm); }

if (pressDec == HIGH) // Decreases RPM { rpm = rpm - 0.01; delay(150); lcd.setCursor(12, 0); lcd.print(rpm); } }

// Function to get pulse delay time based off of inputted RPM value 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; // Converts to microseconds }

jonathan
  • 86
  • 7