How to make a motor do a second task in a loop without interrupting the others?

Hello!! I have a question about how to handle this situation; I am making a camera slider. As you can see, when the program recieves a command to go to the right, it starts to run a sliding motor, a pan motor which rotates the camera from left to right, and a tilt motor, which until the middle of the sliding path gradually tils the camera downwards to position -2000 and afterwards starts to tilt it back up to position 0. I want the three motors to work silmutaneously. I am not sure if first writing for the stepperTilt to go to position 2000, and then under it writing for it to go back to position 0 will work in order. Should i make a separate conditional statement for it and how could i do that without interrupting the work of the other two motors? Thank you a ton for any help!

Here is a simple portrayal of the movements;

  
  if (bluetoothSerial.available()) {
    String command = bluetoothSerial.readString();               
    if (command == "right") {                    //if it recieves "right", there three motors 
      stepperSlide.setSpeed(3000);                 begin to work simultaneously
      stepperSlide.moveTo(60000); 
      stepperSlide.run();                 // the sliding movement
      
      stepperPan.setSpeed(500);
      stepperPan.moveTo(4000);
      stepperPan.run();                 // panning of the camera
      
      stepperTilt.setSpeed(300);
      stepperTilt.moveTo(2000); 
      stepperTilt.run();             //tilting downwards initially

      stepperTilt.setSpeed(300);
      stepperTilt.moveTo(0);                                      
      stepperTilt.run();            //then the tilt goes back to position 0.. Here is where the
                                      problem lies.. Could that work?
      } 
                                                                                    
        
                                                                         
   //Should i  use something like this?  But i think that would interrupt the upper if statement :/
     
        if (stepperTilt.distanceToGo() != 0) {

       stepperTilt.setSpeed(300);
      stepperTilt.moveTo(0);                                      
      stepperTilt.run();            
  }     
     

What is the rest of the code? Does this snippet work for you except for the 2nd motion?

If it works, it looks like it would only run through this snippet once, when it gets&matches the bluetooth command and sets these target positions. Then perhaps in the main loop() it has the stepperSlide.run(); stepperPan.run(); stepperTilt.run(); that get called every loop() and proceed to target.

If that's not how the current full code works, it probably needs a re-write to do the non-blocking and parallel stepperX.run() work as a mix between

https://www.airspayce.com/mikem/arduino/AccelStepper/MultipleSteppers_8pde-example.html

and the target-updating trick in:

https://www.airspayce.com/mikem/arduino/AccelStepper/Bounce_8pde-example.html

...along with some higher-level mode-switching logic to track new bluetooth commands versus finishing sets of motions. For instance, what do you want to happen if you received a second bluetooth command while partially through the set of motions?

1 Like

i believe the motors need to be run one step at a time at the rate of the one that needs to run the most within a timer loop (e.g. millis()). at various times, the other motors will need to be stepped.

presumably the motor sliding the camera needs to be stepped the most frequently. you can consider the rates of motion (steps/sec) of all the motors and at each interval update there position as a float. it's time to step one of the secondary motors when the integer part of their position is different from the previous integer position value

looks like the slide and pan motors will move at a constant rate, but the tilt motor will reverse at the mid-point

1 Like

The rest of the code might tell us more. With the class names in the snippet, I was assuming that the code was using AccelStepper, and that AccelStepper would handle the millis() timing within the *.run() calls in a free-running loop(); I also was assuming the differential speeds in the snippet were appropriate.

But of course, my assumptions could be completely wrong.

...but I can't help it. I'd further assume I should structure the main code to have a checkpoint at the midpoint, and have some state variable keep track of whether it was IDLE, HEADED_TO_MIDPOINT, HEADED_TO_END, or HEADED_HOME, and set up the AccelStepper targets accordingly, like this completely untested snippet:

switch (motionState){

     ...
     case HEADED_TO_MIDPOINT:
          if (stepperSlide. distanceToGo() == 0){ 
             stepperSlide.moveTo(60000);
             stepperTilt.moveTo(0);
             ...
             motionState = HEADED_TO_END;         
          }
          break;
     case HEADED_TO_END:
          if (stepperSlide. distanceToGo() == 0){ 
             stepperSlide.moveTo(0);
             stepperTilt.moveTo(0);
             ...
             motionState = HEADED_HOME;         
          }
          break;
   ...
   }
1 Like

i have no experience with AccelStepper, but these assumption seem reasonable.

does that mean that loop() can simply set a timer so that at the midpoint in time, the tilt motor can simply be set with a new target step? no need to change the speed?

Thank you so much, and apologies for the late reply! I wish i could test the code , but some of the parts havent arrived yet and due date is approaching soon, so trying my best :confused:
The code i've written before has been a complete mess so i think its best to start again.

Based on your initial tips from before I've come up with a simple program,
where i have a go to right button, go to left button and a stop button. I've also incorporated some limit switches, which allow me to home the Sliding motor, plus limit the movement so erros wont happen. But sadly, i am again not sure if the way I've set up if statements here could actually work, or would the other motors be interrupted :0 I gotta admit, I am a little afraid of going into a complicated code, given that i barely understand any of this

Here's what i've put together:

#include <AccelStepper.h>
#include <SoftwareSerial.h> // bluetooth communication with the HC-05 module


// the stepper motors and the pins the will use 
AccelStepper stepperSlide(1, 7, 6); // (Type:driver, STEP, DIR)
AccelStepper stepperPan(1, 5, 4);
AccelStepper stepperTilt(1, 3, 2);

#define limitSwitchLeft 8
#define limitSwitchRight 9
SoftwareSerial bluetoothSerial(0, 1); // RX, TX
String command; // String for storing received commands

 
 
void setup()  {
  
// Move the slider to the initial position - homing

while (digitalRead(limitSwitchLeft) != 0) {  
    stepperSlide.setSpeed(3000);                                  
    stepperSlide.runSpeed();                                      
    stepperSlide.setCurrentPosition(0);                           
    stepperPan.setCurrentPosition(0);
    stepperTilt.setCurrentPosition(0);
    }

 //stepper settings  
 {
    stepperSlide.setMaxSpeed(400.0);
    stepperSlide.setAcceleration(100.0);
    stepperSlide.moveTo(60000);
    
    stepperPan.setMaxSpeed(100.0);
    stepperPan.setAcceleration(100.0);
    stepperPan.moveTo(4000);
    
    stepperTilt.setMaxSpeed(50.0);
    stepperTilt.setAcceleration(100.0);
    stepperTilt.moveTo(2000);
   
  }


    
}

void loop() {

      if (bluetoothSerial.available()) {
    String command = bluetoothSerial.readString();

   // condition to go to the right works, if the "right" command is received + the right limit switch isnt pressed yet
     
       if ((command == "right") && (digitalRead(limitSwitchRight) != 0)) {      
        if (stepperTilt.distanceToGo() == 0)
            stepperTilt.moveTo(-stepperTilt.currentPosition());
        stepperSlide.run();
        stepperPan.run();
        stepperTilt.run();
    }

 // condition to go to the left works, if the "left" command is received + the left limit switch isnt pressed yet
      
  else if ((command == "left") && (digitalRead(limitSwitchLeft) != 0)) {          

      
     if (stepperTilt.distanceToGo() == 0)
          stepperTilt.moveTo(-stepperTilt.currentPosition());
     stepperSlide.moveTo(-stepperSlide.currentPosition());
     stepperPan.moveTo(-stepperPan.currentPosition());
     
     stepperSlide.run(); 
     stepperPan.run();
     stepperTilt.run();
    
  }
  else if (command = "stop") {               // if received command "Stop", the slider stops
   stepperSlide.stop();
   stepperTilt.stop() ;
   stepperPan.stop();
  }
 }
}

The snippet of code looks a bit confused. AccelStepper can manage acceleration, and given a maximum speed and accelleration, you can(asynchonosly) give it a target position and it adjusts it plan to match.

See the Accelstepper Bounce example -- it slows and reverses at both ends of travel, reaching maxSpeed for a period in between. The targets are only re-set when the motor has stopped, and in-between, the free-running loop is calling only the stepper.run() method.

https://www.airspayce.com/mikem/arduino/AccelStepper/Bounce_8pde-example.html

AccelStepper also has some constant speed modes, and some blocking modes, but instead of stepper.run() one calls [stepper.runSpeed()](AccelStepper: AccelStepper Class Reference) to do the non-blocking constant speed processing, or stepper.runSpeedToPosition() to do a blocking motion.

Where the current code seems confused is that it's calling run() and run() makes at most one step, and uses the off-screen acceleration settings to adjust the speed. If there's a loop around the shown snippet that also has the *.run()s in it, it would keep moving towards the moveTo() targets under the acceleration constraints. If there isn't a loop, maybe the OP needs something like the runSpeedToPosition() method instead of run().

I'd move this chunk out to the part of loop that executes every time:

        stepperSlide.run();
        stepperPan.run();
        stepperTilt.run();

Think of it as let the steppers move towards where I told them to moveTo. You need them outside of the if(bluetoothSerial...) stuff, because otherwise you only get one chance to make one step for each new bluetooth command.

You can't use the blocking runToPosition() or runSpeedToPosition() methods because you need to move the other steppers at the same time.

The switching-directions of the tilt is a bit trickier, and you might set it up as two movements, the bluetooth command sets a signal and starts moving toward the middle target, and then once you get to the middle, you update the targets and move towards the end.

Here is some completely untested code that might do some of the things you need:

#include <AccelStepper.h>
#include <SoftwareSerial.h> // bluetooth communication with the HC-05 module


// the stepper motors and the pins the will use 
AccelStepper stepperSlide(1, 7, 6); // (Type:driver, STEP, DIR)
AccelStepper stepperPan(1, 5, 4);
AccelStepper stepperTilt(1, 3, 2);

#define limitSwitchLeft 8
#define limitSwitchRight 9
SoftwareSerial bluetoothSerial(0, 1); // RX, TX
String command; // String for storing received commands

int motionState = 0; // 0= idle, 1= toMiddle, 2= toEnd, 3=home; 
 
void setup()  {
  
// Move the slider to the initial position - homing

while (digitalRead(limitSwitchLeft) != 0) {  
    stepperSlide.setSpeed(3000);                                  
    stepperSlide.runSpeed();                                      
    stepperSlide.setCurrentPosition(0);                           
    stepperPan.setCurrentPosition(0);
    stepperTilt.setCurrentPosition(0);
    }

 //stepper settings  
 {
    motionState = 2; // these targets are aiming towards the end???
    stepperSlide.setMaxSpeed(400.0);
    stepperSlide.setAcceleration(100.0);
    stepperSlide.moveTo(60000);
    
    stepperPan.setMaxSpeed(100.0);
    stepperPan.setAcceleration(100.0);
    stepperPan.moveTo(4000);
    
    stepperTilt.setMaxSpeed(50.0);
    stepperTilt.setAcceleration(100.0);
    stepperTilt.moveTo(2000);
  }
}

void loop() {

      if (bluetoothSerial.available()) {
    String command = bluetoothSerial.readString();

   // condition to go to the right works, if the "right" command is received + the right limit switch isnt pressed yet
     
       if ((command == "right") && (digitalRead(limitSwitchRight) != 0)) {
        motionState = 1 ; // start moving right towards middle      
        if (stepperTilt.distanceToGo() == 0)
            stepperTilt.moveTo(-stepperTilt.currentPosition());
    }

 // condition to go to the left works, if the "left" command is received + the left limit switch isnt pressed yet
      
  else if ((command == "left") && (digitalRead(limitSwitchLeft) != 0)) {          

      
     if (stepperTilt.distanceToGo() == 0)
          stepperTilt.moveTo(-stepperTilt.currentPosition());
     stepperSlide.moveTo(-stepperSlide.currentPosition());
     stepperPan.moveTo(-stepperPan.currentPosition());    
  }
  else if (command = "stop") {               // if received command "Stop", the slider stops
   stepperSlide.stop();
   stepperTilt.stop() ;
   stepperPan.stop();
  }
 }

// handle mid-travel changes:
if(motionState == 1 && stepperTilt.distanceToGo() == 0){ // reached center
      stepperTilt.moveTo(-stepperTilt.currentPosition()) ; //change dirs
   motionState = 2;
}

 // check limit switches
 if( (motionState ==1  || motionState ==2) && digitalRead(limitSwitchRight) == 0){
   // stop and go back to 0;
   motionState = 3;
    stepperSlide.moveTo(0);
    stepperTilt.moveTo(0);
    stepperPan.moveTo(0);
 }
 if( (motionState ==3  ) && digitalRead(limitSwitchLeft) == 0){
   // stop and idle;
   motionState = 0;
    stepperSlide.setCurrentPosition(0);                           
    stepperTilt.moveTo(0);
    stepperPan.moveTo(0);
 }


 // move steppers as needed:
 stepperSlide.run(); 
 stepperPan.run();
 stepperTilt.run();

}

I'm not sure I handled it fully, but the motionState variable is intended to helps loop() remember what to do in between successive loops() and new bluetooth commands.

I also moved some limit switch stuff out to the main loop, since, if you are depending on the limit switches to stop you, they shouldn't be depending upon receiving a fresh bluetooth command.

1 Like

Whoah thank you so much, you are so kind!!! In a couple of days I'll assemble the slider and I'll see what modifications should be done, but this handles what i've been trying to do so well! Maybe i'll remove the "left" command, given how you've made the slider automatically return back as soon as it hits the right limit switch, or perhaps i'll try to add more functionality.. I'll see what works the best in practice! : D

I was tossing stuff out there. You might want to keep a "left" command to override a motion in the middle.

The trick of using a variable like motionState to manage the doing different things is a "state machine":

https://www.gammon.com.au/statemachine

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.