Need help getting stepper motor to loop through spin/pause cycle

Hi guys, I need your help with a project using a stepper motor that is controlled by a single momentary button.

Depending on the position of a rotary switch, I need the stepper motor to run at different duty cycles, most of which are less than 100%. In other words, while holding the switch down, I want to:

  1. Make the motor spin for a given duration
  2. Pause for a given duration
  3. Repeat 1 and 2 until the button is released

The duration of the spin spintime and the pause stepdelay are defined by a 12-position rotary switch (I only need 8 of them, so the other positions are "empty").

My code below is only working for positions 7 and 8, because the stepdelay variable (i.e. the pause time) is 0 for those two positions. I want the motor to spin continuously in these positions, one forward, one reverse. However, for basically any stepdelay value over 0, the motor just emits a whining sound and doesn't spin. Thus positions 1-6 aren't working.

#include <ezButton.h>

const int stepPin = 3; 
const int dirPin = 4;
ezButton go_button = 5; 
const int enable = 6;
const int pos_1 = 8;
const int pos_2 = 9;
const int pos_3 = 10;
const int pos_4 = 16;
const int pos_5 = 14;
const int pos_6 = 15;
const int pos_7 = 18;
const int pos_8 = 7;
int stepdelay; //Motor off time
int spintime; //Motor on time
bool spindirection; //Motor spin direction
unsigned long buttonpresstime; //Moment when the button is pressed

void setup() {
  pinMode(stepPin,OUTPUT); 
  pinMode(dirPin,OUTPUT);
  pinMode(enable,OUTPUT);
  pinMode(pos_1,INPUT_PULLUP);
  pinMode(pos_2,INPUT_PULLUP);
  pinMode(pos_3,INPUT_PULLUP);
  pinMode(pos_4,INPUT_PULLUP);
  pinMode(pos_5,INPUT_PULLUP);
  pinMode(pos_6,INPUT_PULLUP);
  pinMode(pos_7,INPUT_PULLUP);
  pinMode(pos_8,INPUT_PULLUP);
  go_button.setDebounceTime(50);
  digitalWrite(enable,HIGH); //Sets initial condition of the stepper motor driver board outputs to "off"
}

void loop() {
  //Get position from rotary switch
  int pos_1_State = digitalRead(pos_1);
  int pos_2_State = digitalRead(pos_2);
  int pos_3_State = digitalRead(pos_3);
  int pos_4_State = digitalRead(pos_4);
  int pos_5_State = digitalRead(pos_5);
  int pos_6_State = digitalRead(pos_6);
  int pos_7_State = digitalRead(pos_7);
  int pos_8_State = digitalRead(pos_8);

  //Set motor output parameters based on switch position
  if (pos_1_State == LOW) {
    stepdelay = 450;
    spintime = 350;
    spindirection = HIGH;
  } else if (pos_2_State == LOW) {
    stepdelay = 550;
    spintime = 300;
    spindirection = HIGH;
  } else if (pos_3_State == LOW) {
    stepdelay = 650;
    spintime = 250;
    spindirection = HIGH;
  } else if (pos_4_State == LOW) {
    stepdelay = 750;
    spintime = 200;
    spindirection = HIGH;
  } else if (pos_5_State == LOW) {
    stepdelay = 850;
    spintime = 150;
    spindirection = HIGH;
  } else if (pos_6_State == LOW) {
    stepdelay = 950;
    spintime = 100;
    spindirection = HIGH;
  } else if (pos_7_State == LOW) { //Constant forward speed position 
    stepdelay = 0;
    spintime = 32000;
    spindirection = HIGH;
  } else if (pos_8_State == LOW) { //Reverse, constant speed
    stepdelay = 0;
    spintime = 32000;
    spindirection = LOW;
  } else { //Accounting for switch in "empty" positions
    stepdelay = 0;
    spintime = 0;
    spindirection = LOW;
  }
    
  go_button.loop();
  int go_ButtonState = go_button.getState();
  
  if(go_ButtonState == 0) {
    digitalWrite(enable,LOW); //Turn on motor driver board outputs
    digitalWrite(dirPin,spindirection); //Set motor spin direction based on rotary switch position
    buttonpresstime = millis(); //Get the moment in time when the button is pressed
    if (millis() - buttonpresstime < spintime) { //Spin motor for "spintime" duration set by switch position
        digitalWrite(stepPin,HIGH); 
        delayMicroseconds(100); 
        digitalWrite(stepPin,LOW); 
        delayMicroseconds(100);
     }
    delay(stepdelay); //Pause for duration set by switch position
    go_button.getState();
  }
  
  if(go_button.isReleased()){
    digitalWrite(enable,HIGH); //Turn off motor driver board outputs
  }

}

I understand that using delay in a sketch is generally not considered best practice as it shuts down everything else from happening, and that appears to be what the primary source of the problem is here. But I can't seem to successfully insert my "pause time" into the code via another method.

I tried creating a second millis() if loop for the delay using a new unsigned long variable named spindonetime. I inserted the following code in place of the delay(stepdelay); line. Unfortunately, this didn't work either:

spindonetime = millis();
    if (millis() - spindonetime < stepdelay) {
        digitalWrite(enable,HIGH); //Turn off motor driver board outputs for duration of "stepdelay"
    }

I'm pretty stumped at this point.

Thanks for any help you can give!

look at Using millis() for timing. A beginners guide and Several things at the same time

i think you intended this to be a while loop

Originally I thought the same thing, and the first version of this program used that. But I found that the motor will simply continue spinning indefinitely, even after releasing the button. That indicates to me that for some reason it was stuck inside the while loop. Nothing would break the loop, including multiple button presses/releases, or a new switch position with more presses/releases. The only way I could stop it was to shut down the power supply.

but not because of the while

Steppers cannot go from a dead stop to thousands of steps per second immediately. You need to accelerate the motor to high speeds like the AccelStepper or MobaTools stepper libraries (to name two) do.

Or write your own code for acceleration. Robin2 has some sample code for that. See post #8.

Without a clearer explanation, I'm assuming you're saying that the logic for the while loop is not correct. The clause if (millis() - buttonpresstime < spintime) { is the same between them. I only substituted if and while.

But I am also assuming that if I had made a mistake in that clause, you would have said something about it in your first reply. Clearly I'm making an assumption as to what you mean, but given the relative paucity of information supplied, that's what I'm working with. I will freely admit that my assumptions may not be correct.

I've noticed that all example code for millis(), such as what is shown in the links given in the first response from J-M-L, all use if loops.

I'm not asking for thousands of steps immediately. In fact, this program is used to spin the motor at extremely low RPM. I'm using 16th step increments on the driver board to keep everything slow while maintaining as much torque as I can.

That part of the code works just fine when used without a timing loop, so I would think it would have failed if that were the root of the problem. Is that an incorrect assumption?

Just looking through the peephole here.

That if condition will always be true. Can't be what you meant.

a7

This gets to a fundamental question that I have about millis(), so I really appreciate that you brought it up.

My assumption is that millis() is an incrementing counter, so setting a variable to millis() at any time in the past would mean that it will always be smaller than millis() at any time in the future (up to ~50 days, of course). But I've noticed many programs use logic statements with a ">" when comparing [now] to [the past].

In order to measure time elapsed, you need to subtract the smaller number from the larger one. Then the goal becomes [continue doing something] (i.e. stay in the loop) until that difference becomes greater than the time I've asked for, then break out of the loop.

I've tried flipping that sign with no luck.

But the fact that so many examples use it seems to indicate to me that I'm making an incorrect assumption somewhere in the process.

Once in a while loop it will execute the code in a loop until that code tells it to exit. You need to update any variables from within the loop if the condition is going to become false

First, just look at the lines I called out. You set a variable to the current value of millis(), then immediately compare millis() to that by subtracting, the result of which is 0 (or 1 if it happens just right, or 2 actually because millis() can jump by 2)

The idiomatic pattern is to set buttonpresstime somewhere else, not in anything that is getting executed over and over.

Then the subtraction will indeed be meaningful measure of how long since.

Take a close look at other code that uses that idea, like "blink without delay". You will see how and where the variables are used to measure elapsed time.

I just spotted those two lines as obvsly not gonna do you any good, certainly not what you might have meant. I did look no further at any other of your code.

a7

there are often multiple problems and because the code still doesn't work, a correction is assumed wrong.

consider following. it was more difficult to get working using my hardware than i expected. it also assume that any action should cease as soon as the button is released, hence @J-M-L suggestion for using millis() instead of delay().

other improvements are possible, but tried to still with your code

#include <ezButton.h>
const int stepPin = 3;
const int dirPin = 4;
ezButton go_button = 5;
const int enable = 6;
const int pos_1 = 8;
const int pos_2 = 9;
const int pos_3 = 10;
const int pos_4 = 16;
const int pos_5 = 14;
const int pos_6 = 15;
const int pos_7 = 18;
const int pos_8 = 7;
int stepdelay; //Motor off time
int spintime; //Motor on time
bool spindirection; //Motor spin direction
unsigned long buttonpresstime; //Moment when the button is pressed
void setup() {
    pinMode(stepPin,OUTPUT);
    pinMode(dirPin,OUTPUT);
    pinMode(enable,OUTPUT);
    pinMode(pos_1,INPUT_PULLUP);
    pinMode(pos_2,INPUT_PULLUP);
    pinMode(pos_3,INPUT_PULLUP);
    pinMode(pos_4,INPUT_PULLUP);
    pinMode(pos_5,INPUT_PULLUP);
    pinMode(pos_6,INPUT_PULLUP);
    pinMode(pos_7,INPUT_PULLUP);
    pinMode(pos_8,INPUT_PULLUP);
    go_button.setDebounceTime(50);
    digitalWrite(enable,HIGH); //Sets initial condition of the stepper motor driver board outputs to "off"
}

void loop() {
    //Get position from rotary switch
    int pos_1_State = digitalRead(pos_1);
    int pos_2_State = digitalRead(pos_2);
    int pos_3_State = digitalRead(pos_3);
    int pos_4_State = digitalRead(pos_4);
    int pos_5_State = digitalRead(pos_5);
    int pos_6_State = digitalRead(pos_6);
    int pos_7_State = digitalRead(pos_7);
    int pos_8_State = digitalRead(pos_8);
    //Set motor output parameters based on switch position
    if (pos_1_State == LOW) {
        stepdelay = 450;
        spintime = 350;
        spindirection = HIGH;
    } else if (pos_2_State == LOW) {
        stepdelay = 550;
        spintime = 300;
        spindirection = HIGH;
    } else if (pos_3_State == LOW) {
        stepdelay = 650;
        spintime = 250;
        spindirection = HIGH;
    } else if (pos_4_State == LOW) {
        stepdelay = 750;
        spintime = 200;
        spindirection = HIGH;
    } else if (pos_5_State == LOW) {
        stepdelay = 850;
        spintime = 150;
        spindirection = HIGH;
    } else if (pos_6_State == LOW) {
        stepdelay = 950;
        spintime = 100;
        spindirection = HIGH;
    } else if (pos_7_State == LOW) { //Constant forward speed position
        stepdelay = 0;
        spintime = 32000;
        spindirection = HIGH;
    } else if (pos_8_State == LOW) { //Reverse, constant speed
        stepdelay = 0;
        spintime = 32000;
        spindirection = LOW;
    } else { //Accounting for switch in "empty" positions
        stepdelay = 0;
        spintime = 0;
        spindirection = LOW;
    }
    go_button.loop();
    int go_ButtonState = go_button.getState();
    if(go_ButtonState == 0) {
        digitalWrite(enable,LOW); //Turn on motor driver board outputs
        digitalWrite(dirPin,spindirection); //Set motor spin direction based on rotary switch position
        buttonpresstime = millis(); //Get the moment in time when the button is pressed
        if (millis() - buttonpresstime < spintime) { //Spin motor for "spintime" duration set by switch position
            digitalWrite(stepPin,HIGH);
            delayMicroseconds(100);
            digitalWrite(stepPin,LOW);
            delayMicroseconds(100);
        }
        delay(stepdelay); //Pause for duration set by switch position
        go_button.getState();
    }
    if(go_button.isReleased()){
        digitalWrite(enable,HIGH); //Turn off motor driver board outputs
    }
}

And there it still is.

a7

I think I get what you mean. Essentially, you're pointing out that this loop

if (millis() - buttonpresstime < spintime) {

is only running once, then it jumps back to the "ButtonState" loop, and that cycle repeats until the button is released. This is effectively rendering the "millis()" loop meaningless.

Ergo, I need to figure out a way to capture when the button is pressed only once, then hold that time within the secondary loop until the condition is met instead of constantly resetting the buttonpresstime.

I was really only looking at those two lines, as I am at a disadvantage in my current circumstances and can manage little else. They make no sense.

I'll recommend mastering the articles you'll find at or near the top of three google searches, viz:

arduino state change detection

arduino blink without delay

arduino two things at once

which will give you tools for making these wimpy microprocessors appear to be able to punch way above their weight.

If you catch the bug and feel like staying in learning mode, you can google two more and poke around a bit

arduino finite state machine

arduino traffic lights

Life changing. :expressionless:

a7

I have read so many posts and tried so many things, but I cannot get this to work.

The latest version of my code (posted below) is pretty much stripped right out of the Using millis() for timing... post. I've read that one as well as the BWoD post, along with the post by Robin about several things at the same time. I've read them all multiple times and have tried to apply them to my problem. I really have.

The fact that the motor turns in positions 7 and 8 seems to indicate that my actual motor driver code is okay. Thus I think the problem isn't related to a lack of stepper library as has been suggested.

I have tried changing the timing loop to be while instead of if (with the appropriate condition changes), and that didn't work either. The fact that none of the suggested articles mention anything about a while loop lead me to believe that if was the correct choice, but I thought I would try it as was suggested.

The positive change is that the motor now whines to a "beat" in positions 1-6. That is, as the stepdelay variable gets larger, the whine stop/start pattern gets slower. The whine/stop cycle seems to be in line with the value of the stepdelay variable for each position, at least to the accuracy of my ears. So the motor is doing something "correct" related to timing. That being said, the only way the motor actually turns is in positions 7 and 8, where the stepdelay variable is 0.

I understand that the ethos of this forum (like most others) is to "teach a man to fish" rather than just putting some fish on his plate. I want to respect that and am not expecting someone to write a fresh new sketch for me.

Maybe I'm just dense? I don't think so. I have an engineering degree, so I feel like I should be able to grasp this. Alas, it is escaping me. Perhaps I need a new fishing instructor?

If you'll humor me some more, I would certainly be grateful. Thank you. :pray:

Current code:

#include <ezButton.h>

const int stepPin = 3; 
const int dirPin = 4;
ezButton go_button = 5; 
const int enable = 6;
const int pos_1 = 8;
const int pos_2 = 9;
const int pos_3 = 10;
const int pos_4 = 16;
const int pos_5 = 14;
const int pos_6 = 15;
const int pos_7 = 18;
const int pos_8 = 7;
unsigned long stepdelay; //Motor off time
bool spindirection; //Motor spin direction
unsigned long spintime;
unsigned long currentmillis; 

void setup() {
  pinMode(stepPin,OUTPUT); 
  pinMode(dirPin,OUTPUT);
  pinMode(enable,OUTPUT);
  pinMode(pos_1,INPUT_PULLUP);
  pinMode(pos_2,INPUT_PULLUP);
  pinMode(pos_3,INPUT_PULLUP);
  pinMode(pos_4,INPUT_PULLUP);
  pinMode(pos_5,INPUT_PULLUP);
  pinMode(pos_6,INPUT_PULLUP);
  pinMode(pos_7,INPUT_PULLUP);
  pinMode(pos_8,INPUT_PULLUP);
  go_button.setDebounceTime(50);
  digitalWrite(enable,HIGH); //Sets initial condition of the stepper motor driver board outputs to "off"
  spintime = millis(); //Initial spin time value
}

void loop() {
  //Get position from rotary switch
  int pos_1_State = digitalRead(pos_1);
  int pos_2_State = digitalRead(pos_2);
  int pos_3_State = digitalRead(pos_3);
  int pos_4_State = digitalRead(pos_4);
  int pos_5_State = digitalRead(pos_5);
  int pos_6_State = digitalRead(pos_6);
  int pos_7_State = digitalRead(pos_7);
  int pos_8_State = digitalRead(pos_8);

  //Set motor output parameters based on switch position
  if (pos_1_State == LOW) {
    stepdelay = 450;
    spindirection = HIGH;
  } else if (pos_2_State == LOW) {
    stepdelay = 550;
    spindirection = HIGH;
  } else if (pos_3_State == LOW) {
    stepdelay = 650;
    spindirection = HIGH;
  } else if (pos_4_State == LOW) {
    stepdelay = 750;
    spindirection = HIGH;
  } else if (pos_5_State == LOW) {
    stepdelay = 850;
    spindirection = HIGH;
  } else if (pos_6_State == LOW) {
    stepdelay = 950;
    spindirection = HIGH;
  } else if (pos_7_State == LOW) { //Constant forward
    stepdelay = 0;
    spindirection = HIGH;
  } else if (pos_8_State == LOW) { //Constant Reverse
    stepdelay = 0;
    spindirection = LOW;
  } else { //Accounting for switch in "empty" positions
    stepdelay = 320000;
    spindirection = LOW;
  }
    
  go_button.loop();
  int go_ButtonState = go_button.getState();
  
  if(go_ButtonState == 0) 
  {
    digitalWrite(enable,LOW); //Turn on motor driver board outputs
    digitalWrite(dirPin,spindirection); //Set motor spin direction based on rotary switch position
    currentmillis = millis(); //Get the time when the button is pressed
    if (currentmillis - spintime >= stepdelay)  //Stop motor for "stepdelay" duration set by switch position
    {
      digitalWrite(stepPin,HIGH); 
      delayMicroseconds(100); 
      digitalWrite(stepPin,LOW); 
      delayMicroseconds(100);
      spintime = currentmillis;
     }
    go_button.getState();
  }
  
  if(go_button.isReleased())
  {
    digitalWrite(enable,HIGH); //Turn off motor driver board outputs
  }

}

How is the motor powered?

I would suggest to strip down the code even further and only keep one case vale and not even check the position of the button to see if the motor is indeed ticking

I don’t know the button library you use but You write

if(go_ButtonState == 0) 

Isn’t there a better API to know if the button is pressed rather than testing against 0? Feels poor especially as later you do if(go_button.isReleased())

doubt this does what you expect because spintime and stepdelay are unrelated. it would make more sense to capture a timestamp when the motor is initially pressed and step the motor as long as the difference between the currentMillis and that timestamp are < spintime.

the problem then is to determine that the motor should not be paused. this is where a state come in

presumably the reason for making this conditional based on the state of the buttton is to stop the motor as soon as the button is released

i've also found that stepper motors can't step faster than 1 step/msec. maybe this is an inertia issue and they need to be accelerated properly. but have you tried a larger pulse period (> 500 usec)

spintime Seems to be when the last step was taken - so it should work (the variable name is not well chosen)