Teensy 4.1FreqMeasureMulti Library

TRGD

Member
I am currently working on a project where I have two encoders connected to my Teensy 4.1. As of right now I am only focusing on one but if I am not mistaken using this library to sample the pin of the second shouldn't be an issue as there are multiple channels. I am using the FreqMeasureMulti library to get the time the pulse is high (I believe using the countToNanoSeconds method and with the right mode in the constructor is the way to do it, please correct me if wrong), but I need to also have a direction.

I've been looking through the source code of the library (specifically the FreqMeasureMultiIMXRT.h and .cpp since those look to be the ones being used if using a Teensy 4.1) trying to understand and find the interrupt function so that I can set a variable to millis() in order to get a time which if I do this for both pulses of the encoder I can determine the direction by checking which one is first. If someone could point me where in the code I could find this I would appreciate it. Or if you have an alternative to offer in addition to answering my question I would like to hear that as well.
 
What kind of encoders are you experimenting with? I'm trying to get a picture of what your application is and what you want to achieve.

Paul
 
I don't have them on me at the moment so I don't have an exact model if that is what you are asking but they are rotary encoders. They just have four cables-power,ground, and the two data lines I assume for the A and B pulse-hopefully that answers your question in that regard.
 
From your description of 4 wires I assume that you have one those 600 or 800 ppr encoders, like this one below?
And you want to measure how many pulses per second?
I was about to advice the Encoder library as well. It's not that much effort to add pulses/sec to your code.

1719078161074.png

Paul
 
Just checking you are aware that there is an Encoder library, which gives the position (but not the rotational speed).
From your description of 4 wires I assume that you have one those 600 or 800 ppr encoders, like this one below?
And you want to measure how many pulses per second?
I was about to advice the Encoder library as well. It's not that much effort to add pulses/sec to your code.

View attachment 34746
Paul
Hello, yes it is exactly like one of those encoders. So actually to answer both of your questions. I actually have used both the Encoder and QuadEncoder Libraries. From my testing QuadEncoder worked much better to get the direction value with-I believe-the getHoldDifference() method. The reason why I am wanting to edit the FreqMeasureMulti (FMM for future reference) to add the code to get the direction is because using the encoder library and the FMM, since both pins will connect to the encoder wire then one interrupt will conflict with the other. I'm hoping to find the function in FMM for the intterupt of the channel so that I can handle this with one library.

As for the pulses per second how would I go about determining that if you don't mind. I currently don't have the setup at hand though. But the encoder will be on a rail and moved by hand and the movement will be recorded.

And when you say :
I was about to advice the Encoder library as well. It's not that much effort to add pulses/sec to your code.
Do you mean using the encoder library that can be done? Also it's more so getting the time of the pulse than the amount of pulses in a second especially since the movement that would be recorded won't be at a constant speed so some pulses are bound to be shorter than others.
 
Using the Encoder library has never failed me. Did you include INPUT_PULLUP to the inputs?
Here is a little setup and piece of code that reads the number of encoder clicks every 1 msec and prints them out.
Direction of the encoder is shown by the plus/minus sign of the clicks.

IMG_20240623_073101.jpg


C++:
#include <Encoder.h>
Encoder myEnc(5, 6);

elapsedMicros sinceReadAndPrint;

void setup() {
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
}

void loop() {
  if (sinceReadAndPrint >= 1000) { // 1 msec
    sinceReadAndPrint = 0;
    long newPosition = myEnc.readAndReset();
    Serial.println(newPosition);
  }
}

1719121358713.png


Hope this helps,
Paul
 
Hello, yes it is exactly like one of those encoders. So actually to answer both of your questions. I actually have used both the Encoder and QuadEncoder Libraries. From my testing QuadEncoder worked much better to get the direction value with-I believe-the getHoldDifference() method. The reason why I am wanting to edit the FreqMeasureMulti (FMM for future reference) to add the code to get the direction is because using the encoder library and the FMM, since both pins will connect to the encoder wire then one interrupt will conflict with the other. I'm hoping to find the function in FMM for the intterupt of the channel so that I can handle this with one library.

As for the pulses per second how would I go about determining that if you don't mind. I currently don't have the setup at hand though. But the encoder will be on a rail and moved by hand and the movement will be recorded.

And when you say :

Do you mean using the encoder library that can be done? Also it's more so getting the time of the pulse than the amount of pulses in a second especially since the movement that would be recorded won't be at a constant speed so some pulses are bound to be shorter than others.
Use QuadEncoder, which does the quadrature counting entirely in hardware, with no interrupts. If you want to know the speed, use an IntervalTimer. In the IntervalTimer handler, read the encoder count value and subtract the value from the previous IntervalTimer interrupt. The number of encoder counts divided by the time interval will give you encoder counts/second, and from that you can compute RPM.
 
Use the QuadEncoder library on T4.x for this type of high-PPR encoder. No point in having hundreds or thousands of interrupts per second when they are easily avoided.
 
Using the Encoder library has never failed me. Did you include INPUT_PULLUP to the inputs?
Here is a little setup and piece of code that reads the number of encoder clicks every 1 msec and prints them out.
Direction of the encoder is shown by the plus/minus sign of the clicks.

View attachment 34753

C++:
#include <Encoder.h>
Encoder myEnc(5, 6);

elapsedMicros sinceReadAndPrint;

void setup() {
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
}

void loop() {
  if (sinceReadAndPrint >= 1000) { // 1 msec
    sinceReadAndPrint = 0;
    long newPosition = myEnc.readAndReset();
    Serial.println(newPosition);
  }
}

View attachment 34755

Hope this helps,
Paul
Yes, I do have them with INPUT_PULLUP. Unfortunately the encoder I have is locked down with another project but I believe it is that same one you have there so I expect I would get a similar output. I've uploaded a picture of the encoder and it seems to be the same one. Here is also a link I found with the specs. It looks to be 600 ppr (encoder).

I do see what you're saying regarding the direction that would work, I could check just for if it's negative or positive to keep drag of the direction.

My question would be since I am wanting to get the time of the pulse. Before I tried doing it by adding the following code to the isr for the pin being used:

C++:
#ifdef CORE_INT2_PIN
    static ENCODER_ISR_ATTR void isr2(void) { update(interruptArgs[2]);
                unsigned long currentTime = millis();
                if (digitalRead(2)) { 
                    pulseWidthA = currentTime - lastTimeA; // Calculate pulse width when transitioning to HIGH
                    /*if (pulseWidthA > threshold) {
                        pulseWidthA = threshold;
                    }*/
                    newPulse = true; // Set the flag to indicate a new valid pulse
                }
                lastTimeA = currentTime; // Update lastTimeA to current time
                //const unsigned long threshold = 1000; // Example threshold value
    }
    #endif

Do you think this works as I intend or would there be a better way to go about it. And by any chance do you happen to know if there is a way to read the pin directly in the Teensy 4.1 like you do port manipulation in arduinos?

Also are you driving that encoder with 5V or just with the 3.3V from the Teensy. I ask because I was told they needed to be 5V and to use a level shifter. Which works fine when using it with the Encoder library but for some reason when I use the level shifter with the QuadEncoder library it doesn't work at all and gives me values that are all over the place.
 

Attachments

  • IMG_5957.jpg
    IMG_5957.jpg
    443 KB · Views: 9
Last edited:
Use QuadEncoder, which does the quadrature counting entirely in hardware, with no interrupts. If you want to know the speed, use an IntervalTimer. In the IntervalTimer handler, read the encoder count value and subtract the value from the previous IntervalTimer interrupt. The number of encoder counts divided by the time interval will give you encoder counts/second, and from that you can compute RPM.
So what you're suggesting is basically to get a slice of time and getting the speed with delta x / delta t? My question would this result in any steps being missed? And to then translate this to replicate the movement on the stepper would I just set the motor to the speed I calculated and have it run for the amount of time I got using the IntervalTimer?

Additionally, as you can see from my most recent answer I am having some issues with the QuadEncoder Library. I'm using a level shifter since I am powering the encoder with 5v. And when I give one turn of the encoder the position count goes up significantly instead of one by one. Have you experienced something like this or do you have any suggestions when it comes to this? It's especially worse when I use the interrupt version as you can see in the attached screenshot. I am turning the encoder in one direction (not changing directions) and it goes to negative randomly.
 

Attachments

  • Screenshot 2024-06-24 at 2.03.55 PM.png
    Screenshot 2024-06-24 at 2.03.55 PM.png
    479.4 KB · Views: 12
  • Screenshot 2024-06-24 at 2.04.04 PM.png
    Screenshot 2024-06-24 at 2.04.04 PM.png
    752.1 KB · Views: 10
Also are you driving that encoder with 5V or just with the 3.3V from the Teensy. I ask because I was told they needed to be 5V and to use a level shifter. Which works fine when using it with the Encoder library but for some reason when I use the level shifter with the QuadEncoder library it doesn't work at all and gives me values that are all over the place.
You don't need to use a levelshifter since the output stage of the encoder you referenced above has "NPN open collector output circuit". See this thread for more info. So just power it by 5V.
But do I correctly understand that you want to drive a stepper from the readings of the rotary encoder? That is not really difficult to realise.
A few weeks ago there was a similar question on this forum. Following up on that, I made a setup that does just that:

1719257612645.jpeg


I used a DRV8825 stepper motor driver from Pololu and brew this piece of code:
C++:
#include <Encoder.h>
#include <AccelStepper.h>

Encoder myEnc(5, 6);
AccelStepper stepper(1, 8, 9);  // STEP pin: 8, DIR pin: 9, jumper the DRV8825 module to 1/16 micro-step

void setup() {
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  stepper.setMaxSpeed(5000000);
  stepper.setAcceleration(1000000);
}

void loop() {
  long newPosition = myEnc.readAndReset();
  stepper.move(newPosition);
  stepper.runToPosition();
}

The basic idea of this code is to read the number of encoder steps and translate that to the appropriate steps. You may have to do some calculations to match a full rotation of the encoder to a full rotation of the stepper.

Paul
 
So what you're suggesting is basically to get a slice of time and getting the speed with delta x / delta t? My question would this result in any steps being missed? And to then translate this to replicate the movement on the stepper would I just set the motor to the speed I calculated and have it run for the amount of time I got using the IntervalTimer?
Until now you haven't said what you are trying to accomplish. I mentioned that you can calculate speed because that is often what one does with a relative encoder. What are you doing with the encoder, and how fast is it going to turn (revs/sec or rpm) in your application?
Additionally, as you can see from my most recent answer I am having some issues with the QuadEncoder Library. I'm using a level shifter since I am powering the encoder with 5v. And when I give one turn of the encoder the position count goes up significantly instead of one by one. Have you experienced something like this or do you have any suggestions when it comes to this? It's especially worse when I use the interrupt version as you can see in the attached screenshot. I am turning the encoder in one direction (not changing directions) and it goes to negative randomly.
If encoder counts are not going up or down continuously as long as you are turning the encoder in one direction, there is likely something wrong in your wiring/levels. @PaulS has worked with the same encoder you're using, so it's probably wise to follow his advice to get things working. Once you have the system working with Encoder, you should be able to switch to QuadEncoder if you want. If the rate of rotation of your encoder is low, then using the Encoder library is fine. If the rate of rotation is high, and the encoder is producing 10s or 100s of thousands of counts per second, I would definitely use QuadEncoder because it can track position just as well as Encoder, with no interrupts or CPU time except for the periodic read().
 
You don't need to use a levelshifter since the output stage of the encoder you referenced above has "NPN open collector output circuit". See this thread for more info. So just power it by 5V.
But do I correctly understand that you want to drive a stepper from the readings of the rotary encoder? That is not really difficult to realise.
A few weeks ago there was a similar question on this forum. Following up on that, I made a setup that does just that:

View attachment 34780

I used a DRV8825 stepper motor driver from Pololu and brew this piece of code:
C++:
#include <Encoder.h>
#include <AccelStepper.h>

Encoder myEnc(5, 6);
AccelStepper stepper(1, 8, 9);  // STEP pin: 8, DIR pin: 9, jumper the DRV8825 module to 1/16 micro-step

void setup() {
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  stepper.setMaxSpeed(5000000);
  stepper.setAcceleration(1000000);
}

void loop() {
  long newPosition = myEnc.readAndReset();
  stepper.move(newPosition);
  stepper.runToPosition();
}

The basic idea of this code is to read the number of encoder steps and translate that to the appropriate steps. You may have to do some calculations to match a full rotation of the encoder to a full rotation of the stepper.

Paul
Interesting, thank you. To make sure I understood the thread correctly. So you are powering the Teensy with 5V on the Vin pin and connecting the power of the encoder there as well since it needs the 5V and then simply connecting a 1k resistor from the encoder data pins to the 3.3v?

Actually I am working with the person in that thread, I was not aware they had asked on this forum. Essentially to clarify/explain better: The encoder will be moved by a person, they will make a path (most likely moving at different speeds throughout the movement), this path will be recorded/saved and then it can be replayed and sent to the motor. So instead of being a direct encoder -> motor like you have there there would be the middle step of saving the path so that it can afterwards be replayed. Why I am trying to get the speed or the pulsewidth of the encoder signal is because I don't need to record just the path but the speed at which the person has moved the encoder.

Also, might I ask, what is the purpose of the capacitor you are using connected to the driver? Thank you for the help.
 
The encoder will be moved by a person, they will make a path (most likely moving at different speeds throughout the movement), this path will be recorded/saved and then it can be replayed and sent to the motor. So instead of being a direct encoder -> motor like you have there there would be the middle step of saving the path so that it can afterwards be replayed. Why I am trying to get the speed or the pulsewidth of the encoder signal is because I don't need to record just the path but the speed at which the person has moved the encoder.
Based on this description, I would say you should use FreqMeasureMulti, as opposed to Encoder or QuadEncoder. If you configure FreqMeasureMulti on both of the encoder output pins (A and B), you can record the time of each rising and falling edge on both pins, and later you could translate these encoder signals into stepper commands. FreqMeasureMulti writes data values to a queue, which you can check from your sketch using the available() method, and then read with the read() method. After each read(), you can call readLevel() to determine if the recorded edge was rising or falling. With the information for all of those edges, you will be able to compute the position and speed of the encoder at any point in the data.
 
So you are powering the Teensy with 5V on the Vin pin and connecting the power of the encoder there as well since it needs the 5V and then simply connecting a 1k resistor from the encoder data pins to the 3.3v?
Yes, 5V from Teensy Vin pin to the encoder 5V VCC. And no, no external pullups to 3V3 - just use pinMode(5, INPUT_PULLUP); to enable Teensy's internal pullup.
Actually I am working with the person in that thread
Right, that thought crossed my mind.
Also, might I ask, what is the purpose of the capacitor you are using connected to the driver?
That capacitor is there as a bulk capacitor to supply short peak currents that the stepper driver may demand. On the Pololu page it is also advised.

Paul
 
So we tested with an oscilloscope and got some time recordings for the high and low pulse and determined that for some reason the FreqMeasureMulti library wasn't giving us the right values but we've tested a code that seems to be working whose recordings match what we're getting on the oscilloscope.
This is our current code:

C++:
const int pwmPin1 = 2;  // First pin to read PWM signal
const int pwmPin2 = 3;  // Second pin to read PWM signal

// Array to store pulse data (you can adjust the size as needed)
#define MAX_PULSES 800 // it will do 100 steps since one step is HIGH and LOW
volatile unsigned long pulseTimes1[MAX_PULSES];
volatile unsigned long pulseTimes2[MAX_PULSES];
volatile unsigned long pulseTime1[MAX_PULSES];
volatile unsigned long pulseTime2[MAX_PULSES];
volatile int pulseCount1 = 0;
volatile int pulseCount2 = 0;

const unsigned long debounceDelay = 50; // Debounce time in microseconds
volatile unsigned long lastChangeTime1 = 0;
volatile unsigned long lastChangeTime2 = 0;
volatile boolean lastState1 = LOW;
volatile boolean lastState2 = LOW;

// Define states for the state machine
enum State {IDLE, HIGH_STATE, LOW_STATE};
volatile State state1 = IDLE;
volatile State state2 = IDLE;

void setup() {
  Serial.begin(115200);
  pinMode(14, OUTPUT); // LED pin for first PWM
  //pinMode(5, OUTPUT); // LED pin for second PWM
  pinMode(pwmPin1, INPUT_PULLUP); // Use internal pull-up resistor
  pinMode(pwmPin2, INPUT_PULLUP); // Use internal pull-up resistor
  attachInterrupt(digitalPinToInterrupt(pwmPin1), pwmInterrupt1, CHANGE);
  attachInterrupt(digitalPinToInterrupt(pwmPin2), pwmInterrupt2, CHANGE);
}

void loop() {
  if (pulseCount1 >= MAX_PULSES || pulseCount2 >= MAX_PULSES) {
    noInterrupts(); // Disable interrupts while we process the data
    analyzePulseData1();
    analyzePulseData2();
    PulseData1();
    PulseData2();
    pulseCount1 = 0; // Reset the counter
    pulseCount2 = 0; // Reset the counter
    interrupts(); // Re-enable interrupts
  }
}

void pwmInterrupt1() {
  unsigned long currentTime = micros();
  boolean currentState = digitalRead(pwmPin1);

  if ((currentTime - lastChangeTime1) > debounceDelay) {
    switch (state1) {
      case IDLE:
        if (currentState == HIGH) {
          state1 = HIGH_STATE;
        } else {
          state1 = LOW_STATE;
        }
        lastChangeTime1 = currentTime;
        break;
      case HIGH_STATE:
        if (currentState == LOW) {
          if (pulseCount1 < MAX_PULSES) {
            pulseTimes1[pulseCount1] = currentTime;
            pulseCount1++;
          }
          state1 = LOW_STATE;
          lastChangeTime1 = currentTime;
        }
        break;
      case LOW_STATE:
        if (currentState == HIGH) {
          if (pulseCount1 < MAX_PULSES) {
            pulseTimes1[pulseCount1] = currentTime;
            pulseCount1++;
          }
          state1 = HIGH_STATE;
          lastChangeTime1 = currentTime;
        }
        break;
    }
  }
}

void pwmInterrupt2() {
  unsigned long currentTime = micros();
  boolean currentState = digitalRead(pwmPin2);

  if ((currentTime - lastChangeTime2) > debounceDelay) {
    switch (state2) {
      case IDLE:
        if (currentState == HIGH) {
          state2 = HIGH_STATE;
        } else {
          state2 = LOW_STATE;
        }
        lastChangeTime2 = currentTime;
        break;
      case HIGH_STATE:
        if (currentState == LOW) {
          if (pulseCount2 < MAX_PULSES) {
            pulseTimes2[pulseCount2] = currentTime;
            pulseCount2++;
          }
          state2 = LOW_STATE;
          lastChangeTime2 = currentTime;
        }
        break;
      case LOW_STATE:
        if (currentState == HIGH) {
          if (pulseCount2 < MAX_PULSES) {
            pulseTimes2[pulseCount2] = currentTime;
            pulseCount2++;
          }
          state2 = HIGH_STATE;
          lastChangeTime2 = currentTime;
        }
        break;
    }
  }
}

void analyzePulseData1() {
  Serial.println("PWM Signal Analysis 1:");
  Serial.println("---------------------");

  for (int i = 0; i < pulseCount1 - 1; i++) {
    unsigned long pulseWidth = pulseTimes1[i + 1] - pulseTimes1[i];
    pulseTime1[i] = pulseWidth;
    Serial.println(pulseTime1[i]);
  }
}

void analyzePulseData2() {
  Serial.println("PWM Signal Analysis 2:");
  Serial.println("---------------------");

  for (int i = 0; i < pulseCount2 - 1; i++) {
    unsigned long pulseWidth = pulseTimes2[i + 1] - pulseTimes2[i];
    pulseTime2[i] = pulseWidth;
    Serial.println(pulseTime2[i]);
  }
}

void PulseData1() {
  for (int i = 0; i < pulseCount1 - 1; i += 2) {
    digitalWrite(14, HIGH);  // Turn the LED on (HIGH is the voltage level)
    delayMicroseconds(pulseTime1[i]); // Wait for a specified time
    digitalWrite(14, LOW); // Turn the LED off by making the voltage LOW
    delayMicroseconds(pulseTime1[i + 1]);
  }
}

void PulseData2() {
  for (int i = 0; i < pulseCount2 - 1; i += 2) {
    //digitalWrite(5, HIGH);  // Turn the LED on (HIGH is the voltage level)
    delayMicroseconds(pulseTime2[i]); // Wait for a specified time
    digitalWrite(5, LOW); // Turn the LED off by making the voltage LOW
    //delayMicroseconds(pulseTime2[i + 1]);
  }
}

It's working fine but we seem to be missing some steps. Do you have any suggestions to improve the debouncing or the missing steps? Or maybe as to why FreqMeasureMulti might be working incorrectly. Below is a screenshot and the test code being used for FreqMeasureMulti.


C++:
#include "FreqMeasureMulti.h"
const int freqPin = 9; // Pin connected to the signal whose frequency is measured
FreqMeasureMulti freq;
//extern volatile bool checkF;

void setup() {
  Serial.begin(115200);
  freq.begin(freqPin, FREQMEASUREMULTI_Alternate);
}

void loop() {
  if (freq.available()) {
    unsigned long duration = freq.read();
    //float frequency = FreqMeasureMulti::countToFrequency(duration);
    float test = FreqMeasureMulti::countToNanoseconds(duration);
    
    /*Serial.print("Frequency: ");
    Serial.print(frequency);
    Serial.print(" Hz");*/
    Serial.print("Time: ");
    Serial.print(test/1000);
    Serial.println(" us");
    
  }
}
 

Attachments

  • WhatsApp Image 2024-06-27 at 12.40.58.jpeg
    WhatsApp Image 2024-06-27 at 12.40.58.jpeg
    255.1 KB · Views: 8
  • WhatsApp Image 2024-06-27 at 12.41.31.jpeg
    WhatsApp Image 2024-06-27 at 12.41.31.jpeg
    267.4 KB · Views: 8
  • Screenshot 2024-06-28 at 2.05.45 PM.png
    Screenshot 2024-06-28 at 2.05.45 PM.png
    217.7 KB · Views: 9
The FlexPWM timer used by FreqMeasureMulti requires very fast edges. Your signal looks like it has rather slow rise/fall times, and that will not work with FreqMeasureMulti. Can you add a schmitt trigger on your input?
 
The FlexPWM timer used by FreqMeasureMulti requires very fast edges. Your signal looks like it has rather slow rise/fall times, and that will not work with FreqMeasureMulti. Can you add a schmitt trigger on your input?
Understood, I believe the encoder I was using for that test was a bit lower in resolution to what I am using now. Would a higher resolution encoder work better to use the library? At the moment I don't believe I have one around.

Quick questions
Yes, 5V from Teensy Vin pin to the encoder 5V VCC. And no, no external pullups to 3V3 - just use pinMode(5, INPUT_PULLUP); to enable Teensy's internal pullup.
Also further question on this, It appears we are using a different encoder now which according to the model information I found seems to have a line driver output instead of an npn as you mentioned in that thread. I assume this would prevent me from using it with just INPUT_PULLUP or can I still do this?
 

Attachments

  • IMG_6005.jpg
    IMG_6005.jpg
    559.3 KB · Views: 6
Also further question on this, It appears we are using a different encoder now which according to the model information I found seems to have a line driver output instead of an npn as you mentioned in that thread. I assume this would prevent me from using it with just INPUT_PULLUP or can I still do this?
From what I read on that page, the 200PPR GHS38-06G200BML5 apparently has an "RS422 (line driver)" interface. Since RS422 specifies differential signalling, I assume that A+/A- and B+/B- are the connections that we should use?
Still I would like to see a real datasheet to be certain what voltage levels, timing and pin connections are to be expected.

Paul

PS: found this on AliBaba:
1719847705363.png

I think the whole reference to RS422 is not relevant? I guess it just has a differential output driver.
 
Last edited:
Follow-up: based on that last picture of the "L" differential output stage, it is highly possible that the output signal level will be close to VCC, in your case 5V. This 5V level will kill your Teensy so don't try that. Best is to check the output signal levels with an oscilloscope first to make sure.
As a side note: the "E" open collector output version apparently has a built-in pullup resistor to VCC. Again, if the VCC is 5V, that output pin will lifted to 5V which is lethal for your Teensy.
All said, it's probably better to look for a different rotary encoder that has clear documentation.

Paul
 
Back
Top