Rotary encoder- how can I detect when it's stopped rotating?

Hi everyone,

I’ve been doing this all day and have got a mental block, I’ve got a Rotary encoder with a resolution of 1600 pulses per revolution, I’ve also got a stepper motor set at 1600 microsteps. I’m using the Rotary encoder to drive the stepper. I need to vary the stepper speed by reading the encoder and multiplying the reading to produce a different but proportional speed on the stepper, now I’ve got a sketch that I’ve cobbled together from some examples and it all works EXCEPT the stepper still rotates when the encoder is not rotating, I need to fine a way of stopping it doing this, any ideas? as always any help would be much appreciated.

Many thanks

Chris

# define EN  33 //ENA+(+5V) stepper motor enable , active low     Orange
# define DIR 35 //DIR+(+5v) axis stepper motor direction control  Brown
# define STP 31 //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;
 
unsigned long currentTime;
float multiplier;

void setup() {
  
  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=1.2;

  if(micros() > currentTime + (stepperInterval*multiplier)){currentTime = micros();
                                                            digitalWrite (STP, HIGH);
                                                            digitalWrite (STP,LOW);}

}
 
 
void updateEncoder(){

  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, LOW); };
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {encoderValue --;digitalWrite (DIR, HIGH);};

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

  previousInterval=micros();
 
}

Maybe something like:

if lastEncoder  != currentEncoder {
  inactivityTime == currentMillis
}
lastEncoder = currentEncoder

if currentMillis-inactivityTime >= inactivePreset
  // do thing A
else 
 // do thing B

Perhaps the quickest way is to remember your encoder value and see if the new encoder value is equal to the remembered value. If equal, then stop stepping the motor.

Paul

Thank you gentlemen, wood and trees spring to mind here, had another look at it this morning and everything I needed was there just needed to rearrange things slightly. My solution, under your guidance, was to pull out the sum variable and make it global then remember it for the next pass through the loop

if(micros() > currentTime + (stepperInterval*multiplier)){currentTime = micros();
                                                            if (sum !=lastSum){digitalWrite (STP, HIGH);}
                                                            lastSum=sum;
                                                            digitalWrite (STP,LOW);}

just need to tidy it up so the digital write low is not being called when not required

Thanks once again
Chris

ah..kind of works but now the multiplier doesn't work, in fact however fast the encoder turns the stepper turns at a constant speed, not quite what I had in mind but I think I'm on the right track

oh dear, this is what comes when coding half asleep :confused: it's not listed but there is a call to serial print which I was using to see what was going on and which totally screws up the stepper timing! Very Heisenberg :slight_smile: remove that and it almost works then rearrange the stepper code like this

if((micros() > currentTime + (stepperInterval*multiplier))&& (sum !=lastSum)){currentTime = micros();
                                                                         digitalWrite (STP, HIGH);
                                                                         lastSum=sum;
                                                                         digitalWrite (STP,LOW);}

also and it works without any timing issues that I see at the moment

Edit: also don't do any calculations in the if statement as that also appears to affect the stepper timing, move the stepperInterval*multiplier calculation outside the if statement.

But now it can only take one step for each step of the encoder. Make the multiplier 0.4 and you will see it steps no faster than what you have now.

With multiplier 1.2 then it can only take steps when the encoder is decellerating. Wind it faster and it stops making steps.

Doug's suggestion is much smarter than Paul's.

What I eventually settled on using the lastEncoded variable from the ISR and just checking whether it had changed

 interval=StepperInterval*multiplier; 

 if((micros() > currentTime + (interval))&& (lastEncoded != previousEncoded)) 
{currentTime = micros();
  prevStepMicros+=interval;
  lastEncoded=previousEncoded;
  digitalWrite(STP, HIGH);
  digitalWrite(STP, LOW);}

if it's not changed it's not moving! yeah I know, lastEncoded, previousEncoded are a bit similar but I'm braindead at the moment, I will change those later.
Maybe not smarter but simpler and appears to work at any speed...
I suspect this will come back to bite me at some point.

You still have the same problem. Your stepper misses any steps while the encoder is accelerating and can never take more than one step per click.

Additionally, since you are using addition to calculate times in microseconds, you will have a glitch every 70 minutes. That glitch may be severe enough to lock it up for 70 minutes.

Yeah got that about addition and micros, just realised there may be another issue here as well in the fact that the resolution on micros() is 4us, so if the encoder is rotating at 60 rpm with 1600 pulses per revolution then you should get 625us between pulses but micros() will return 628us between pulses. So that would mean by the time it’s done one revolution we’re 4800us (3usx1600) out?

No

micros returns the nearest multiple of 4, so its going to average out in the long run, not accumulate.

Well the way I’m looking at is this, and for arguments sake the encoder is 60rpm generating a pulse every 625us which is what it should be

encoder true timing 625,625,625,625,625,625,625,625,625,625 =6250
and in this application
micro() returns timing as
628 sends that to a
stepper and u get 628 ,628 ,628 ,628 ,628 ,628 ,628 ,628 ,628 ,628= 6280

a bit of an exaggeration but I think it illustrates my point.