High speed quadrature encoder issue (Teensy 4.0)

nativebird

New member
I am having an issue utilizing a high resolution quadrature rotary encoder in a high-speed application. Up to about 1700 RPM, everything works fine, the Teensy 4.0 tracks the pulse count properly. However, beyond that speed, the count is not updated. The encoder has 2000 pulses per revolution, and the max speed needed is about 2000 rpm, which should give a signal frequency of about 67khz. The encoder itself (link below) is rated for 6000 rpm, and I've confirmed with the oscilloscope that it is outputting the proper signal at 2000 rpm and above. Is the Teensy not fast enough to keep up with this sort of input?

I'm using interrupts to track the pulses from the encoder. I also tried using dedicated encoder libraries but had the same issue.

Here is the code:

C++:
// Encoder pins, Phase A and B for the quadrature signal, Phase Z pulses once per rev.
const int encA = 15;
const int encB = 14;
const int encZ = 16;
const int encoderPulsesPerRev = 2000;

//Counters
long counter = 0; //Encoder counter
long lastCounter = 0;
long revs = 0; //Encoder revolutions (Z output)
long lastRevs = 0;
long currentRevs = 0;
long diff;

//Time tracking for calculating RPM
long currT = 0;
long prevT = 0;
int sampleT = 1000;

float rpm;





void setup() {

  Serial.begin(9600);

  pinMode(encA, INPUT_PULLUP);
  pinMode(encB, INPUT_PULLUP);
  pinMode(encZ, INPUT_PULLUP);

  attachInterrupt(encA, isrA, RISING);
  attachInterrupt(encB, isrB, RISING);
  attachInterrupt(encZ, isrZ, RISING);
  
}

void loop() {

  currT = millis();

  //Calculating RPM
  if(currT - prevT >= sampleT){
    rpm = (float(revs-lastRevs)/sampleT)*60000;
    prevT = currT;
    lastRevs = revs;

    diff = counter - lastCounter;
    lastCounter = counter;
  
    Serial.print("RPM: ");
    Serial.println(rpm);
    Serial.print("Pulse Count: ");
    Serial.println(counter);
    Serial.print("Revolutions: ");
    Serial.println(revs);
    Serial.print("Difference: ");
    Serial.println(diff);
    Serial.println();
  }
}

//Interrupt routines
void isrA() {
  if (digitalRead(encB) == LOW){
    counter++; }
  else {
    counter --;}
}

void isrB() {
  if (digitalRead(encA) == LOW) {
    counter --; }
  else {
    counter ++; }
}

void isrZ() {
  revs ++ ;
}


I've also included some screenshots with the serial monitor output: here you can see the encoder tracking properly at lower rpms, with about a 20k-50k difference in the count from one sample to the next, depending on speed and acceleration.

tracking_proper.png


Here you can see where the counter abruptly cuts off and stops updating, somewhere between 1800 and 1900 rpm.

transition.png



Here is the link to the encoder:
Encoder


Any thoughts? Thank you for your help.
 

Attachments

  • tracking_fail.png
    tracking_fail.png
    94.4 KB · Views: 6
Have you tried the library below that uses the T4.x quadrature hardware? That's a much better solution for your project. At 1700 rpm, you'll have 1700/60 * 8000 = ~226K interrupts/sec. The T4.0 can do that, but the library below can provide a better solution with 0 interrupts.


If you want to do it with A and B interrupts, you need interrupts on both rising and falling edges on both A and B, and you need to take the state of both A and B into account on each edge. See the source of the PJRC encoder library for the algorithm.
 
Have you tried the library below that uses the T4.x quadrature hardware? That's a much better solution for your project. At 1700 rpm, you'll have 1700/60 * 8000 = ~226K interrupts/sec. The T4.0 can do that, but the library below can provide a better solution with 0 interrupts.


If you want to do it with A and B interrupts, you need interrupts on both rising and falling edges on both A and B, and you need to take the state of both A and B into account on each edge. See the source of the PJRC encoder library for the algorithm.
Thank you for the reply. I tried out this library, but unfortunately I'm still running into the same issue. I will look into the PJRC Encoder library and see if I can figure out the solution using interrupts.
 
You need CHANGE interrupt, but you can just trigger on A for the interrupt if you don't mind 1/2 the resolution. Triggering only on rising edges is distinctly odd timing - draw the waveforms and you'll see...

BTW if the encoders are on a long cable you'll need stronger pullup resistors than the built-in one's, 4k7 is a good value to do for.
No need to interrupt on Z, sample Z on the other interrupts...
 
Thank you for the reply. I tried out this library, but unfortunately I'm still running into the same issue. I will look into the PJRC Encoder library and see if I can figure out the solution using interrupts.
I don't think the problem is on the software side but rather on the hardware side. @MarkT's suggestion is good but I would even go for 1K pullup resistors [pulled up to 3V3]. See also this thread. Your rotary encoder is very similar.

With the standard Encoder library, the code is very simple:
C++:
#include <Encoder.h>
Encoder myEnc(15, 14);

void setup() {
}

void loop() {
  long newPosition = myEnc.read();
  Serial.println(newPosition); // encoder lib will output 8000 pulses per rev
  delay(10);
}

Paul
 
Back
Top