-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MultipleSteppers without Acceleration #139
Comments
You can plug in your own accelerator, no need to change the lib. Since the accelerator doesn't need to calculate anything, this should be quite simple. Here some untested code: #include "Arduino.h"
#include "TeensyStep.h"
class dummyAccelerator
{
public:
// will be called before the movement. Do all needed precalculations here and return the starting speed
inline int32_t prepareMovement(int32_t currentPos, int32_t targetPos, uint32_t targetSpeed, uint32_t pullInSpeed, uint32_t pullOutSpeed, uint32_t a)
{
v = targetSpeed; // no acceleration so we only save the requested speed
return v;
}
// will be called whenever TeensyStep needs a new speed, do the calculation based on the current position here and return the new speed
inline int32_t updateSpeed(int32_t currentPosition)
{
return v; // no acceleration, so just return the target speed
}
// calculate the steps required to decelerate to a stop
inline uint32_t initiateStopping(int32_t currentPosition)
{
return 0; // no acceleration, we can stop imediately
}
protected:
int32_t v = 0;
};
using MyController = TeensyStep::StepControlBase<dummyAccelerator, TimerField>;
MyController ctrl;
Stepper s0(0, 1), s1(2, 3);
void setup()
{
s0.setMaxSpeed(10000);
s1.setMaxSpeed(20000);
ctrl.move(s0, s1);
}
void loop()
{
}
|
Ok, I have tried that code and it seems to work somehow. (Teensy 3.6 @180MHz) But when you look closer, there seems to be something wrong. This is the code I have used for testing:
The distance is the same as the speed, so in theory, the movement should take exactly 1s. I have measured the STEP and DIR signal with a Picoscope and there is something wrong... The time of the DIR signal is 1.001s, which is not exact 1.000s. Here is does not matter really, but if you take 200 segments with a much shorter time for each signal, then 0.001s will be definitly noticable. But if you look close between the segments, you see a gap on the STEP output. This gap will be very noticable in my application, since I want to use many small segments to replicate a complete bezier curve. The cylce time is fine (1s/3200Hz = 312,5 microseconds), so there seems to be some kind of delay before and/or after the segment... |
Interesting, I'll have a closer look tomorrow. |
The gap is due to the call of A better solution would be to use the built in callback functionality which invokes a user supplied function whenever the controller reached the target. ctrl.setCallback(onPositionReached); Unfortunately the callback is invoked at the beginning of the last pulse. Thus, setting new targets in the callback (while the last pulse is still active) will mess up the library. I therefore changed the library to invoke the callback after the last pulse is done (use the branch improveCallback for testing). With this you can do the following (please note that I renamed and moved the accelerator to #include "Arduino.h"
#include "TeensyStep.h"
#include "NoAccelerator.h"
using MyController = TeensyStep::StepControlBase<NoAccelerator, TimerField>;
MyController ctrl;
Stepper sx(0, 1), sy(2, 3);
struct pos
{
int x;
int y;
};
constexpr pos path[] = {
{3200, 3200},
{ 0, 0},
{5000, -1000},
{ 200, -3200},
{ 0, 0},
};
constexpr size_t pathLength = sizeof(path) / sizeof(path[0]);
// this will be called whenever the controller finished a movement
void onPositionReached()
{
static unsigned p = 0;
digitalWriteFast(4, HIGH); // measure switching speed
if (p < pathLength)
{
sx.setTargetAbs(path[p].x);
sy.setTargetAbs(path[p].y);
ctrl.moveAsync(sx, sy);
p++;
}
digitalWriteFast(4, LOW);
}
void setup()
{
pinMode(4, OUTPUT); // use pin 4 to measure switching speed
ctrl.setCallback(onPositionReached);
sx.setMaxSpeed(3200);
sy.setMaxSpeed(3200);
onPositionReached(); // invoke the callback to start the chained movements
}
void loop()
{
} The code generates the following pulse train: Zoomed into the last transitions: The transition between two path points takes some 6µs which is needed to calculate the new movement parameters and setup the timers. Thus, the first pulse after the switch will be 6µs too late. Depending on your application this may or may not be a problem. Hope that helps. |
Thanks for the quick support, I tried your latest example, but there still seems something wrong. There is now an impuls on each new segment, but somehow this first impuls is way too long. It looks like this impuls is connected to the next impuls. On your measurement it looks ok, but with my test, this seems to be wrong. |
Did you use exactly my firmware and the |
Yes, code is the same, just different pins: NoAccelerator.h class NoAccelerator
{
public:
// will be called before the movement. Do all needed precalculations here and return the starting speed
inline int32_t prepareMovement(int32_t currentPos, int32_t targetPos, uint32_t targetSpeed, uint32_t pullInSpeed, uint32_t pullOutSpeed, uint32_t a)
{
v = targetSpeed; // no acceleration so we only save the requested speed
return v;
}
// will be called whenever TeensyStep needs a new speed, do the calculation based on the current position here and return the new speed
inline int32_t updateSpeed(int32_t currentPosition)
{
return v; // no acceleration, so just return the target speed
}
// calculate the steps required to decelerate to a stop
inline uint32_t initiateStopping(int32_t currentPosition)
{
return 0; // no acceleration, we can stop imediately
}
protected:
int32_t v = 0;
}; main code: #include "Arduino.h"
#include "TeensyStep.h"
#include "NoAccelerator.h"
using MyController = TeensyStep::StepControlBase<NoAccelerator, TimerField>;
MyController ctrl;
Stepper sx(7, 6), sy(24, 10);
struct pos
{
int x;
int y;
};
constexpr pos path[] = {
{3200, 3200},
{ 0, 0},
{5000, -1000},
{ 200, -3200},
{ 0, 0},
};
constexpr size_t pathLength = sizeof(path) / sizeof(path[0]);
// this will be called whenever the controller finished a movement
void onPositionReached()
{
static unsigned p = 0;
digitalWriteFast(32, HIGH); // measure switching speed
if (p < pathLength)
{
sx.setTargetAbs(path[p].x);
sy.setTargetAbs(path[p].y);
ctrl.moveAsync(sx, sy);
p++;
}
digitalWriteFast(32, LOW);
}
void setup()
{
pinMode(32, OUTPUT); // use pin 4 to measure switching speed
pinMode(7, OUTPUT);
pinMode(6, OUTPUT);
pinMode(24, OUTPUT);
pinMode(10, OUTPUT);
ctrl.setCallback(onPositionReached);
sx.setMaxSpeed(3200);
sy.setMaxSpeed(3200);
onPositionReached(); // invoke the callback to start the chained movements
}
void loop()
{
} Here are some screenshot from all position changes. I have now included the measurement of the second motor: As you can see, the long step always happens, except on last change on the second motor... If you have look on the step interval on the motor in the last segment, you also see, that it is not consistent, it looks like 5 or 6 steps are made, then there is a little gap, and the the next 5/6 steps are made... |
The important thing is the changed lib from the branch I mentioned. Are you sure that you use that? I can have a look later today
BlueMail for Android herunterladen
Am 26. Juli 2022, 17:11, um 17:11, BlueGene00 ***@***.***> schrieb:
…Yes, code is the same, just different pins:
NoAccelerator.h
```c++
class NoAccelerator
{
public:
// will be called before the movement. Do all needed precalculations
here and return the starting speed
inline int32_t prepareMovement(int32_t currentPos, int32_t targetPos,
uint32_t targetSpeed, uint32_t pullInSpeed, uint32_t pullOutSpeed,
uint32_t a)
{
v = targetSpeed; // no acceleration so we only save the requested speed
return v;
}
// will be called whenever TeensyStep needs a new speed, do the
calculation based on the current position here and return the new speed
inline int32_t updateSpeed(int32_t currentPosition)
{
return v; // no acceleration, so just return the target speed
}
// calculate the steps required to decelerate to a stop
inline uint32_t initiateStopping(int32_t currentPosition)
{
return 0; // no acceleration, we can stop imediately
}
protected:
int32_t v = 0;
};
```
main code:
```c++
#include "Arduino.h"
#include "TeensyStep.h"
#include "NoAccelerator.h"
using MyController = TeensyStep::StepControlBase<NoAccelerator,
TimerField>;
MyController ctrl;
Stepper sx(7, 6), sy(24, 10);
struct pos
{
int x;
int y;
};
constexpr pos path[] = {
{3200, 3200},
{ 0, 0},
{5000, -1000},
{ 200, -3200},
{ 0, 0},
};
constexpr size_t pathLength = sizeof(path) / sizeof(path[0]);
// this will be called whenever the controller finished a movement
void onPositionReached()
{
static unsigned p = 0;
digitalWriteFast(32, HIGH); // measure switching speed
if (p < pathLength)
{
sx.setTargetAbs(path[p].x);
sy.setTargetAbs(path[p].y);
ctrl.moveAsync(sx, sy);
p++;
}
digitalWriteFast(32, LOW);
}
void setup()
{
pinMode(32, OUTPUT); // use pin 4 to measure switching speed
pinMode(7, OUTPUT);
pinMode(6, OUTPUT);
pinMode(24, OUTPUT);
pinMode(10, OUTPUT);
ctrl.setCallback(onPositionReached);
sx.setMaxSpeed(3200);
sy.setMaxSpeed(3200);
onPositionReached(); // invoke the callback to start the chained
movements
}
void loop()
{
}
```
Here are some screenshot from all position changes. I have now included
the measurement of the second motor:
Segment 1 ( 3200/3200 ):
![01](https://user-images.githubusercontent.com/10575045/181039914-4472f63c-5eef-41da-9d68-96c05b2149ee.JPG)
Segment 2 ( 0/0 ):
![02](https://user-images.githubusercontent.com/10575045/181039927-2c9571f6-ca4e-4dc9-8542-198e62ab915a.JPG)
Segment 3 ( 5000/-1000 ):
![03](https://user-images.githubusercontent.com/10575045/181039944-458a9399-55fc-4683-8ca1-99191bc97c03.JPG)
Segment 4 ( 200/-3200 ):
![04](https://user-images.githubusercontent.com/10575045/181039967-16d81d2a-3bc9-445c-bb90-6f5d7e71e3f3.JPG)
End:
![05](https://user-images.githubusercontent.com/10575045/181039981-4ed08b05-34c4-42e9-b89b-acfa0529fbaf.JPG)
As you can see, the long step always happens, except on last change on
the second motor... If you have look on the step interval on the motor
in the last segment, you also see, that it is not consistent, it looks
like 5 or 6 steps are made, then there is a little gap, and the the
next 5/6 steps are made...
--
Reply to this email directly or view it on GitHub:
#139 (comment)
You are receiving this because you commented.
Message ID: ***@***.***>
|
Yes, this was the error... I have copied the new branch to "C:/Programs/Arduino/libraries", but the original library was still under "C:/Documents/Arduino/libraries". Now it seems to be alright. The next step is to update the speed for each segment. In the example above, the maxSpeed was set for both axis to 3200. This should now also be updated on each new segment. So with each new target-position, there will be a new speed. The end goal is to have a fixed time interval and then calculate the position and speed accordingly, so that each segment is run in a fixed time. Here is a quick example with a 1 second interval: Distance and Speed for each segment will be calculated before the movement and stored in an array. So basically I just need a second array which contains the speed and is updated with each new position value. I will calculate the speed in a way, that it will match the fixed time interval for each motor. (in this example 1s, but in the real application it will be much less). I have added this second array and I call the setMaxSpeed before the setTargetAbs. This seems to work fine. Is this approach a good one or could this lead to problems? I will now perform a few more tests (with my real application code, more motors and much higher speed). When everything works as expected, this should be somehow integrated into the TeensyStep as example etc. The end result will enable users to run multiple stepper motors on a definied path in a fixed time. constexpr spee speedpath[] = {
{2000, 2000},
{1000, 1000},
{3000, 3000},
{2000, 2000},
{4000, 4000},
};
void onPositionReached()
{
static unsigned p = 0;
digitalWriteFast(32, HIGH); // measure switching speed
if (p < pathLength)
{
s0.setMaxSpeed(speedpath[p].x);
s1.setMaxSpeed(speedpath[p].y);
s0.setTargetAbs(path2[p].x);
s1.setTargetAbs(path2[p].y);
ctrl.moveAsync(s0, s1);
p++;
}
digitalWriteFast(32, LOW);
} |
Glad it works. Instead of changing max speed I'd try the speed override in moveAsync. I.e. you can call it in that way:
to move the motors with 30% of max speed. This might be more convenient. All in all the method is not very efficient, it needs to calculate new motion parameter for each segment. So, make the segments not too short. I was thinking of doing something like a dedicated pathController which could do this much more efficient and therefore more smoothly. You would give it an array or a ringbuffer with coordinates/speed entries and it would sequentially move to this points with the given speed. Implementing this is something which will take quite some time (but would be fun). Let me know if it works at the end. Also, having it as an example is a good idea. |
I don't get this part. Is there a special need why you factor in time? This will lead to large jumps in the motor speed which will generate a rather jumpy/rough movement? Can you explain why in your application it is important to have each segment done in the same time? Shouldn't the trajectory speed be constant during the movement (after acceleration of course)? The trajectory speed could be calculated by some acceleration scheme and the speed of the single motors could be determined from the tractectory speed by simple trig. If you choose the segments short enough (distance wise) you should get a very smooth movement. |
Here is the background: It is a multi-axis system and the user will set the overall movement time for all motors and individual keyframes(positions) for each motor. Those keyframes can be location anywhere, so all axis are independent from eachother. Some axis can have only 1-2 keyframes, some 20 keyframes. The keyframes are connected by bezier-splines, so there is always a smooth transition between the points. This bezier-calculation takes some time, so it is not possible to calculate the next movement in real-time. Instead, I do this before the movement and store the values in arrays. To "follow" this bezier curve, the entire curve is split into very small linear segments. So lets say the movement is 6 seconds long and I take 300 segments, each segment is 0,02 seconds. So the movement will be updated every 0,02 seconds. I still need to determine the perfect amount of segments/update interval... This is my approach to run a custom bezier-curve. If there are any other solutions, let me know. But as far as I researched, this linear interpolation is implemented by most CNC machines, so it should be nothing "new". |
Ok, got it. Looks like some camera movement system where you need the camera be at some points/angles at specific times. I come from the CNC world, where the time aspect is not so critical, it is more important that the path is followed with a given and constant speed. For testing I suggest to have a close look at the movement. If one of the motors ends up with a significantly different speed, Bresenham's algorithm tends to generate jumpy movements which might be a problem. Also have a close look at the timing, since time doesn't enter the used algorithms, the movements might take a slightly different time as calculated. Looking forward to see some results |
Yes, it is indeed a camera motion system ;) I will have a good look at the timing, speeds etc. This was the problem with accelstepper... The time was not really exact and with a certain speed, the entire movement was slowed down. But I have the picoscope and will measure the step signals and run an evaluation to verify proper behavior. For my first tests it looked very promising. Once everything is verified, I will publish a complete documentation with examples. Next step would be to test and implement it for the Teensy4. I saw there there is separate library for T4, but it is still in Beta? Since T3.6 will be not available for some more months, it would be good to have an alternative with T4. What is the current status and are the changes above easy to implement into the T4 library? |
The T4 beta is a completely different setup. I still needs a lot of work but I didn't find sufficient time so far. I'd rather not do experiments with it at the moment. Can you post some key performance requirements?
|
With my previous code, I had some issue with small movements/small speeds. There were some rounding errors and this cause some jerky movement at slower speeds. With increased microstepping, it worked much better. That is the reason, why I am currently testing with 1/32 microstepping. This will of course lead to higher step frequency for the controller. So lets say the motor should reach 3000 rpm, this would be 96.000 steps/s with a standard 200 steps/turn motor. I guess 100k will be sufficient for most cases. Currently I am testing with 6 motors, but having the ability to go up to 10 (or even higher) would be great. I know that there will be some processing limitation at some point, but in this case, I could use multiple Teensys and trigger the movement by a common start signal. How is overclocking of the Teensy effecting the library? I guess most tests were performed with the standard clock frequency. Thanks for your support ;) |
Here is a basic first preview of the movement: The input was 5 keyframes (0 / 32.000 / 8.000 / 32.000 / 0) for all 6 stepper motors. Then there was the bezier calculation which split up the entire movement into 400 segments. Moving duration is 4s, so each segment is 10ms. The movement is identical, because all motors received the same input positions. But the code in the background was individual for each stepper, so I just need to change the input position and the movement would be different. So far so good, but I noticed some smaller issues. This measurement here was from a different motion, but the issue is the same. As you can see, when the movement is "positive", the DIR signal stays low the entire time. But when the direction is negative, you can definitely some small gaps in the DIR signal each time there is a transition between the segments. Time between the gaps is ~ 10ms, which is the update interval. The gap length of the DIR-signal is ~ 1ms. I guess this will have no big influence, since this gap in the DIR is that short and most times it is not during a STEP impuls, so it will have no effect on the movement. But of course there could be some situations, were they align and this would impact the movement, since the motor will try to change direction for that one single step. As you can see in the STEP signal, there is also a larger gap, but I can not tell for now if this will slow down the movement at some point or cause other issues, need to investigate further... Another issue I have noticed, is when you have for example one axis which will have no movement. So you might want to run Motor A the entire time, lets say 10 seconds, but Motor B should only run from 0 to 5 seconds. So the last 5 seconds, Motor B will have a speed of 0 and the position change is also 0. This will definitely cause a crash in the library, because you try to devide by 0 at some point and this is not handled in the library. I did a quick fix and will not transmit a speed value of 0, but will change it to 1, then it works. But of course, this is not a real solution, just a work-around. Will do more tests, but until now, it looks really good and is definitely much faster and more precise then my previous solution I tried with Accelstepper. |
@BlueGene00 Hi, your topic is very interesting. May I talk to you directly to learn more about motion control in CNC / Cinematography? I've been researching in these topics for a while but I don't think I know enough. It's okay if this is improper, I would be happy catching up any new comment from Luni64 and you. Cheers and I hope TeensyStep will solve everyone's problem in no time! :D |
@atlesg Yes, a direct discussion might be the fastest way to exchange different ideas. How do I contact you directly? I will keep this topic updated anyways. This current issue which I have noticed, is when the target values and/or speed is 0, then the entire library will fail. I try to investigate further, but this must be taken care of. If there is no movement of the segment, the time must delayed anyway. |
While using TeensyStep and a dummy accelerator "kind of works" for the task it is definitely not very efficient. To test better solutions I wrote a dedicated lib from scratch last week. It works much better but needs some improvements and fixes. I hope I have something to share in the next days. |
Hi yes! I use many social accounts. Do you have Discord? mine is Morty#8279 - I also have Whatsapp, Viber... but I don't think it's not suitable to post them here. Let's catch up (I don't know how to text private message on Github tho...) |
No word can describe your effort. Guess I gotta find some T3 and T4 in country, they are quite overpriced here because not many people use it. Do you suggest any shield/stack up board to use with Teensy (Op-Amp board will protect Teensy from Stepper/Servo Motors). |
Depending on the size of your motors I recommend to either use one of the ubiquitous small drivers (DRV8825, A4988...). For larger motors a good choice is one of the digital Leadshine drivers. (Be careful, a lot of not so good Leadshine clones are around). Use a drive voltage as high as possible to get good performance. There is no need to protect the teensy if you use one of the small drivers. For the larger drivers with opto coupled inputs a 3.3->5V converter (e.g. 74HC245) might be necessary. Make sure that you get a good stepper. Look for low inductance motors (see here for more information: https://luni64.github.io/TeensyStep/applications2/901_stepper/steppers#electrical-parameters) |
@luni64 Thank you! Please keep us updated with your new dedicated lib. I think you should open some donation links, I would like to contribute to your awesome project. |
Hi, I have been testing the following code with a single motor to follow the path with varying speed. I seem to have stumbled onto an issue.
While execution, when updating to
|
Sorry for answering slowly. I'm currently recovering from a covid infection. I still need a couple of days before I can continue working on this. |
@luni64 Any update on this topic? Hope you are feeling better now ;) |
Oh, looks like I was distacted by other stuff after the infection. Frankly, I completely forgot about it. Can't promise, but I try to work on it on the weekend. |
@luni64 Have you checked? I have made some further testing with the code above and found some points. Point 1 is currently the most important for me. Point 1: When it is inside the "setup", it is working properly: void setup(){
...
ctrl.setCallback(onPositionReached);
onPositionReached();
...
} But when I move the functions into the loop, it does not work: void loop(){
...
ctrl.setCallback(onPositionReached);
onPositionReached();
...
} To avoid any influence from the other code, I would like to run this in a while loop. This should be put inside a function, so that I can just call it from anywhere else inside the "loop", for example: void performMovement()
{
while (currentSegment< totalSegmentAmount)
{
stepperX.setMaxSpeed(newSpeedArrayX[currentSegment]);
stepperY.setMaxSpeed(newSpeedArrayY[currentSegment]);
stepperX.setTargetAbs(myArrayX[currentSegment]);
stepperY.setTargetAbs(myArrayY[currentSegment]);
ctrl.moveAsync(stepperX, stepperY);
currentSegment++;
}
Serial.println("Movement is finished");
} I have tried almost everything, but as soon as the functions are not inside the "setup", it will not work. It will just go through the function, but there will be not movement... Point 2: So I got the exact step amount only with 240 and 256 MHz. There was a big error with 196 MHz... I need to investigate what the max. possible Step-frequency is, or at which frequency the CPU speed will lead to different results. Not important for now, but if you are working on "special library", this should be considered, especially when the library should be compatible with Teensy 3.6 and 4.1. Point 3: But we still need to consider, that the code must pause for the segment duration, no matter if there is a movement or not. This is why I have added a "support" axis, which is not connected to an actual motor, but just will change position value between each segment. So lets say, we have a 10 second overall movement with 100 segments. So each segment has a duration of 0.10 seconds. Now I take this help axis and move between position = 0 and position =500 for example. So the speed is 500/0.1=5000. With this, it is ensured, that the movement on all other actual axis will be paused and not just skipped by the code. It is just a work-around, I guess there are much "cleaner" solutions for this kind of problem. |
Hey @luni64, any update on this issue? |
Hello sir @luni64 |
Any update on this topic or the support for Teensy 4.x? Teensy 3.6 will not be available for some more months, so it would be great to have a version for 4.x |
@BlueGene00 Have you tried STM32? quite good alternative for Teensy shortage crisis |
STM32 would require a complete redesign of my control board. So I would like to stick with Teensy. There is a new update on the Teensy 3.6 page: So it would be good to have an adjusted library for 4.1. I guess there are some more people who use Teensy for stepper control, so it definitly help. AccelStepper is not realy an alternative, unless you drive with very low step frequencies. Let hope @luni64 or somebody else finds some time to finish the TeensyStep for 4.x |
@BlueGene00 Hi there! I discovered this thread and I am also working on my own motion control system for Teensy 4.1. I was able to fork the teensystep4 library and implement 74HCT595 level shifters to expand the # of stepper drivers (in this case I need 16 steppers). I would love to get to talk to you through DMs about your implementation of motion control software in teensystep. Thank you! |
Hey atlesg. I sent you a discord friend request/PM if you'd be interested in checking that out. Thank you! |
As far as I understood, the MultipleSteppers example only works with an integrated acceleration/declaration.
For my application I want to run different segments one after another (Polyline to interpolate a spline curve).
So the acceleration is already taken care of, I just need a code which runs all steppers synchronous between two points, but without the acceleration...
Of course I could set a very high acceleration, then it would akt a linear movement. But there is still the calculation in the background which might slow down the movement and prevent the motor from reaching very high speeds.
What would be the easiest way to get rid of the acceleration in the code and just perform a linear movement between several motors?
The text was updated successfully, but these errors were encountered: