Stepper motor going out of time

Hi,

I'm running a stepper motor with it's micro step set to 1600 with the following code

# define EN  24 //ENA+(+5V) stepper motor enable , active low     Orange
# define DIR 26 //DIR+(+5v) axis stepper motor direction control  Brown
# define STP 22 //PUL+(+5v) axis stepper motor step control       RED

unsigned long currentTime;
unsigned long interval;

void setup() {

  
  pinMode (EN,OUTPUT); //ENA+(+5V)
  pinMode (DIR,OUTPUT); //DIR+(+5v)
  pinMode (STP,OUTPUT); //PUL+(+5v)
  digitalWrite (EN, LOW); //ENA+(+5V) low=enabled
  digitalWrite (DIR, LOW); //DIR+(+5v)
  digitalWrite (STP, LOW); //PUL+(+5v)

}

void loop() {

 interval=(currentTime +625-10);
 
if(micros() > interval){digitalWrite (STP, HIGH);
                        digitalWrite (STP,LOW);
                        currentTime = micros();}

}

what that should do, with the interval set at 625 microseconds, is spin the stepper at 1 revolution per second, but I've noticed that the stepper goes out over a couple of minutes with some sort of accumulative error hence the -10. Is this because that's how long it takes to run the code? I need this to be accurate as it's for a CNC application. Is there a better way of doing this?

Many thanks
Chris

Try this approach to the timing

if (micros() - prevStepMicros >= 625) {
   prevStepMicros += 625;
   digitalWrite(STP, HIGH);
   digitalWrite(STP, LOW);
}

Always use subtraction with millis() and micros() to avoid problems when the values rollover back to 0.

Adding the interval to the time for the previous step avoid an accumulation of small errors.

Arduinos don't run at precisely 16MHz so longer term timing is likely to drift relative to a clock.

...R

Thanks, now you mention it I have seen that before, with the rollover, it’s a lot more accurate…however(there’s always one) it’s causing a problem elsewhere in the code. This sketch controls the speed of a shaft based on the speed of another shaft(which sometimes changes speed under load) I also need to rotate the second shaft at different ratios to the first shaft. Here’s the full code

# define EN  24 //ENA+(+5V) stepper motor enable , active low     Orange
# define DIR 26 //DIR+(+5v) axis stepper motor direction control  Brown
# define STP 22 //PUL+(+5v) axis stepper motor step control       RED
# define encoderPin1 20
# define encoderPin2 21

volatile int lastEncoded = 0;
volatile long encoderValue = 0;
volatile long lastencoderValue = 0;
volatile long stepperInterval=0;
volatile long previousInterval=0;

//temp
//volatile int MSB;
//volatile int LSB ;
//volatile int encoded ;
//volatile int sum;
//unsigned long lastSum;
int count, lastCount;
int interval;

 
unsigned long currentTime, prevStepMicros;
float multiplier;

void setup() {

  Serial.begin(9600);

  pinMode (EN,OUTPUT); //ENA+(+5V)
  pinMode (DIR,OUTPUT); //DIR+(+5v)
  pinMode (STP,OUTPUT); //PUL+(+5v)

  pinMode(encoderPin1, INPUT); 
  pinMode(encoderPin2, INPUT);

  digitalWrite (EN, LOW); //ENA+(+5V) low=enabled
  digitalWrite (DIR, LOW); //DIR+(+5v)
  digitalWrite (STP, LOW); //PUL+(+5v
 
  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  digitalWrite(encoderPin2, HIGH); //turn pullup resistor on
 
  attachInterrupt(digitalPinToInterrupt(encoderPin1), updateEncoder, CHANGE); 
  attachInterrupt(digitalPinToInterrupt(encoderPin2), updateEncoder, CHANGE);

 

}
 
void loop(){

  multiplier=3;

  interval=stepperInterval*multiplier; 
                        
 if ((micros() - prevStepMicros >= interval)&& (count !=lastCount)){prevStepMicros +=interval;
                                                                   lastCount=count;
                                                                   digitalWrite(STP, HIGH);
                                                                   digitalWrite(STP, LOW);}
                                                                     
//  if((micros() > currentTime + (interval))&& (count !=lastCount)) {currentTime = micros();
//                                                                  lastCount=count;
//                                                                  digitalWrite (STP, HIGH);
//                                                                  digitalWrite (STP,LOW);}
                                                             
                                                             

                                             
}
 
 
void updateEncoder(){

  count++;
 
  stepperInterval=micros()-previousInterval;

  int MSB = digitalRead(encoderPin1); //MSB = most significant bit
  int LSB = digitalRead(encoderPin2); //LSB = least significant bit
 
  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
 
  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {encoderValue ++;digitalWrite (DIR, HIGH);};
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {encoderValue --;digitalWrite (DIR, LOW);};

  lastEncoded = encoded; //store this value for next time

  previousInterval=micros();

 }

the multiplier variable is what changes the speed of the second shaft, with the new piece of code that no longer works, trouble is I’m getting to the limits of my current knowledge of the Arduino and steppers, I don’t know if I’m going down a blind alley with this

To my mind you have too much code in your ISR. What is it all supposed to do?

Multibyte variables that are updated in an ISR should NOT be used like this

interval=stepperInterval*multiplier;

because the value could change while it is being read.

You should make a copy of the variable with interrupts disabled and use that in the rest of your code - like this

noInterrupts();
   copyStepperInterval = stepperInterval;
interrupts();

...R

The code in the isr works out what direction the encoder is going...found it on the web, to be honest I'd tried several code samples but that was the first one that worked I liked the way it used bitwise operators thought it was quite a novel approach, the count variable is there so I can tell if the encoder is actually rotating . I take your point about the disabling the interrupt when reading volatile variables, again something I had read about but failed to realise the implications. I'll implement that tomorrow.

But why is this in the ISR ?

stepperInterval=micros()-previousInterval;

Are you trying to synchronize something?

If so, what?

...R

Yes, I have two shafts one driven by a 3 phase motor the will slow down when a load is applied to it and a second shaft that is driven by the stepper motor. The stepperInterval variable stores the time in microseconds of the change of state on the encoder outputs (channels a and b) that tells me what speed the encoder is running at on the first shaft in microseconds per pulse (1600 pulses per revolution. At 60 RPM it works out at 625 microseconds. I then use that to drive a second shaft via the stepper motor and can vary the second shafts speed by the multiplier variable and keep them sync'd. If the first shaft changes speed because loading the second shaft would be adjusted too. I was thinking along the lines that as the timing is quite critical the best place to but this would be inside the ISR, I didn't think there was any other way of doing it.

In that case, what is all this stuff doing in the ISR

 int MSB = digitalRead(encoderPin1); //MSB = most significant bit
  int LSB = digitalRead(encoderPin2); //LSB = least significant bit
 
  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
 
  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {encoderValue ++;digitalWrite (DIR, HIGH);};
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {encoderValue --;digitalWrite (DIR, LOW);};

  lastEncoded = encoded; //store this value for next time

Would this work

void updateEncoder(){

  latestPulseMicros = micros();
  stepperInterval = latestPulseMicros - previousInterval;
  previousInterval = latestPulseMicros;

 }

Note that NOT calling micros() twice avoids the risk that its value changes between calls - although an error of 4 in 625 is probably not important.

…R

Yes, it does thanks. I'm not sure if I can move the rest of the code out of the ISR. I'm not 100% sure about how it works just yet(Yes I know I should not use code that I don't understand, but I needed a quick fix and that fitted the bill) I would probably have to find a different way of testing for encoder direction. I've only just starting to use interrupts so I'm feeling my way through this. The way I understand it is that the interrupt interrupts the main program flow and runs the ISR then goes back to the main program code? If that's the case I couldn't see why it mattered if the code was inside the ISR or in the main code it still had to process it and would take the same amount of time either way. Also as the direction is just as important as the speed I wanted to have them both at the same time. I'm wondering if I really need to use an interrupt to do any of this as the fastest response I need will be around 320 microseconds.

TobyOne:
Also as the direction is just as important as the speed

It may be in your mind but you have not communicated the reason to us. I did ask in Reply #3 what is all the code in the ISR supposed to do.

And you still have not given us a description of the project you are trying to create. It is much easier to give useful advice when we understand the full context.

...R

thought I'd done that in #6 and #4. This for a lathe, the spindle(first shaft) is monitored by the encoder so I know what rpm it is doing. The leadscrew (second shaft) drives the cutting tool. Normally these would be sync'd together by sets of gears and depending on what the ratio of the gears are will change the speed of the cutting tool. the two uses for this are 1. to cut threads and 2. to maintain a constant movement of the tool to achieve better surface finishes. I want to do away with the gears and sync the two shafts with the use of an encoder and a stepper. The spindle will change speed when cutting threads due to the tool loading which is why it needs to constantly monitored and the stepper speed needs to adjusted otherwise the thread will not be accurate. The direction is also important as the lathe has to be run in reverse when going back to the start of the thread(you can't cut a thread in one go it has to be done in several passes) Thread cutting typically happens around 60 to 100 rpm and normal turning anywhere up to 3000rpm.

Actually forget it, I'll work it myself, dealing with you is harder than dealing with the problem.

Now that I know what you are doing the code in your ISR makes more sense.

However I am finding it more difficult to understand this comment in your Original Post

, but I've noticed that the stepper goes out over a couple of minutes with some sort of accumulative erro

If you are detecting and updating the speed several times per revolution of the chuck how can there be a period of minutes in which to detect an accumulated speed error?

If you had told us that the difference between the count of encoder-pulses and the count of stepper-steps was diverging I could understand that.

...R

I think the drifting out of sync could be down to the 4us resolution of micros(). Say for example I just want shaft A and shaft B to revolve at the same speed at 60rpm, the encoder on shaft A will produce 625us between pulses (1600 pulses per revolution) However micros() will return 628us between pulses and send that to the stepper on shaft B, would that not mean by the end of one rotation we have lost 4800us (3usx1600)? If that's correct the longer the shafts are running the worse it will get.

Sorry about the pervious last comment, was late, been a long day and I was tired and emotional :0) I have to deal with the general publics(read that computer illiterate) IT problems so I'm well aware of getting the right information and context.

The approach Robin gave in #1 will not drift over time since it steps an exact count each time. You are then
only limited by the system clock accuracy for drift.

Following from what @MarkT has said, the 4µsec granularity will mean that some steps take place at times that are slightly too late but that will be compensated by some that are a little shorter so that over a long time the maximum error for any step will not exceed 4µsecs.

However, I come back to what I said in Reply #11 - it seems to me that it is the relationship between the step count and the encoder count that you should be concentrating on. If you had a stepper motor driving the chuck that is how you would achieve synchronization.

Another question ... do you have some means to identify absolute synchronization so that (for example) you can tell that when the chuck is at "top dead centre" the stepper motor is at XXX. That sort of system would allow correction for small errors, or a complete system stop for an unacceptable error.

...R

Yes it is the synchronisation between the encoder and the stepper that is the problem. The stepper will drift out of sync with the encoder because of the 4us resolution unless you can get the encoder to rotate so that between pulses it's a multiple of 4. I don't think it would be possible to drive the chuck from a stepper as I believe speed range is to wide. I have seen 5 or 6 axis machines that must be using steppers to drive the chuck but I doubt those steppers(I suspect they are closed loop servos) come in at a price range that a hobbyist could afford. Checking the TDS might work as you could limit the slip to one rotation, of course the encoder I have does not have a one pulse per revolution option. Maybe some sort of count pulse check would suffice.

Can someone confirm the suspected accumulative error that I have described in #12? That is to say that if the encoder is producing a pulse every 625us with 1600 pulses per rotation, timing it with micros() will report a 628us gap between pulses rather than 625us, use that to drive the stepper and it'll be going slightly slower that the encoder, positionally getting further behind the longer it runs.(just in case you've just jumped in on this, the stepper and encoder are not joined in anyway and are on different shafts)

Some time ago I did an example showing how to synchronize two steppers. With a lathe in mind.

Instead of using the current position of the "spindle stepper" you could of course also use your encoder output. Here a quick video showing the experiment.

This was done for TeensyStep but the algorithm should work for any library which is able to change the stepper speed on the fly (video links to the code).

TobyOne:
Yes it is the synchronisation between the encoder and the stepper that is the problem. The stepper will drift out of sync with the encoder because of the 4us resolution unless you can get the encoder to rotate so that between pulses it's a multiple of 4.

I'm obviously not on your wavelength on this matter. Let's try for mutual clarification.

You say the encoder produces 1600 pulses per revolution (I presume that means one revolution of the chuck). How many stepper steps should there be for one revolution of the encoder?

I don't think it would be possible to drive the chuck from a stepper

I was not suggesting that you should - I was just using the idea to illustrate my understanding of the problem.

...R

The comment about the chuck was a valid point.The encoder on the spindle shaft,which has the chuck on it, produces 1600 pulses per revolution it is a 400p device and by reading the change on both the a and b channels you get 1600 pulses. The stepper motor on the leadscrew is set to micro step at 1600 pulses per revolution, if it was a simple case of the spindle shaft rotating at the same speed as the leadscrew you could call a digitalwrite from the encoder isr and the the two would run sync'd no problem. But I need the stepper to drive the leadscrew at varying speeds so I get a ratio between the chuck speed and leadscrew. For example if the chuck is rotating @ 60 rpm the encoder would return 625us between pulses, now if I take that 625us and double it and use that figure to space the pulses to the stepper that will rotate the stepper and the leadscrew at half the speed of the chuck, that was just one example there are many different ratios required.

Luni64, I'm not using a stepper on the spindle it's just powered by a standard 1 phase induction motor, I'm just using an encoder to monitor it's rpm