Hi there!
I'm working on a motion-control camera system that uses the AccelStepper/Multistepper library. I was successfully able to get non-blocking code working with all the steppers. Essentially, I am culling through a matrix of stepper motors and positions. The ith position of the jth stepper gets queued into the stepperGroup.moveTo() function (stepperGroup is called camera in the code).
I then constantly check if each stepper has not reached its destination and use the run() function, which is enough to get everything going appropriately:
short currentKeyframe = 0;//global keyframe position. Keeps value the same even when paused void runMocoNonBlocking3(){ //TEST 3 replace i with currentKeyframe. WORKS(ISH). Purpose is to save position even after pause if(stepper1.distanceToGo()!=0 && stepper2.distanceToGo()!=0 && stepper3.distanceToGo()!=0 && stepper4.distanceToGo()!=0 && stepper5.distanceToGo()!=0) camera.run();//this should be called as much as possible else{ if(currentKeyframe < NUM_KEYFRAMES){ //change back to i=1 if testing with delta for(short j = 0; j < NUM_STEPPERS; j++){ targetPos[j] = kuper[currentKeyframe][j]; } camera.moveTo(targetPos); currentKeyframe++; } else currentKeyframe = 0;//only for looping. Replace this with a "finished" message } }
Now onto the confusing part. I essentially have an exposure time per frame of footage, and said exposure time dictates the feedrate of the system. In most cases, the exposure time per frame is 1 second. However, I also have to take into account the save time (i'm saving RAW images locally to the camera's card). Therefore, the exposure time is 2 seconds (essentially). Hypothetically, the feedrate of each stepper for each position is the delta of the future position - current position divided by 2.
In Multistepper, the moveTo() function handles synchronicity and is able to find the longest time. The function will then distribute the subsequent feed rate to all steppers so they all finish at the same time. This works well, but the moment I replace it with the abs(thisDistance) / 2, all my steppers seem to finish half of their overall movement than before.
void MultiStepper::moveTo(long absolute[]) { // First find the stepper that will take the longest time to move float longestTime = 0.0; uint8_t i; for (i = 0; i < _num_steppers; i++) { long thisDistance = absolute[i] - _steppers[i]->currentPosition(); //float thisTime = abs(thisDistance) / _steppers[i]->maxSpeed(); //**THIS IS THE PART THAT MUCKS UP THE MOVEMENT float currSpeed = abs(thisDistance) / 2; //replace 2 with global exposureTime variable float thisTime = abs(thisDistance) / currSpeed; //** if (thisTime > longestTime) longestTime = thisTime; } if (longestTime > 0.0) { // Now work out a new max speed for each stepper so they will all // arrived at the same time of longestTime for (i = 0; i < _num_steppers; i++) { long thisDistance = absolute[i] - _steppers[i]->currentPosition(); float thisSpeed = thisDistance / longestTime; _steppers[i]->moveTo(absolute[i]); // New target position (resets speed) _steppers[i]->setSpeed(thisSpeed); // New speed } } }
I do recognize that by fixing the speed to be divided by 2 subsequently renders the entire for loop at the beginning of this function redundant. However, I left it all in to make commenting in/out this test code versus the original code easier.
There's also a line of code in the setSpeed() function that constrains speed between the -maxSpeed and +maxSpeed. Because I wouldn't be using the maxSpeed with the change i'm trying to do, I commented it out. With or without that line however, the result is still the same.
This is the test position matrix that I am using:
void queryKuperSimpleTest(){ //set each stepper to 0 position kuper[0][0] = 0*steppers[0].coefficent; //deg kuper[0][1] = 0*steppers[1].coefficent; //mm kuper[0][2] = 0*steppers[2].coefficent; //deg kuper[0][3] = 0*steppers[3].coefficent; //deg kuper[0][4] = 0*steppers[4].coefficent; //deg kuper[1][0] = 45*steppers[0].coefficent; //deg kuper[1][1] = 5*steppers[1].coefficent; //mm kuper[1][2] = 10*steppers[2].coefficent; //deg kuper[1][3] = 30*steppers[3].coefficent; //deg kuper[1][4] = 25*steppers[4].coefficent; //deg kuper[2][0] = 75*steppers[0].coefficent; //deg kuper[2][1] = 10*steppers[1].coefficent; //mm kuper[2][2] = 20*steppers[2].coefficent; //deg kuper[2][3] = 60*steppers[3].coefficent; //deg kuper[2][4] = 45*steppers[4].coefficent; //deg kuper[3][0] = 90*steppers[0].coefficent; //deg kuper[3][1] = 15*steppers[1].coefficent; //mm kuper[3][2] = 30*steppers[2].coefficent; //deg kuper[3][3] = 90*steppers[3].coefficent; //deg kuper[3][4] = 65*steppers[4].coefficent; //deg }
The entire file that runs the steppers can be found here for clarity: https://github.com/HackinSpock/Motion-Control-Software/blob/main/src/main.cpp
Would anyone be able to help me figure out why i'm getting incorrect movement by adjusting the moveTo() function?
Thank you for your time!
float currSpeed = abs(thisDistance) / 2; //replace 2 with global exposureTime variable
float thisTime = abs(thisDistance) / currSpeed;
This results in thisTime being set to abs(thisDistance)/(abs(thisDistance)/2) which is always 2. Since thisTime is always 2 for every stepper, longestTime will always be 2 as well.
It would seem that you need to calculate thisTime by using the stepper's current speed to get a better estimate of the time required for the move to the new position.
Anything seems possible when you don't know what you're talking about.
float currSpeed = abs(thisDistance) / 2; //replace 2 with global exposureTime variable float thisTime = abs(thisDistance) / currSpeed;
This results in thisTime being set to abs(thisDistance)/(abs(thisDistance)/2) which is always 2. Since thisTime is always 2 for every stepper, longestTime will always be 2 as well.
It would seem that you need to calculate thisTime by using the stepper's current speed to get a better estimate of the time required for the move to the new position.
You're absolutely correct. I guess I'm perhaps misunderstanding the purpose of doing all these calculations at this point. If Stepper A is going from 0 to 100mm and Stepper B is going from 0 to 50mm, and both need to do this movement in 2 seconds, Stepper A would go 50mm/s and Stepper B 25mm/sec.
Hypothetically, they should both reach their target position at the same time yes? Or am I missing something?
Hypothetically, they should both reach their target position at the same time yes? Or am I missing something?
I believe that Multistepper will already do that for you. If you give it a new target location then it should time the stepping of each stepper to complete the move at the same time. You appear to be overloading the built-in method; I presume that's because you need to complete the move in a specific time and need to compute new speeds for the steppers.
Anything seems possible when you don't know what you're talking about.
@will Correct. That's essentially what I am trying to do. For the purposes of the tests, i'm dividing by 2 for each new position, and by what we're discussing, that should work. Except that somehow the steppers are not able to get to their intended positions.
I guess I'm perhaps misunderstanding the purpose of doing all these calculations at this point. If Stepper A is going from 0 to 100mm and Stepper B is going from 0 to 50mm, and both need to do this movement in 2 seconds, Stepper A would go 50mm/s and Stepper B 25mm/sec.
Hypothetically, they should both reach their target position at the same time yes? Or am I missing something?
I'm not sure if it's you missing the point or me 🙂
First, I think this function should not be an overloading of the MultiStepper class; it belongs as a module in YOUR code separate from Multistepper. So, rename the above function to something like prepareAndMoveTo(long absolute[]);
Second, as mentioned in the previous post, your calculations will ALWAYS result in longestTime being 2 so those calculations are wasted; you could more efficiently just assign the value 2.0 when you declare it as float.
Perhaps I'm wrong here (please correct me if so) but it seems to me that what you need to do is calculate the highest speed over all of the steppers to make sure that they can all complete the move in the required time (2 seconds).
1) remove declaration of longestTime and all references to it
2) declare a new variable float maxSpeed = 0.0;
3) in the first loop
a) calculate currSpeed as present (the speed required for this stepper to complete the move)
b) set maxSpeed = maximum of currSpeed and maxSpeed
4) if (maxSpeed>0.0)
4a) loop on i
_steppers[I]->setSpeed(maxSpeed);
4b) myMultiStepper->MoveTo(absolute[]); // Your MultiStepper instance
The idea here is to set all stepper speeds to the highest rate needed to force all steppers to the new absolute location in time. Since MultiStepper already manages the move in such a wy that all steppers arrive at the end of the move together, we can safely set all of them to the fastest speed required.
This is the reason for not overloading MoveTo in MultiStepper, we want to let it do its own synchronizing magic after we set the speeds to complete in the time we want.
Please let me know if I've missed your objective and taken a random walk in an awkward direction.
Anything seems possible when you don't know what you're talking about.
I think your analysis is correct.
The algorithm that MultiStepper::moveTo uses determines the slowest servo time and adjusts the speed of all other servos to fit that time.
As I understand the issue, the goal is to insure all servos reach the target position within 2 seconds. The desired calculation is to determine the speed of each servo that reaches the target position in at most 2 seconds.
The calculation would be
float desired_speed = abs(target_pos - current_pos)/200000.0f;
(AFAICT, time is measured in microseconds and speed is measured in steps per second.)
Given a list of target positions (long absolute[]), iterate over the servos, calculate the desired_speed, and for any servo speed (AccelStepper::speed()) slower than the desired speed call AccelStepper::setSpeed(desired_speed).
Now call MultiStepper::moveTo to update all the other servos to match the new slowest servo.
Which is pretty much what you said, so I'm trying to agree with you.
I guess I'm perhaps misunderstanding the purpose of doing all these calculations at this point. If Stepper A is going from 0 to 100mm and Stepper B is going from 0 to 50mm, and both need to do this movement in 2 seconds, Stepper A would go 50mm/s and Stepper B 25mm/sec.
Hypothetically, they should both reach their target position at the same time yes? Or am I missing something?
I'm not sure if it's you missing the point or me 🙂
First, I think this function should not be an overloading of the MultiStepper class; it belongs as a module in YOUR code separate from Multistepper. So, rename the above function to something like prepareAndMoveTo(long absolute[]);
Second, as mentioned in the previous post, your calculations will ALWAYS result in longestTime being 2 so those calculations are wasted; you could more efficiently just assign the value 2.0 when you declare it as float.
Perhaps I'm wrong here (please correct me if so) but it seems to me that what you need to do is calculate the highest speed over all of the steppers to make sure that they can all complete the move in the required time (2 seconds).
1) remove declaration of longestTime and all references to it
2) declare a new variable float maxSpeed = 0.0;
3) in the first loop
a) calculate currSpeed as present (the speed required for this stepper to complete the move)
b) set maxSpeed = maximum of currSpeed and maxSpeed
4) if (maxSpeed>0.0)
4a) loop on i
_steppers[I]->setSpeed(maxSpeed);
4b) myMultiStepper->MoveTo(absolute[]); // Your MultiStepper instance
The idea here is to set all stepper speeds to the highest rate needed to force all steppers to the new absolute location in time. Since MultiStepper already manages the move in such a wy that all steppers arrive at the end of the move together, we can safely set all of them to the fastest speed required.
This is the reason for not overloading MoveTo in MultiStepper, we want to let it do its own synchronizing magic after we set the speeds to complete in the time we want.
Please let me know if I've missed your objective and taken a random walk in an awkward direction.
Oh it's definitely me for sure. I appreciate the clarification!
So the pseudo code you provided makes sense (at least I hope I understood you correctly). I went ahead and wrote it. It technically works, as in the steppers all move in sync but it appears nothing changed between this new code and what I had before. The movement still goes way above my estimated time of 8 seconds (in this case, it runs through an example of 4 positions, therefore a desired 8-second movement).
exposureTime is the 2.0 seconds btw declared as a global variable.
void prepareAndMoveTo(long absolute[]){ float maxSpeed = 0.0; for (int i = 0; i < NUM_STEPPERS; i++){ float currSpeed = (absolute[i] - steppers[i].getPosition()) / exposureTime; if(currSpeed > maxSpeed) maxSpeed = currSpeed; } if(maxSpeed > 0.0){ for (int i = 0; i < NUM_STEPPERS; i++){ steppers[i].setSpeedStepper(maxSpeed); // New speed } camera.moveTo(absolute); } }
My confusion still stems from the continued use of the Multistepper moveTo() function. If we call the moveTo function after all these calculations, we're essentially just going to end up overriding them. (This is the default moveTo() function. This has not been modified)
void MultiStepper::moveTo(long absolute[]) { // First find the stepper that will take the longest time to move float longestTime = 0.0; uint8_t i; for (i = 0; i < _num_steppers; i++) { long thisDistance = absolute[i] - _steppers[i]->currentPosition(); float thisTime = abs(thisDistance) / _steppers[i]->maxSpeed(); if (thisTime > longestTime) longestTime = thisTime; } if (longestTime > 0.0) { // Now work out a new max speed for each stepper so they will all // arrived at the same time of longestTime for (i = 0; i < _num_steppers; i++) { long thisDistance = absolute[i] - _steppers[i]->currentPosition(); float thisSpeed = thisDistance / longestTime; _steppers[i]->moveTo(absolute[i]); // New target position (resets speed) _steppers[i]->setSpeed(thisSpeed); // New speed } } }
moveTo() still relies on calculating the time based on the maxSpeed, which is declared in the setup (i.e. stepper1.setMaxSpeed(1000)//some amount of steps per second). So after the calcs we did in prepareAndMoveTo(), it ends up recalculating thisTime based off of maxSpeed.
So if in prepareAndMoveTo() I have a stepper motor going from 0mm to 100 steps in 2 seconds, we'd see a maxSpeed of 50steps/s. After feeding that into setSpeed(), I'm then calling the moveTo() function of the stepper group, it would end up calculating thisTime as 100steps / (1000steps/second) = 0.1 seconds.
(using 1000steps/second maxSpeed as an example. It's been used as a default speed in example sketches).
Now when it goes to calculate thisSpeed, it'll end up being: 100steps / 0.1seconds = 1,000 steps per second, which is not the desired 50steps/s that I calculated earlier. This then gets fed back into setSpeed, thus overriding the 50steps/s we calculated earlier.
Does that make sense, or am I still not understanding something? Not trying to argue, i'm just genuinely confused.
I very much appreciate both of your time for helping me figure this out, btw.
The most likely reason that it runs over your expected time is that the system is too busy and that your run() is called past the time that a step was due. That is, either you've put it in the wrong code area or you're just trying to do too much between calling for every next step to be taken in time to match the speed selected.
You said in your first post that you finally got "non-blocking" code running. But, from the code you posted, you're not doing anything until the move is complete, so you are losing time waiting for all of the steppers to complete their moves.
Knowing that MultiStepper will complete the move for all steppers at the same time and assuming that you don't ever need to interrupt the move, I would suggest that you add the line
camera.runSpeedToPosition(); after line camera.MoveTo(absolute);.
This will then run the steppers (albeit blocking) with NO wasted time testing distance to go in the loop. When the prepareAndMoveTo function is done the move will be totally complete.
You'll also need to redo (or eliminate) the function runMocoNonBlocking.
If you do need to test for something inside the move, you may be able to use an interrupt or timer to provide the opportunity to test the condition. Otherwise, if you truly need a non-blocking means for control then you're going to have to optimize your code to reduce any function calls and accessory calculations to allow run() to be called often enough to never miss a step on any stepper.
Sorry, I know this probably wasn't the answer you wanted.
Anything seems possible when you don't know what you're talking about.
moveTo() still relies on calculating the time based on the maxSpeed, which is declared in the setup (i.e. stepper1.setMaxSpeed(1000)//some amount of steps per second). So after the calcs we did in prepareAndMoveTo(), it ends up recalculating thisTime based off of maxSpeed.
So if in prepareAndMoveTo() I have a stepper motor going from 0mm to 100 steps in 2 seconds, we'd see a maxSpeed of 50steps/s. After feeding that into setSpeed(), I'm then calling the moveTo() function of the stepper group, it would end up calculating thisTime as 100steps / (1000steps/second) = 0.1 seconds.
(using 1000steps/second maxSpeed as an example. It's been used as a default speed in example sketches).
Now when it goes to calculate thisSpeed, it'll end up being: 100steps / 0.1seconds = 1,000 steps per second, which is not the desired 50steps/s that I calculated earlier. This then gets fed back into setSpeed, thus overriding the 50steps/s we calculated earlier.
Does that make sense, or am I still not understanding something? Not trying to argue, i'm just genuinely confused.
Well, I'm sure confused by all of that 🙂
I believe that you're overthinking and overcomplicating things here. AFAIK speed is in steps/second, so if we set a speed of 50 then it means we have to take step every 1/50 of a second. As long as our stepper can accommodate this speed we're golden. We also need to consider that run() is called AT LEAST this often to make sure that every step is taken on time.
So the time taken to move from A to B is abs(A-B)/50 where A,B are in. steps and 50 is the speed (50 steps/second).
So, to move 100 steps would take (100-0)/50 = 2 seconds.
Is that clear(er) or have I dragged you completely off the track ?
Anything seems possible when you don't know what you're talking about.
Thanks for the prompt response. Indeed I would like to have everything non-blocking and the runMocoNonBlocking() function was actually working really well in my state machine:
void loop(){ buttonState = digitalRead(buttonPin);//pause button switch(state){ case 0: if(buttonState == HIGH) //pause button { //stopSteppers(); state = 1; break; } else { runMocoNonBlocking(); break; } case 1: if(buttonState == HIGH) //pause button { state = 0; break; } else { digitalToggleFast(LED_BUILTIN);//finished delay(200); break; } } }
I was able to pause the movement at any point with a button and I didn't lose any steps. You're definitely correct in suggesting to move the speed calcs before the loop. I could probably just add another dimension to my matrix that handles speed calcs in the setup.
I'm not entirely sure how the run() could be the issue, considering i've been able to pause at any moment without losing steps, but I'll look into reconfiguring where it's called.
There are several things I want to do tangentially with the movement (i.e. pausing and such), and I really wanna steer clear of runToPositionSpeed(), but I hear you, yah. I got blocking working a while back and can relook into that as well.
@will Your calcs are definitely correct, but the multistepper moveTo() function divides by maxSpeed, rather than 50 in your example. I got confused because you mentioned not to modify moveTo(), but I agree that I would want to divide by 50.
Should I make a second version of moveTo() in the multistepper class to divide by the input speed rather than using maxSpeed?
I'm not entirely sure how the run() could be the issue, considering i've been able to pause at any moment without losing steps, but I'll look into reconfiguring where it's called.
There are several things I want to do tangentially with the movement (i.e. pausing and such), and I really wanna steer clear of runToPositionSpeed(), but I hear you, yah. I got blocking working a while back and can relook into that as well.
1) perhaps you could pause, but that would have hidden the fact that you were NOT completing the move in the time you expected 🙂
2) You could also (instead of calling runToPositionSpeed) just make a loop like
while ((state not paused) && (any stepper move not complete)) {
camera.run();
}
That would still allow you to pause and still minimize the time between calls of run().
You could further optimize that by noting the stepper index that had the max speed set and just testing that one for remaining steps and ONLY IF it had no steps left to take then check the rest of the steppers as well.
Anything seems possible when you don't know what you're talking about.
@will Your calcs are definitely correct, but the multistepper moveTo() function divides by maxSpeed, rather than 50 in your example. I got confused because you mentioned not to modify moveTo(), but I agree that I would want to divide by 50.
Should I make a second version of moveTo() in the multistepper class to divide by the input speed rather than using maxSpeed?
I think my point is that you should leave the class alone. It's doing its job correctly since all of the steppers arrive at the end of the move together, so let it be. YOUR job is to give it the information it needs to complete the job in time and that means determining the stepper with the farthest to go and making sure that it will finish in time.
Since we don't know which stepper has the farthest to go, we have to check all of them and we don't want to have to remember which has the longest track. So we (being programmers and lazy by nature) calculate the speed required for all of the steppers and just set everything to the fastest one. We then rely on our friendly (unmodified) MultiStepper class to arrange the other steppers for simultaneous completion.
Anything seems possible when you don't know what you're talking about.