Optical Encoder skips pulses at high speeds

Hey all!

I am using an optical encoder and interrupts on a Nano, and I seem to be able to get the right pulses when I rotate the encoder shaft slowly, but when I go fast it jumps and gives me smaller numbers of pulses counted. I have no idea why.

The optical encoder used has PPR of 5120, and max RPM of 5000, its an NPN (Open Circuit), and does ABZ (but I am only using AB at the moment, not sure how to use Z to be honest).

My questions are:
1- In my current code I am interrupting for rising of the A signal, what would happen if I measured the B Rising signal as well? What about if I also did the A & B Falling signal? I know that its supposed to increase my resolution, is that a good idea? How would I even implement it via code?

2- This is how I am calculating the potential max pulses the interrupt might need to log at max speed.
5000 RPM/60 seconds = ~83 Rounds per Second (RPS)
5120 PPR * 83 RPS = ~424000 pulses in a second at max RPM
therefore 1/42400 = ~0.000002seconds = 2microseconds
Is this calculation correct?

3- Please tell me how to fix this speed issue!! When I rotate the shaft slowly, I can see the pulses increase nicely, 5120/4 at 90 degrees and 5120/2 at 180 degrees and so on. But when I do it fast it gives me small numbers, like 1000 pulses counted for a full rotation.

This is the code I am using:

#define encoderPinA 2
#define encoderPinB 3

volatile long encoderCount=0;


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(encoderPinA),handleEncoder, RISING);
 // attachInterrupt(digitalPinToInterrupt(encoderPinB),handleEncoder2, RISING);

}

void loop() {
  // put your main code here, to run repeatedly:

  Serial.println(encoderCount);
  delay(1000);

  }

}

void handleEncoder() {

  if (digitalRead(encoderPinA)>digitalRead(encoderPinB)){
    encoderCount++;
  }
  else {
    encoderCount--;
  }
}

Look forward to fixing this!!

Best

You have to suspend interrupts while dealing with encoderCount to prevent your ISR from changing its value while it is being read.

Other than that, I think 424,000 pulses/second is too high.

What is the part number for your encoder?

1 Like

Correct.

A 16 MHz controller has 32 clock pulses left for handling a single encoder pulse, that's impossible IMO. Use an encoder with less PPR or a controller with higher clock frequency.

1 Like

I see,

In this article they say they can get down the interrupt to 1.5microseconds, that should theoretically then at least allow me to use this one encoder at full speed, correct?

But if I were to add another one then I would overload and miss pulses?

I guess my question is, what is how come the 424kHz pulse frequency of my encoder higher than the 16MHz of the arudino clock?

Appreciate your support!

I guess the other thing confusing me, is that Im rotating the shaft by hand right now, and I am definitely not getting anywhere close to 5000rpm doing that, so why is it still missing pulses?

like if I'm rotating at like 200rpm with my hand, which is about the speeds I am working with, technically the interrupt pulse frequency would be ~17kHz. I imagine that should be fine for it to capture all the pulses no?

What you observe is the speed limit of interrupt handling. Continued rotation is not required to overload the controller temporarily.

You can increase the limit a bit by using PCINT instead of the external interrupt handler.

Not quite correct because it doesn’t account for the time to read the two(!?!) pins, do the comparison and do the 32 bit math. All the overhead of processing within the interrupt makes it more likely to skip new steps.

I highlight “two” because you trigger the interrupt on the rising edge and then re-read it. Since it should be high after RISING you should be able to save a read operation.

Your code does not compile.
There is a "}" error in the loop function. There is 1 more than necessary.

Try this code.
I changed the serial speed in the setup and the routine in the loop.

And I simulated it here using pulse generators to replace the encoder.

//https://forum.arduino.cc/t/optical-encoder-skips-pulses-at-high-speeds/1275203/1
#define encoderPinA 2
#define encoderPinB 3
volatile long encoderCount = 0;
//-------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(encoderPinA), handleEncoder, RISING);
  // attachInterrupt(digitalPinToInterrupt(encoderPinB),handleEncoder2, RISING);
}
//-------------------------------------------------------------
void loop() {
  if ((encoderCount % 1000) == 0 )  {
    Serial.println(encoderCount);
  }
}
//-------------------------------------------------------------
void handleEncoder() {
  if (digitalRead(encoderPinA) > digitalRead(encoderPinB)) {
    encoderCount++;
  }
  else {
    encoderCount--;
  }
}

thank you so much!

What am I supposed to see with this code? Because when I hooked up my encoders I am just seeing 0's?

Will this solve the fact that my encoder PPR is 5120? is that not too high for the arudino interrupt to read every pulse at high RPM?

I see, so even in the best case scenario though, my encoder at a lower RPM of say 200, will be pulsing the interrupt at 16kHz, is that okay for my Arduino to handle?

What if I added 4 encoders and used an ESP32, or PCINT with the Nano, will that overload my MCU?

I am just still a little lost why the encoder seems to miss pulses even at a speed like 200RPM, I know the high PPR doesn't help, but isn't a 16kHz pulse rate okay for the Arduino?

but a 200RPM with 5120PPR, corresponds to about ~16kHz interrupt pulses, why is the interrupt still missing pulses and giving me smaller values in my EncoderCount value?

If I change this optical encoder to say 320PPR or 640PPR, will all my problems disappear and I will get all the pulses read even at 200RPM+?

Is that assumption correct?

Well, at 200RPM you get 1/(200/60*5120)=58.6us, or 16e6/(200/60*5120)=937cycles between pulses which is apparently too small a time compared to the overhead costs of handling it and doing everything else in your application. How much of 937cycles is consumed by saving and restoring processor state for an interrupt, calling digitalRead twice, doing the arithmetic of comparing the two integers, doing the 32 bit increment/decrement? From the reported behavior It is apparently a large fraction of the available time budget.

Does your application need bidirectional 0.07° resolution at 5000RPM? Some of the better encoders have a set of A,B,Z outputs with the Z giving a 1 pulse per revolution index signal.

Some processors can receive the quadrature inputs and directly drive a counter/timer in hardware. An Arduino Uno could count (unidirectional) pulses with hardware on the pin5/PD5/T1 line into the 16bit Timer1 TCNT1 register at speeds up to maybe 8mHz or 8e6/5120*60=93750RPM

image

The z output is just a single pulse issued every rotation. Read it with a RISING interrupt.

1 Like

You may be wrong with the 200 RPM. Which naximum RPM is shown?

For a reasonable test make your motor increase speed and remember the highest RPM shown. That's the definite limit for your encoder and code. Where definite means that you have to change the encoder or your controller or your code for higher RPM.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.