How to exit from a WHILE cycle while stepper is running

Hello,

for a project I have the need to activate a stepper motor when I press a button and to stop it when I press the same button again.

For this reason I've created this bit of code:

void runmotor(){
    runactual = digitalRead(runpin);
  if (runactual == LOW && runbefore == HIGH){
    runmode = !runmode;
    runbefore = runactual;
  }
  else if (runactual == HIGH && runbefore == LOW){  
    runbefore = runactual;   
  }
}

Which read the button "runpin" (set as INPUT_PULLUP so not to use a resistor) and changes the variable "runmode" ON an OFF.

Then I've tried to write this WHILE cycle with no success:

  while(runmode == true){
    if(runmode == false) break;
    stepper.setSpeed(stepperspeed);
    stepper.runSpeed();
  }
}

The problem is that once the motor start running it seems like Arduin doesn't read the button state any longer and so I am not able to change the state of variable "runmode" any longer.

Have you got any suggestions?

I've also tried, instead of the WHILE cycle an IF cycle but the motor doesn't run smoothly as, I guess, it keeps on reading the button interrupting the cycle of the motor spinning.

Thank you very much for your precious support!

Alessandro

The problem is that once the motor start running it seems like Arduin my code doesn't read the button state

Fixed that for you.

break;

You appear to be using the AccelStepper library?

Can you post all the code please, not just snippets, explaining what it should be doing and
what its actually doing.

My initial feeling is you should not be using while at all, that's what loop() is for.

The problem is that once the motor start running it seems like Arduin doesn't read the button state any longer and so I am not able to change the state of variable "runmode" any longer.

Perhaps you mean :

The problem is that once the motor starts running your code doesn't read the button again and so the state of variable "runmode" never changes in the while loop

Solution : do not use a while loop

Ale_V:
The problem is that once the motor start running it seems like Arduin doesn't read the button state any longer and so I am not able to change the state of variable "runmode" any longer.

Perhaps, as already pointed out, because you didn't read the button state in the while loop?

Have you got any suggestions?

Read the button state in the while loop, or much better replace with an if so your code is non-blocking.

I've also tried, instead of the WHILE cycle an IF cycle but the motor doesn't run smoothly as, I guess, it keeps on reading the button interrupting the cycle of the motor spinning.

There doesn't seem to be sufficient code in runmotor to cause that.
My guess would be there are substantial delays or looping in whatever is calling runmotor().
But as you don't present that code, we can't help.

I've re-written the code in an easier form.
I'm sorry for not having posted the whole code but it is extremely long (as the same script does dozen of other things).

// LIBRARIES
#include <AccelStepper.h>

// Stepper Variables
#define dirPin 8
#define stepPin 9
#define motorInterfaceType 1
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);
int steppermaxspeed = 4000;
int stepperacceleration = 8000;
byte M0 = 12;
byte M1 = 11;
byte M2 = 10;

// RUN VARIABLE
const int runpin = 7;
byte runactual;
byte runbefore;
static boolean runmode = false;

void setup() {
  Serial.begin(9600);
  
  digitalWrite(M0, LOW); digitalWrite(M1, HIGH); digitalWrite(M2, LOW); // Set 1/4 Steps

  // Quick test to see if the motor work
  stepper.setMaxSpeed(steppermaxspeed);
  stepper.setAcceleration(stepperacceleration);
  stepper.setCurrentPosition(0);
  stepper.moveTo(8000); // 800 (single rotation in 1/4 Steps) * 10 Rotations
  stepper.runToPosition();

  // RUN 
  pinMode(runpin, INPUT_PULLUP);
  runbefore = digitalRead(runpin);
  
}

void loop() {
  runactual = digitalRead(runpin);
  if (runactual == LOW && runbefore == HIGH){
    runmode = !runmode;
    Serial.println(runmode);
    runbefore = runactual;
  }
  else if (runactual == HIGH && runbefore == LOW){  
    runbefore = runactual;   
    Serial.println(runmode);
  }
  delay(10);


    while(runmode == true){
    if(runmode == false) break;
    stepper.setSpeed(200);
    stepper.runSpeed();
  }
}

As I said if I use an IF statement the motor doesn't run properly.
I guess that using a LOOP cycle wouldn't help as well as I need the motor to keep on spinning "while" the condition is true and stop when it is not (when I press the button again), not for a number of cycles.

Many thanks for your help,

Alessandro

I guess that using a LOOP cycle wouldn't help as well as I need the motor to keep on spinning "while" the condition is true and stop when it is not (when I press the button again), not for a number of cycles.

Using the loop() function to repeat code does not imply doing it for a fixed number of times. I think that you are confusing the loop() function with a for loop.

Detect when the button becomes pressed in loop() and change the state of a boolean variable each time you detect a change of button state to pressed. Use the value of the boolean to determine whether the motor should make another step or not

Here's your problem...

delay(10);

At the very least...
Do your debounce delay ONLY when the button changes state.

The ideal solution would be to do your debounce timing using millis() as demonstrated in the Debounce Tutorial.

without showing your complete code the real reason might stay hidden.

Steppermotors must have their pulses in a very regular pattern. Of course you can accelerarte and deccelerate stepper-motors but this is still aregular pattern. If you try to do things that need more than a few milliseconds the pulsetrain of the step-pulses became hickups.

So it all depends on what stepper-library you were using and if you use a blocking step-signal-creation or a non-blocking step-signal-creation.

If steppers should run smoothly on higher rpms and/or with microstepping best thing to do is to dedicate another small microcontroller-board to do nothing else but the step-signal-creation.

If it is really nescessary depends on the maximum-speed the microstepping factor (halfsteps or 1/8 or 1/32-steps)
and on the other things you want to do in parallel with the step-signal-creation
calculation-example: max
speed 1200 rpm = 20 rotations per second with 1/32-microsteps means 640 step-signals per second.
So running through the complete loop has to be finished in 1/640 = 1,5 milliseconds. There would be no time left for
transferring a lot of data to a display through a SPI or I2C-interface.

So please describe your requierements fastest rpm of the stepper-motor microstepping to which grade
halfsteps, quartersteps etc. and what you want to do in parallel with the stepper running

So please if your code exceeds the 9000 character-limit of postings just attach it as attachement.

best regards Stefan

Thank you again for your answers!

So far I arrived here:

// LIBRARIES
#include <AccelStepper.h>

// Stepper Variables
#define dirPin 8
#define stepPin 9
#define motorInterfaceType 1
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);
int steppermaxspeed = 4000;
int stepperacceleration = 8000;
byte M0 = 12;
byte M1 = 11;
byte M2 = 10;
long stepperspeed = 200;

// RUN VARIABLE
const int runpin = 7;
byte runactual;
byte runbefore;
static boolean runmode = false;

//DEBOUNCING VARIABLES
long t = 0;
long debounce_delay = 200;

//--------------------
// RE Variables
#define CLK 4
#define DATA 5

static uint8_t prevNextCode = 0;
static uint16_t store=0;

//--------------------

void setup() {

  pinMode(CLK,INPUT_PULLUP);
  pinMode(DATA,INPUT_PULLUP);
  
  Serial.begin(9600);
  
  digitalWrite(M0, LOW); digitalWrite(M1, HIGH); digitalWrite(M2, LOW); // Set 1/4 Steps
  stepper.setMaxSpeed(steppermaxspeed);
  stepper.setAcceleration(stepperacceleration);
  stepper.setCurrentPosition(0);
  stepper.moveTo(8000); // 800 (single rotation in 1/4 Steps) * 10 Rotations
  stepper.runToPosition();

  // RUN 
  pinMode(runpin, INPUT_PULLUP);
  runbefore = digitalRead(runpin);
  
}

void readbutton(){
    runactual = digitalRead(runpin);
  if (runpin){
    if ((millis()-t) > debounce_delay){
        if (runactual == LOW && runbefore == HIGH){
        runmode = !runmode;
        
        Serial.println(runmode);
        runbefore = runactual;
        }
      else if (runactual == HIGH && runbefore == LOW){  
        runbefore = runactual;   
        
        Serial.println(runmode);
        }
    }
  }
}

void loop() {
    readbutton();
    while(runmode == true){
      readbutton();
      if(runmode == false) break;
      stepper.setSpeed(stepperspeed);
      stepper.runSpeed();     
  }

  // RE READING
      static int8_t c,val;
      
         if( val=read_rotary() ) {
            c +=val;
      
            if ( prevNextCode==0x0b) {
               stepperspeed = stepperspeed-100;
               Serial.println(stepperspeed);
            }
      
            if ( prevNextCode==0x07) {
               stepperspeed = stepperspeed+100;
               Serial.println(stepperspeed);
            }
         }
      }
      
      // A vald CW or  CCW move returns 1, invalid returns 0.
      int8_t read_rotary() {
        static int8_t rot_enc_table[] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0};
      
        prevNextCode <<= 2;
        if (digitalRead(DATA)) prevNextCode |= 0x02;
        if (digitalRead(CLK)) prevNextCode |= 0x01;
        prevNextCode &= 0x0f;
      
         // If valid then store as 16 bit data.
         if  (rot_enc_table[prevNextCode] ) {
            store <<= 4;
            store |= prevNextCode;
            //if (store==0xd42b) return 1;
            //if (store==0xe817) return -1;
            if ((store&0xff)==0x2b) return -1;
            if ((store&0xff)==0x17) return 1;
         }
         return 0;
}

I am sorry that the code isn't that clear, I'll try to clean it up a bit.
I've added a debouncing code to the button and created a dedicated function. Now it works, even if sometimes it makes some small mistakes.
I've also addes a rotary encoder to vary the speed of the stepper motor.
The final think I'd like to add is a I2C screen but I am quite used with that.

Couple more questions:
as I am using this 200 steps stepper motor (down to 1/4 steps), it means it takes 800 steps to complete 360°. Therefore setting a speed of 800 should equal 1 RPM, am I right?
I've seen (making some tests) that increasing the speed over 4000 steps per minute is basically pointless, is it correct for such a motor (I am using it together with a DRV8825).
Do you think I may vary the speed while the motor is running?

Thank you very much!

Alessandro

Hi Allessandro,

I'm not sure what the setspeed exactly is. which stepper-motor-library are you using and what does the docs of the library or some examples say?

4000 steps per minute would be 4000/60 = 66.67 steps per second = 800 steps/rotation / 66.67 steps per second = 12 seconds for one rotation.

I guess you meant 4000 steps per second = 5 rotations per second = 300 rpm

With increasing rpm the torque of stepper-motors goes down. The torque also depends on the current.
And a little bit of the voltage.

So what is the maximum speed that you want to drive the stepper-motor?

You can vary the rpm of a stepper-motor within borders. It is not possible from 0 RPM to instantly go up to 2000 rpm.
Then you will loose steps or maybe the steppermotor is just wiggling but not turning.
Depending on the load different maximum rpms are possible.
So the real maximum rpm you will only know if the mechanical load is the load that the finished project does have.

Turning an encoder as fast as possible with each signal means change rpm by 10 wil surely make the motor stall.
again depends on the stepper-motors perfomance and the mechanical load.

As a general hint: connect/disconnect the coilwires of the stepper-motor only with power-supply OFF.
Otherwise voltage-spikes will destroy the driver.

you seem a little bit lazy about answering questions. You can do so. I'm not able to force you. The only thing is with small experience you might have more trouble or at a late state of your project it turns out to be impossible to finish the way you started. But of course it is up to you how much you tell about the whole project

best regards Stefan

if (runpin){

That expressing is always true (runpin = 7). So it's pointless.

long t = 0;
long debounce_delay = 200;

times should be unsigned. Use "uint32_t", or "unsigned long" if you must.

t is a terrible name for a variable. I searched your code for where you set it. It appears as though you don't.

Therefore...

if ((millis()-t) > debounce_delay){

...is permanently true* 200ms after your program starts, and so your debounce delay is completely ineffective.

void readbutton(){
    uint32_t now = millis();
    if (now - t > debounce_delay) {
      runactual = digitalRead(runpin);
      if (runactual == LOW && runbefore == HIGH) {
        runmode = !runmode;
        Serial.println(runmode);
        runbefore = runactual;
      } else if (runactual == HIGH && runbefore == LOW) { 
        Serial.println(runmode);
        runbefore = runactual;          
      }
      t = now;
   }
}

*Or at least until the millis() clock wraps at 47+ days.

Hello Everyone and sorry for my late reply but I had some very busy days at work.

@StefanL38:
Sorry for giving you the impression of being lazy, it is actually the contrary, I simply tried to keep my posts as short as possible as I thought not many people would be willing to read an amazingly long post.
Thank you very much for your answer, I'll try to be more specific here.

@pcbbc:
You are right, I have forgotten to add a line of code to update t = millis();
Here is the correct bit of code:

void readbutton(){
    runactual = digitalRead(runpin);
  if (runpin){
    if ((millis()-t) > debounce_delay){
        if (runactual == LOW && runbefore == HIGH){
        runmode = !runmode;
        
        Serial.println(runmode);
        runbefore = runactual;
        }
      else if (runactual == HIGH && runbefore == LOW){  
        runbefore = runactual;   
        
        Serial.println(runmode);
        }
    t = millis();
    }
  }
}

Going back to the main topic...
The aim of the project is to create an experimental rolling shutter for a camera.
I've 3D printed the whole case and various "blades" for the shutter. 180°, 90°, etc...
The diameter is around 20cm and they are connected with a bolt to the shaft of the motor.
They need to rotated at various speed (which I modify with a rotary encoder).

I've set up a test which measure the time for 100 rotations:

 digitalWrite(M0, LOW); digitalWrite(M1, HIGH); digitalWrite(M2, LOW); // Set 1/4 Steps
  stepper.setMaxSpeed(steppermaxspeed);
  stepper.setAcceleration(stepperacceleration);
  stepper.setCurrentPosition(0);
  steppertestbegin = millis();
  stepper.moveTo(80000); // 800 (single rotation in 1/4 Steps) * 100 Rotations
  stepper.runToPosition();
  steppertestend = millis();
  testresult = (float)(steppertestend-steppertestbegin)/1000;
  Serial.println("Speed Test");
  Serial.print("Stepper Speed: "); Serial.println(steppermaxspeed);
  Serial.print("Stepper Acceleration: "); Serial.println(stepperacceleration);
  Serial.print("Time to complete 10 rotations: "); Serial.println(testresult);
  Serial.print("Average time to complete 1 rotation: "); Serial.println(testresult/100);

The result seems to be okay to me:

11:21:02.735 -> Speed Test
11:21:02.735 -> Stepper Speed: 4000
11:21:02.768 -> Stepper Acceleration: 8000
11:21:02.768 -> Time to complete 10 rotations: 20.97
11:21:02.835 -> Average time to complete 1 rotation: 0.21

If the speed is 4000 steps per second and it takes 800 steps to make a turn it should make 5 revolution per second: it reports 0.21 sec per rotation which, considering the acceleration, should be good.

Here is the complete code for the moment:
MAIN

// LIBRARIES
#include <AccelStepper.h>

// METHODS DECLARATIONS
void readbutton();
void encoder();


// Stepper Variables
#define dirPin 8
#define stepPin 9
#define motorInterfaceType 1
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);
int steppermaxspeed = 4000;
int stepperacceleration = 8000;
byte M0 = 12;
byte M1 = 11;
byte M2 = 10;
long stepperspeed = 200;
byte singlestep = 4;

// STEPPER TEST VARIABLES
uint32_t steppertestbegin;
uint32_t steppertestend;
 float testresult;

// RUN VARIABLE
const int runpin = 7;
byte runactual;
byte runbefore;
static boolean runmode = false;

//DEBOUNCING VARIABLES
uint32_t now = 0;
long debounce_delay = 200;

// -----------------------------------------------------------------------------
// RE Variables
byte pinClk   [] = { 2,4 };
byte pinData  [] = { 3,5 };

#define N_ENC   sizeof(pinData)     // determine # of encoders

// allocate state variables for each encoder (i.e. N_ENC)
int  encState   [N_ENC] = {};
int  encInp     [N_ENC] = {};
long encPos     [N_ENC] = {};
long prevencPos [N_ENC] = {};
int res;

// -----------------------------------------------------------------------------
// A vald CW or  CCW move returns 1, invalid returns 0.
int8_t read_rotary (
    int  & store,
    int  & prevNextCode,
    byte data,
    byte clk )
{
    static int8_t rot_enc_table[] = {
        0,1,1,0,   1,0,0,1,
        1,0,0,1,   0,1,1,0 };

    prevNextCode <<= 2;
    prevNextCode  |= data << 1;
    prevNextCode  |= clk;
    prevNextCode  &= 0x0F;

    // If valid then store as 16 bit data.
    if  (rot_enc_table [prevNextCode] ) {
        store <<= 4;
        store  |= prevNextCode;
        store  &= 0xFF;

        if (store == 0x2b) {
          return -1;
        }
        if (store == 0x17) {
          return 1;
        }
    }

    return 0;   // invalid input
}

// -----------------------------------------------------------------------------

void setup() {
  // -----------------------------------------------------------------------------
  // RE Setup
  for(int i = 0; i < N_ENC; i++){
    char info [100];
    sprintf(info, "Clk Input number %d: %d",i, pinClk[i]);
    Serial.println(info);
    sprintf(info, "Data Input number %d: %d",i, pinData[i]);
    Serial.println(info);    
  }
  
  for (int i=0; i < N_ENC; i++){
    pinMode (pinData[i], INPUT_PULLUP);
    pinMode (pinClk[i], INPUT_PULLUP);
  }
  // -----------------------------------------------------------------------------
  
  Serial.begin(9600);
  
  digitalWrite(M0, LOW); digitalWrite(M1, HIGH); digitalWrite(M2, LOW); // Set 1/4 Steps
  stepper.setMaxSpeed(steppermaxspeed);
  stepper.setAcceleration(stepperacceleration);
  stepper.setCurrentPosition(0);
  steppertestbegin = millis();
  stepper.moveTo(80000); // 800 (single rotation in 1/4 Steps) * 100 Rotations
  stepper.runToPosition();
  steppertestend = millis();
  testresult = (float)(steppertestend-steppertestbegin)/1000;
  Serial.println("Speed Test");
  Serial.print("Stepper Speed: "); Serial.println(steppermaxspeed);
  Serial.print("Stepper Acceleration: "); Serial.println(stepperacceleration);
  Serial.print("Time to complete 10 rotations: "); Serial.println(testresult);
  Serial.print("Average time to complete 1 rotation: "); Serial.println(testresult/100);

  // RUN 
  pinMode(runpin, INPUT_PULLUP);
  runbefore = digitalRead(runpin);
  
}



void loop() {
    readbutton();
    while(runmode == true){
          for (int i = 0; i < N_ENC; i++) {
        encoder (i);
        
    }
      readbutton();
      if(runmode == false) break;
      stepper.setSpeed(stepperspeed);
      stepper.runSpeed();     
  }
    for (int i = 0; i < N_ENC; i++) {
        encoder (i);
        
    }
}

TAB 1

// -----------------------------------------------------------------------------

void readbutton(){
    runactual = digitalRead(runpin);
  if (runpin){
    if ((millis()-now) > debounce_delay){
        if (runactual == LOW && runbefore == HIGH){
        runmode = !runmode;
        
        Serial.println(runmode);
        runbefore = runactual;
        }
      else if (runactual == HIGH && runbefore == LOW){  
        runbefore = runactual;   
        
        Serial.println(runmode);
        }
    now = millis();
    }
  }
}

// -----------------------------------------------------------------------------

void encoder (int  idx ) {
    res = read_rotary (encState [idx], encInp [idx],
                            digitalRead (pinData [idx]),
                            digitalRead (pinClk  [idx]) );
                            
    encPos [idx] += res;
    // -----------------------------------------------------------------------------
    // ENCODER 1
    if (res == -1 && idx == 0) {
      stepper.setCurrentPosition(0);
      stepper.moveTo(-singlestep);
      stepper.runToPosition();
    }
    if (res == 1 && idx == 0) {
      stepper.setCurrentPosition(0);
      stepper.moveTo(singlestep);
      stepper.runToPosition();
    }
    // -----------------------------------------------------------------------------
    // ENCODER 2
    if (res == -1 && idx == 1) {
      stepperspeed = stepperspeed -100;
      Serial.println(stepperspeed);
    }
    if (res == 1 && idx == 1) {
      stepperspeed = stepperspeed +100;
      Serial.println(stepperspeed);
    }
    // -----------------------------------------------------------------------------

    
    if (res)  {
        char s [80];
        sprintf (s, " %d: %02X %2d", idx, encState [idx], encPos [idx]);
        Serial.println (s);
    }
}

// -----------------------------------------------------------------------------

't' is a fine name for a variable if it represents time, this is the de-facto variable for time in physics.

Alas its a poor choice for a global variable as it could easily clash - it should probably be a locally
declared static variable so its scope is local but extent is global.

MarkT:
't' is a fine name for a variable if it represents time, this is the de-facto variable for time in physics.

Alas its a poor choice for a global variable as it could easily clash - it should probably be a locally
declared static variable so its scope is local but extent is global.

I'm aware of that, and in that regard it is marginally better than some other entirely random letter. For use as a local variable with limited scope 't' would be fine.

I misspoke. I should have said "terrible name for a global variable", which it seems we can agree on.

Not only could it clash, there's no indication of which particular "time" it represents and it's next to impossible to search the supplied sketch for where it is set.

@Alex_V you still have not corrected/removed this erroneous line...

if (runpin){

What was your intention when writing it?

As "const int runpin = 7" that line is the equivalent of...

if (7){