Stepper motor drifting systematically

Hello,

In a project for university, I use a strong stepper motor (Nema 23, with TB6600 driver and Arduino Mega2560) to make a rope move up and down. A laser and photodiode (attached to the moving arm, itself attached to the stepper) are used to reset the position in case the rope pulls too hard on the motor and there is an overtorque situation.

I do indeed expect problems when there is an overtorque... BUT I have a systematic displacement even when the load if very small, i.e. when the rope is not attached. I don't expect this to be a problem with the motor, it's quite sturdy. See this video for the problem.

My personal hypothesis is that there is a small down time in the current delivered to the motor, and mean there is no current, then gravity acts and moves the arm a tiny bit. However this would be rather surprising.

For my project, I need the average of the motion to stay precise, but I don't know how to do that 'properly'. I thought about software fixes using the photosensor seeing the laser and resetting the stepper zero position, but this seems rather silly (and needs to be gradually changed etc etc). I have the feeling that the error can be fixed otherwise and is due to my lack of understanding of what I'm doing, be it hardware or software.

Here is my code attached (over the 9000 characters limit).

Any ideas on how to avoid this drifting motion will be greatly appreciated!

PS: I'm not a great expert either in C++ or Arduino or electronics, so if my mistake could have been fixed easily with more research, sorry for the post. I'm a theoretical physicist and haven't touched experiments in a while, I'm doing this project because we were let down by the experimental team.

dev.ino (10.4 KB)

The code is pretty rich so hard to say if there is a fault in your logic (also I'm unsure of what the real drift issue is)

this seems a lot. did you try with default values or not as extreme?

  stepper.setMaxSpeed(16000);
  stepper.setAcceleration(5000000);

and this   delay(100000000000000000000); // to end everythingis rather usually done with while(true);

comrad_dau:
Here is my code attached (over the 9000 characters limit).

When you have the sort of problem you describe it is best to do some tests with a very short program that does nothing except move the motor. Start your tests with slow maximum speeds and slow acceleration. The AccelStepper library includes several short example programs.

If there are no problems with the short program you can infer that the problem you are experiencing is caused by some other part of your long program.

If there are problems with the short program you should not waste time on the longer program until they have been resolved.

...R

while(true);

Good idea indeed.

The AccelStepper library includes several short example programs.

I had no apparent problems with the basic examples.

Sorry about the long program, I now narrowed down to the essential : a sinusoidal motion, with an increasing amplitude over the first few periods (which is what I need). I think that the key lays in the fact that I have conditions to check at each step in the real program (here simulated with delay(3)).

// dirPin on 2
// stepPin on 3

// Include the AccelStepper library:
#include <AccelStepper.h>
#include <math.h>


// Define stepper motor connections and motor interface type. Motor interface type must be set to 1 when using a driver:
#define dirPin 2 
#define stepPin 3 
#define motorInterfaceType 1

// Create a new instance of the AccelStepper class:
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);

// conventions : 
// everything in SI units
// times in milliseconds have the suffix ..._ms


int steps = 1600; //total number of steps for a full rotation
double time_to_run = 10000000;
double time_increase_amplitude = 10000;


double amplitude(double t, double omega, double epsilon)
{
  // returns the amplitude in front of the sin
  if(t < time_increase_amplitude)
  {
    return epsilon * t * 1/time_increase_amplitude;
  }
  else
  {
    return epsilon;
  }
   
}

double angle(double t, double omega, double epsilon) //t given in milliseconds
{
  return  amplitude(t, omega, epsilon) * steps * sin(omega* t/1000);
}


int run(double omega, double epsilon)
{
  //returns 1 if there was a bump, 0 if none

  stepper.setMaxSpeed(10 * steps);

  double time_start = millis();
  double time = millis();
  

  while(time - time_start < time_to_run)
  {
    time = millis();

    delay(3); // to simulate a condition to be tested, (if the rope that is to be moved
    // triggered a detector or not)

    stepper.moveTo(angle(time - time_start, omega, epsilon));
    stepper.runToPosition();

    time = millis();
  }

}


void setup() {
  //stepper:

  stepper.setMaxSpeed(10*steps);
  stepper.setAcceleration(5000000);

  
  
}

void loop() {

  delay(4000); //to reset by hand the position of the arm and start the video
  
  double epsilon = 0.05;
  double omega = 14;

  
  run(omega, epsilon);

  

  
}

In this new video, the drifting is obvious, and goes against gravity hence no problem of overtorque.

stepper.setMaxSpeed(16000);
stepper.setAcceleration(5000000);

About that, well it was a bit of a trial and error... I am not sure about the default values. What I do that is rather not very clean is that I give positions and ask accelStepper to go there 'immediately' hence the high speed and acceleration. My motion is dictated by a function I choose, hence I don't want another acceleration from accelStepper. I know this is not very proper, but don't know better either. (See my new reduced code).

Clearly, the problem lies in my implementation of my sinusoidal motion and the fact that there is a delay due to sensors and functions to be evaluated at each step. In the future, I'd like to implement an arbitrary function.

Side note, when I measured the time it took for the device to do 30 periods at omega = 10, I had that in practice omega = 9.48 (in the long code), so I assume that this imprecision is also from the same source.

Thanks again to all of you.

so your target angle formula is (with epsilon = 0.05 and omega = 14) and t is in ms

for the first 10s:

epsilon * (t / 10000) * 1600 * sin(omega * t / 1000)

After 10s:

epsilon * 1600 * sin(omega * t / 1000)

given that t is going to evolve in steps that are related to the duration of the movement, are you expecting the arm to come back to its reference point? (is that what you call drifting ?) there is no chance this would happen... it just goes to different positions on this sine curve
basically your formula gives a nice amplifying sinusoid (the blue line below) but your target angles in time are taken somewhere along this sine curve at various points (the red dots), so the movement you see looks more like the black line

if you want it to come back to the reference point you need to ask for that after the movement:

stepper.moveTo(angle(time - time_start, omega, epsilon));
stepper.runToPosition();
stepper.moveTo(0); // go back to reference or if you want to oscillate around that 0, then go to -angle(time - time_start, omega, epsilon)
stepper.runToPosition();

side notes:

I see your angle includes

 sin(omega * t / 1000)

with omega = 14 --> the sin() function uses radian, so I'm not sure what that factor is useful for.

I would not use double for anything related to time, use unsigned long.

why don't you just use runSpeedToPosition() which does not implement acceleration. just set a reasonable speed

Thanks a lot for your answer J-M-L, but I'm not sure I entierly understand it:

  • First note, this drifting only happens when I introduce the linedelay(3); in my code, between the calls for the motor to go from one position to the next. This is done so as to replicate the goal of my experiment, where the motor moves an arm, creates a ripple in a string attached to it, and some detector is there to check for this ripple... And I need to check for it very often! I can imagine using two arduinos but that would be a hassle.

So my problem boils down to having the motor run smoothly while at the same time being able to record data from a (and in the future multiple) sensors.

  • I can see what you describe in your figure happening, with a correct choice of points indeed. (Btw thanks for taking the time to even make a figure!).

  • What I call drifting is that indeed the motion isn't centered around the initial zero point, i.e. the mean of the sin moves, as it does with the dark dotted line in your figure. I need the blue one, of course.

  • I understand that t evolves in steps, but if we go further in your figure, you just take a few random points, and you should still be around the correct mean position. I don't really see how the dark dotted line would happen except by chance. After all, if the stepper still knows where he is and doesn't lose track, and even if the dotted line thing happens, it should go back to a motion around the center, the dotted line cannot go on indefinitely up... It's as if the zero position of the stepper changed over time. It's here that I'm not sure to follow you.

if you want it to come back to the reference point you need to ask for that after the movement:

  • What I want is the blue motion in your figure. This doesn't mean going to an angle, then to the center, then to another angle, it has to be seemless and 'natural' from my function.

with omega = 14 --> the sin() function uses radian, so I'm not sure what that factor is useful for.

  • 14 is a random choice : in my real application, I run over a wide number of omega/epsilon (to make a bifurcation diagram).

why don't you just use runSpeedToPosition() which does not implement acceleration. just set a reasonable speed

  • I will. At the same time, about the speed, note that my motor needs to go rather fast for my physical application to happen (basically there is a string attached to the arm, and the string is attached to a far end, with most of it laying on the ground. the arm of the motor moves and creates little ripples that propagate along the portion of string that was previously on the ground. for this we need a significant rotation speed and amplitude). But again, I only experienced my problem with the 'delay(3)'.
  double time_start = millis();
  double time = millis();

  while (time - time_start < time_to_run)
  {
    time = millis();

You should use 'unsigned long' for millis() values, not 'double'.

what I'm saying is that your sine function depends on time T0, T1, T2, T3, .... and you don't control the time when you call the math function - so your Tx are not controlled by your code and you can end up anywhere on the blue line at each call. You are likely to get my black line or something that does not feel sinusoid at all.

If you need the blue line, then I would control this almost step by step and be in charge of timing, not let the library take guesses at acceleration etc.

you define a update position frequency that is compatible with how fast your motor can go and you issue commands to take steps in a non blocking way (runSpeed() not runSpeedToPosition()) - which will let you take measures
something like this

...

unsigned long startTime, lastTick;
const unsigned long tickPeriod = 10; // move along the sine curve every 10ms

void setup() {
  ...
  stepper.setSpeed(1600); // 1 full rotation per second
  startTime = lastTick = millis();
}

void loop() {
  stepper.runSpeed();
  if (millis() - lastTick >= tickPeriod) {
    lastTick = millis();
    stepper.moveTo(angle(lastTick, omega, epsilon));
  }

  // here you can do something else if it's fast
  ...
}

There is another thing I would add: this may be a mechanical problem. The arm looks fairly complex and sounds a bit "rattly". It is possible you are suffering mechanical resonances which cause the stepper motor to mis-step.

Driving stepper motors is much more complicated than you might think when they have a load on them. They all have resonant frequencies which you can find out by reading the data sheets. And when the load itself has one or more resonant frequencies it can lead to complications such as mis-steps or double steps.

So although J-M-L has clearly nailed the software issues, try altering the delay times between the steps (WITHOUT altering anything else) to see if the behaviour changes.

Finally, having read your posts I think a stepper motor is completely the wrong approach. I would expect to see a servo motor in a role like this. Far better, because you've got closed loop position feedback.

Finally, having read your posts I think a stepper motor is completely the wrong approach. I would expect to see a servo motor in a role like this. Far better, because you've got closed loop position feedback.

This is what I went for initially but couldn't find fast and strong enough servos. (At least at a reasonable price). I need about 1 kg of force at about 10 Hertz.

There is another thing I would add: this may be a mechanical problem.

I agree (and expressed a similar concern in the original post). And this is only a trivial version of what I want to implement in the future, a Russell Linkage.

comrad_dau:
I need about 1 kg of force at about 10 Hertz.

You need to say over what distance, so we can calculate the power requirement.

comrad_dau:
I agree (and expressed a similar concern in the original post). And this is only a trivial version of what I want to implement in the future, a Russell Linkage.

Which reinforces my view that a stepper motor is going to be no good. If you can't source an affordable servo motor (and there are some serious motors that would eat this job for breakfast), how about a variable speed motor with a crank to do the jiggling? There are loads out there with different reduction ratio gearboxes.