Teensy 4.x H/W Quadrature Encoder Library

I want to use the QuadEncoder + Interrupts on PinA and PinB. It seems that switsching the Pins (2 and 3) with the XBAR to the encoder disabled the interrupts and vice versa, attaching interrupts to the Pins, disables the QuadEncoder. The later blocks the former.

Any idea to habe Filtering of the QuadEncoder and Interrupt response on the signal transition (without applying the signal to two Pins)?

Best regards

Edmund

Not really sure what you are asking. But if the question is if you can attach an interrupt to same pins as you are using while you are using the encoder library the answer is no. The library uses it own set of interrupts to determine when the triggers are set.

The library provides filtering capabilities as illustrated in the QuadEncoder Example.

You might want to check this thread out on how to set up an interrupt based on a compare value: QuadEncoder Compare Interrupt
 
OK, thank you. As far as I could see that is not described on the teensy homepage or in your library, maybe it should be added.

Anyhow the library works very well for me and the filter function makes it very stable for high speeds.
 
is it possible to use 6 quadrative encoders on a T4.1 ?

how does it work ? does the t4.1 have 2 processors and one of them is dedicated to counting pulses ?
 
is it possible to use 6 quadrative encoders on a T4.1 ?

how does it work ? does the t4.1 have 2 processors and one of them is dedicated to counting pulses ?

You can get the library and its examples, etc. from the github link below. The T4.1 has only one CPU, but it also has many peripherals, including several types of timer that can count pulses or measure periods, and one particular type of timer called QuadTimer that can do quadrature up/down counting on the basis of A and B signals from an encoder. It looks like the library supports only 4 instances, but I'm not sure if that is the absolute limit. Still, if you use this library for 4 of your 6 encoders, that should help a lot. Use QuadEncoder for whichever encoders have the highest resolution and speed, and use the Encoder library for the slower ones. There is a link to the T4.1 processor reference manual at the bottom of the T4.1 product page at pjrc.com. I highly recommend at least looking through the table of contents to get a sense of the capabilities, and then read the section on QuadTimer.

https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library
 
Hi Joe,

thank you for the prompt reply - i like your idea to improve performance using 4 of the 6 encoders with this library

i will do some reading :)
 
is it possible to use 6 quadrative encoders on a T4.1 ?

how does it work ? does the t4.1 have 2 processors and one of them is dedicated to counting pulses ?

joepasquariello said:
It looks like the library supports only 4 instances, but I'm not sure if that is the absolute limit.
The IMXRT1062 has a only 4 hard wired quad encoders built in so the library was built that limit. So yes its a hard limit on the library
 
Hi mjs513

I'm having a curious issue with this library when definint phase A as pin1 and phase B as pin 3 on a Teensy 4.1.

The signal and phase offset looks good on the scope (there is overshoot and a little ringing) but it only randomly counts +- a count occasionaly.

DS1Z_QuickPrint19.png

If I define pin 2 to be phase A the whole lot works fine.

I have changed the teensy 4.1 to make sure it is not a hardware issue and the problem occurs with both.

I note that UART1 default Tx is also Pin1. I am not using this UART but I am wondering if I need to disable that mapping somewhere in case that is keeping pin1 defined as an output somehow?
 
Hi mjs513

I'm having a curious issue with this library when definint phase A as pin1 and phase B as pin 3 on a Teensy 4.1.

The signal and phase offset looks good on the scope (there is overshoot and a little ringing) but it only randomly counts +- a count occasionaly.

View attachment 30800

If I define pin 2 to be phase A the whole lot works fine.

I have changed the teensy 4.1 to make sure it is not a hardware issue and the problem occurs with both.

I note that UART1 default Tx is also Pin1. I am not using this UART but I am wondering if I need to disable that mapping somewhere in case that is keeping pin1 defined as an output somehow?

That really is strange. I just did a simple setup with a KY-040 rotatary encoder attached to pins 1 and 3 to try and duplicate. I then ran the simple encoder example and it did not have a problem working:
Capture.PNG

You could try telling it to use pullups:
Code:
QuadEncoder knobLeft(1, 1, 3,1);
to see if it helps.

Also could be something in the sketch or your hardware? Does it work with Paul's Encoder library?
 
The Encoder example available from the Arduino list works just fine, is this using your library?

If so, there is obviously something in my project screwing things up.... will need to dig around and look for anything playing with pin 1...
 
The Encoder example available from the Arduino list works just fine, is this using your library?

If so, there is obviously something in my project screwing things up.... will need to dig around and look for anything playing with pin 1...

The example above is using the QuadEncoder Library.
 
@mjs513 is there a way to detect speed in the form of RPM?
I am using an optical encoder on a Pioneer CDJ1000 jog wheel using the following circuit:
cdj_encoder_circuit.jpg

(do I need pullup resistors on JOG1 & JOG2 lines?)
(Powering with 3.3v)

I know the encoder has 3600 pulses per rotation.
I need to know what the speed is, what the direction of rotation is and the pulse count

Would appreciate some help with it
 
(do I need pullup resistors on JOG1 & JOG2 lines?)
(Powering with 3.3v)

I know the encoder has 3600 pulses per rotation.
I need to know what the speed is, what the direction of rotation is and the pulse count

Would appreciate some help with it
Havent tested alot of encoders - really only used this one for testing:
1719961547477.png


Dont remember using pullups - level shifter since it was 5v device. But you can specify PU in the constructor:
Code:
QuadEncoder myEnc1(1, 0, 1, 0);  // Encoder on channel 1 of 4 available
                                 // Phase A (pin0), PhaseB(pin1), Pullups Req(0)

Direction of rotation and pulse count come directly out of the library but RPM no. Take a look at the examples +/- sign of the counter will give you direction. Take a look at the examples and the Encoder thread - there is a ton of info on it


You would have to count pulses and divide by time to get the RPM. Think this was asked before someplace on the forum.
 
How are the pulses/steps counted?
I was told from someone who reveres engineered the CDJ1000 that a full rotation of the jog wheel should be 3600 pulses, yet using the example code in the library I am getting roughly 13100 pulses/counts

What is the best way I can measure this? Perhaps just a simple sketch with an interrupt on a digital pin that will increment a count variable and read out after a full wheel rotation?
 
I was told from someone who reveres engineered the CDJ1000 that a full rotation of the jog wheel should be 3600 pulses, yet using the example code in the library I am getting roughly 13100 pulses/counts

What is the best way I can measure this? Perhaps just a simple sketch with an interrupt on a digital pin that will increment a count variable and read out after a full wheel rotation?
Interesting is the fact that if you divide 13100 counts/4 you get 3275 counts which is pretty close to your 3600 pulses per revolution which is kind of odd.

You could try using the standard encoder library as a comparison. Its part of teensyduino install.
 
@Rezo @mjs513 it has been a long time since I have done anything with encoders. I used them on some Rovers back a long time ago.
And during the later time of that I used a RoboClaw (BasicMicro) handle the encoders. Please pardon in advance my not too concise writing below.

Earlier when I was doing it manually (either in basic on a BasicMicro processor, or later Atmega328)
How to detect direction. As @mjs513 mentioned the library does it for you. There are several webpages out there that give some basic
understanding on how this works, like:

RPM: as mentioned, you capture how many encoder events that happen over some specific period of time, and then do the math,
that converts to RPM: (encoder count)/(counts per revolution) = revolutions ...

Measuring counts per revolution. As you mentioned, I would maybe try to get the count of encoder events that happen over some number of revolutions. For example, like 10 revolutions. And then divide by 10. For me this sometimes helped me when I was doing it manually, as if I end and not 100% accurate on start and stop location, doing multiple revolutions helped minimize the impact of this.

As for expected counts coming out of a motor, lets say that is installed on a rover. It may depend on how the encoder in connected.
I have had some where something is connected to the actual wheel being driven and an optical encoder that works off of that.
So if you have something like 100 marks on the wheel you would get the count of 100 per revolution...

However others like:
with:

This encoder mounts to the rear of the motor, which runs at the speed of the actual motor, but the motor here
has something like a 30 to 1 reduction built in. For every revolution of your wheel, you would receive about 3000 cycles.

And as for reading being about 4 times to high, As mentioned in the Sparkfun link above as well as the Lynxmotion encoder, there are about 4 counts per cycle... Which may be what you are counting here.
1721248671696.png


Sorry again if this is complete incoherent. :D
 
How are the pulses/steps counted?
I was told from someone who reveres engineered the CDJ1000 that a full rotation of the jog wheel should be 3600 pulses, yet using the example code in the library I am getting roughly 13100 pulses/counts

What is the best way I can measure this? Perhaps just a simple sketch with an interrupt on a digital pin that will increment a count variable and read out after a full wheel rotation?
The root of the word quadrature is "quad" or "four". The idea is you have two square waves (A and B) with 90-deg phase offset, and by counting the rising and falling edge of both A and B you get 4x the resolution, and by keeping track of the order the edges occur, you can determine the direction of rotation. With a 3600-PPR encoder, there will be 3600 full periods of both A and B in one rev, and the total number of rising/falling edges is 3600 x 4 = 14400. If you want to count edges using interrupts, you must interrupt on both edges of both signals to get the correct total, so even at 1 rev/sec (60 rpm) you will have 14400 interrupts/sec, and at 10 rev/sec (600 rpm), you will have 144000 interrupts/sec. It pretty quickly chews up your processing capability and makes it impossible to do anything else. That's why you should use the QuadEncoder library, which does the counting in hardware, and not the Encoder library, which does the counting in software.
 
@KurtE & @joepasquariello

Was kind of hinting at that but probably should have made it clearer.

If you go back in this thread you are going to see a EncSim sketch. In the sketch it specifically states:

Code:
uint32_t ppr = 4 * 3600;            // pulses per rev (*4 for quadrature)

for quadrature you have to multiply your ppr by 4 as Joe started.

As a quick test 4*3600 = 14400. But it your data is showing a few skipped. Runing the following sketch with ppr = 4*3600 and just 1 rpm gives me the following:

Code:
pplication TachSim0 Jul 17 2024 17:55:37
ppr = 14400   rpm =    1.0   frq =    960.0
Current position value1: 14400
Position differential value1: 1
Position HOLD revolution value1: 0

Current position value1: 14400
Position differential value1: 1
Position HOLD revolution value1: 0

Current position value1: 14400
Position differential value1: 1
Position HOLD revolution value1: 0

Current position value1: 14400
Position differential value1: 1
Position HOLD revolution value1: 0


Heres the sketch:

Code:
#include "EncSim.h"
#include <SerialCommand.h>

EncSim tachsim( 0, 1 );    // pins 0=A, 1=B, 2=M

SerialCommand sCmd;        // SerialCommand object

uint32_t ppr = 4 * 3600;            // pulses per rev (*4 for quadrature)
float    rpm = 1;            // rpm = (60*frq)/(ppr*4)
float    frq = (ppr*4)*(fabs(rpm)/60);    // keep this relationship
bool     update = true;            // update = true to trigger one print

void LED_on();
void LED_off();
void rpmCommand();
void pprCommand();
void frqCommand();
void default_fn( const char *command );

#include "QuadEncoder.h"
uint32_t mCurPosValue;
uint32_t old_position = 0;
uint32_t mCurPosValue1;
uint32_t old_position1 = 0;
QuadEncoder myEnc1(1, 2, 3, 0);  // Encoder on channel 1 of 4 available
                                 // Phase A (pin0), PhaseB(pin1), Pullups Req(0)
                                
void setup()
{
  myEnc1.setInitConfig();  //
  myEnc1.init();

  pinMode( LED_BUILTIN, OUTPUT );    // Configure the onboard LED for output
  digitalWrite( LED_BUILTIN, LOW );    // default to LED off

  // Setup callbacks for SerialCommand commands
  sCmd.addCommand( "on",  LED_on );    // Turns LED on
  sCmd.addCommand( "off", LED_off );    // Turns LED off
  sCmd.addCommand( "rpm", rpmCommand );    // rpm followed by new rpm value
  sCmd.addCommand( "ppr", pprCommand );    // ppr followed by new ppr value
  sCmd.addCommand( "frq", frqCommand );    // frq followed by new ppr value
  sCmd.setDefaultHandler( default_fn );    // Handler for command that isn't matched  (says "What?")

  Serial.begin( 115200 );
  delay( 1000 );
  Serial.printf( "Application TachSim0 %s %s\n", __DATE__, __TIME__ );

  tachsim.begin();                // begin()
  tachsim                    // CONFIGURATION
  .setPhase( 90 )                // normal 90 deg phase shift
  .setTotalBounceDuration( 0 )            // no bouncing
  .setPeriod( ppr*4 )                // marker every ppr*4 A/B pulses
  .setFrequency( frq );                // frequency = f(ppr,rpm)

  tachsim.setContinousMode( true );        // don't stop at target
  tachsim.moveRelAsync( rpm >= 0.0 ? +1 : -1 );    // +/- direction
}

void loop()
{
  sCmd.readSerial();
  if (update == true) {
    Serial.printf( "ppr = %4lu   rpm = %6.1f   frq = %8.1f\n", ppr, rpm, frq );
    update = false;
  }

  mCurPosValue = myEnc1.read();
  if(mCurPosValue != old_position){
    /* Read the position values. */
    if(mCurPosValue == ppr) {
      Serial.printf("Current position value1: %ld\r\n", mCurPosValue);
      Serial.printf("Position differential value1: %d\r\n", (int16_t)myEnc1.getHoldDifference());
      Serial.printf("Position HOLD revolution value1: %d\r\n", myEnc1.getHoldRevolution());
      Serial.println();
    }
  }

  old_position = mCurPosValue;
  if(old_position == ppr) myEnc1.write(0);


}

void LED_on() { digitalWrite( LED_BUILTIN, HIGH ); }
void LED_off() { digitalWrite( LED_BUILTIN, LOW ); }

void rpmCommand()
{
  char *arg = sCmd.next();
  if (arg == NULL) {
    Serial.println( "No arguments" );
  }
  else {
    rpm = atoi(arg);                // char* to integer
    frq = (ppr*4)*(fabs(rpm)/60);        // compute new frequency
    tachsim.moveRelAsync( rpm >= 0 ? +1 : -1);    // set direction = f(rpm)
    tachsim.setFrequency( frq );        // set frequency
  }
  update = true;
}

void pprCommand()
{
  char *arg = sCmd.next();
  if (arg == NULL) {
    Serial.println( "No arguments" );
  }
  else {
    int temp = atoi(arg);        // char* to integer
    if (temp > 0) {            // if valid ppr
      ppr = temp;            //   set ppr
      frq = (ppr*4)*(fabs(rpm)/60);    //   compute new frequency
      tachsim.setPeriod( ppr*4 );    //   marker every ppr pulses
      tachsim.setFrequency( frq );    //   set frequency
    }
  }
  update = true;
}

void frqCommand()
{
  char *arg = sCmd.next();
  if (arg == NULL) {
    Serial.println( "No arguments" );
  }
  else {
    frq = (float)atoi(arg);        // char* to integer
    rpm = (60*frq)/(ppr*4);        // back-calc rpm
    tachsim.setFrequency( frq );    // set frequency
  }
  update = true;
}

// This gets set as the default handler, and gets called when no other command matches.
void default_fn( const char *command )
{
  Serial.println( "What?" );
}
 
Back
Top