Go Down

Topic: Stepper motor going out of time (Read 868 times) previous topic - next topic

TobyOne

Hi,

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

Code: [Select]
# 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

Robin2

Try this approach to the timing

Code: [Select]
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
Two or three hours spent thinking and reading documentation solves most programming problems.

TobyOne

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

Code: [Select]
# 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

Robin2

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
Code: [Select]
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
Code: [Select]
noInterrupts();
   copyStepperInterval = stepperInterval;
interrupts();


...R
Two or three hours spent thinking and reading documentation solves most programming problems.

TobyOne

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.

Robin2

But why is this in the ISR ?
Code: [Select]
stepperInterval=micros()-previousInterval;

Are you trying to synchronize something?

If so, what?

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

TobyOne

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.

Robin2

In that case, what is all this stuff doing in the ISR
Code: [Select]
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
Code: [Select]
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
Two or three hours spent thinking and reading documentation solves most programming problems.

TobyOne

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.

Robin2

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
Two or three hours spent thinking and reading documentation solves most programming problems.

TobyOne

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.

Robin2

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
Quote
, 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
Two or three hours spent thinking and reading documentation solves most programming problems.

TobyOne

#12
Mar 27, 2019, 10:52 am Last Edit: Mar 27, 2019, 10:59 am by TobyOne
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.

MarkT

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.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

Robin2

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
Two or three hours spent thinking and reading documentation solves most programming problems.

Go Up