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
}
8
  • While moving (pressGo == HIGH) your high part of the pulse has a variable duration. How do you control the low part? -- Did you measure the generated signal, for example with a frequency counter or oscilloscope? Commented Jul 13, 2023 at 6:34
  • I made a quick table and it revealed that going from 3.0 to 3.1 in rpm crosses the "255/256 border" in pulse_delay. You might want to temporarily change your sketch to adjust pulse_delay directly to check this in detail. Commented Jul 13, 2023 at 8:31
  • @thebusybee I haven't been able to get my hands on a frequency counter or an oscilloscope, but I have used a multimeter just to see if there was an issue I was making with my hardware setup - which I don't think there is. I'll measure the generated signal to check that out! What do you mean by crossing the 255/256 border? I have changed my pulse_delay directly in my pressGo if statement and it seems to work alright when changed directly. Thank you for the quick response!
    – jonathan
    Commented Jul 13, 2023 at 16:44
  • pulse_delay is a float, but you call delayMicroseconds() with it. This function expects an unsigned int according to the documentation, so the value is converted. The unsigned int uses multiple bytes, and values from 0 to 255 "use" only the least significant byte. Starting with 256, the next byte(s) carry other values than 0. That's what I mean by "255/256 border". -- Which range of values did you check concretely? Commented Jul 13, 2023 at 19:09
  • Thank you for the clarification on the byte "limit". I converted my pulse_delay variable to unsigned int since it makes sense to me that I should utilize it when using the delayMicroseconds() function. I also switched to the equation defined in my setRPM function to the one defined in this post. Using this equation I concretely checked rpms of 3, 5, and 10.
    – jonathan
    Commented Jul 13, 2023 at 22:01

2 Answers 2

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.

2
  • Thank you for the response! For the second part of your answer, did you mean that I should split the pressGo motor run statement into stepPin High, (1/2 pulse delay), stepPin Low, (1/2 pulse delay)? if (pressGo == HIGH) { digitalWrite(stepPin, HIGH); delayMicroseconds(pulse_delay); digitalWrite(stepPin, LOW); delayMicroseconds(pulse_delay); } Here is my code for reference. I have also included another if statement such that if my pulse_delay is greater than that 16384 for the delayMicrosecond function, it uses delay() instead.
    – jonathan
    Commented Jul 17, 2023 at 17:17
  • @jonathan Yes, you can go that route. Just be aware that the time to "turn-around" is currently and generally undetermined. You might want to measure it, for example with an oscilloscope or similar device. As long as it is small compared to the delays, you can consider to ignore it. Or you take it into account for the low phase. Commented Jul 17, 2023 at 17:26
1

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 (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;  // 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
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.