Encoder Tester

luni

Well-known member
Recently a lot of new encoder libraries and other encoder related information and questions were posted in the forum.

To name a few:
Encoders (once more)
Encoder Library not as efficient as XOR method
Rotary Encoder Debugging on SAMD11
Hardware Quadrature Code for Teensy 3.x
Encoder
How to use encoders as pot
... and a lot more

Due to the obviously growing interest in using encoders and writing encoder libraries I thought that publishing a small encoder simulator tool might be useful for systematic testing of the libraries. Here the link to the GitHub repo: https://github.com/luni64/EncSim. It can generate quadrature signals at count rates from 1Hz up to 1.4MHz. The phase of the generated signals can be adjusted from 90° (standard) down to 10°. Optionally a random bouncing signal with adjustable parameters can be added. The simulator can be controlled from any serial terminal (TyCommander, PuTTy, arduino serial monitor...).

In case somebody wants to try it without installing the library first, I attached the Hex files for T3.6 and T3.2 in the Repo and to this post. Just upload the FW, connect to a serial terminal and type help. Output signals are generated on pin 0 and pin 1.

interface.PNG


and here an example of a generated signal with added simulated contact bouncing:

50Hz_bounce5000_20_500.jpg 50Hz_bounce5000_20_500_zoom.jpg

Detailed documentation can be found in the GitHub readme.
 

Attachments

  • EncSimSerial.zip
    100.2 KB · Views: 208
Last edited:
Adding 3rd output [Z / Trigger] possible?

Great work with this tester luni!

I'm just received a set of T4.1 boards and i'm new with the Teensy's.

One of the first project with T4.1 is to make a simulator for rotary encoders witch supports at least 32768 (X4) pulses per rev. and with trigger (Z) also implemented.
I'm found out that IMXRT1060RM "quadrature decoder/encoder" has a native support for home, trigger, so probably trigger can be added on this lib too? If so.. are you interested? thoughts? Ideas?

Secondly is there idea about a performance yet?
eg: with encoder type mentioned and with 25RPS we should get a 819200 transitions/s right?
I'm not sure, but believe that teensy had now enough of juice to output this pulse rate and even higher without any trouble!?
I had done past a simpler quadrature A,B,Z simulator with ESP32 where i toggling outputs with isr-timer and found out that output frequency with 32768ppr starts to come more unstable with higher frequency's.
and was saturated somewhere around my target.

ps, here's the encoders i try to simulate and they had a versions with max of 131072 ppr:
https://www.baumer.com/dk/en/product...ncoder/c/33780

-Tepa
 
Glad you like it.

One of the first project with T4.1 is to make a simulator for rotary encoders witch supports at least 32768 (X4) pulses per rev. and with trigger (Z) also implemented.
By 'trigger' you mean a reference/zero pulse issued at a settable number of counts, right? I.e., the same as you usually get from rotary encoders once per revolution. That shouldn't be a big deal. I can have a look at that.

Secondly is there idea about a performance yet?
I didn't spend much time with optimization for the current version. As it is, it maxes out at a pulse rate of about 900kHz for a T4x.

I'm not sure, but believe that teensy had now enough of juice to output this pulse rate and even higher without any trouble!?
Unfortunately the interrupt system of the IMXRT is a bit on the slow side. So, don't expect much higher performance than a T3.6 in applications using timer interrupts. My gut feeling is that something like 2Mhz (counts per second) should be possible with some optimization. For higher frequencies the library concept would need to be changed completely.

What is your targeted max pulse rate?
 
Thanks for quick reply


By 'trigger' you mean a reference/zero pulse issued at a settable number of counts, right? I.e., the same as you usually get from rotary encoders once per revolution. That shouldn't be a big deal. I can have a look at that.
Yes correct!. i would be very pleasant for that!!

I didn't spend much time with optimization for the current version. As it is, it maxes out at a pulse rate of about 900kHz for a T4x.

Unfortunately the interrupt system of the IMXRT is a bit on the slow side. So, don't expect much higher performance than a T3.6 in applications using timer interrupts. My gut feeling is that something like 2Mhz (counts per second) should be possible with some optimization. For higher frequencies the library concept would need to be changed completely.

What is your targeted max pulse rate?
Currently i would be happy if can get clean out of for 300kHz so 900kHz sounds good to me!

Ones implemented i can try to do stress test and share results here!

br,
Tepa
 
Here's a graph for showing alignment... Phase shift 90° and positive direction: zero (trigger) should should go high with B-rising and come back low with A-falling.
quadrature.png
 
The start of the Z pulse obviously depends on the period ('ppr') you select. E.g., if you set the period to an odd number, the starting edge for the Z pulse will change each time, if you use a period which can be divided by 4, the pulse will always start at the falling edge of B. Real life encoders necessarily have a period which can be divided by 4. So, just make sure you set the period correctly then it will work as expected.

Here a first test result. I set the pulse rate to 800'000 pps. To display the zero pulses without zooming I used a small zero pulse period of 12.

Anmerkung 2020-08-19 113624.jpg

Code:
#include "EncSim.h"

EncSim encSim(0, 1, 2);             // A=0, B=1, Z=2

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);

    encSim.begin();
    encSim                          // setup parameters
        .setFrequency(800'000)      // 800 kpps
        .setPeriod(12)              // Zero pulse every 12 counts
        .setPhase(90)               // normal 90° phase
        .setTotalBounceDuration(0); // no bouncing

    encSim.moveRelAsync(5000);      // run for some time
}

void loop()
{
    digitalToggleFast(LED_BUILTIN); // heart beat
    delay(250);
}
 
I updated the GitHub repository with the index pulse generation feature: (v2.1.0.0) https://github.com/luni64/EncSim/releases Let me know if it works for you.

Quite close!!

Pulse sequence is correct Z works as it should. tested with .setPeriod(32768) witch is my current most high count encoder.

Frequency is a bit tricky for me. I see you had used 800k so probably i had something wrong next i try explain what happened while playing.
from 100k to 500k frequency was rising linearly.. using .setFrequency(500'000) i got [A&B pulse freq 117,1kHz] [Z pulse freq 14,3Hz] witch corresponds in shaft speed 859rpm.
.setFrequency(500'001) and any bigger number.. and i have output of [A&B freq 185,1kHz] [Z freq 22,6Hz] = 1356rpm.

I hope to get clean frequency with: [A&B freq 273kHz] [Z freq 33,33Hz] = 2000rpm.
Also i noted that by keep something inside loop effecting output frequency and especially variation of frequency ( this was expected )
Code:
#include "Arduino.h"
#include "EncSim.h"

EncSim encSim(0, 1, 2);             // A=0, B=1, Z=2

void setup()
{
    pinMode(LED_BUILTIN,OUTPUT);   
    
    encSim.begin();
    encSim                          // setup parameters
        .setFrequency(500'000)      // 500000 Counts per second / equals [A&B freq == 117,2kHz] [Z freq 14,3Hz = 859rpm]
        //.setFrequency(500'001)    // 500001 Counts per second / equals [A&B freq == 185,1kHz] [Z freq 22,6Hz = 1356rpm] 
        .setPeriod(32768)           // Index pulse every 32768 counts [X4 mode] 
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    encSim.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
}

void loop()
{
    //digitalToggleFast(LED_BUILTIN); // heart beat   
    //delay(1000);
    
}

Arduino Settings:
TeensySettings.png
 
EncSim v2.2.0.0

I could reproduce you issues and fixed them

Explanation:
To be able to generate non 90° quadrature signals I used chained one shot timers. However, triggering those timers takes some time so that the resulting frequency of the quadrature signal is less than required. The effect gets larger for smaller signal periods since then the additional delay gets dominant.

Fix:
  • I changed the lib to use a periodic timer instead. To generate the non 90° signals I set the next period of the timer in the ISR which doesn't introduce the retrigger errors.
  • I also switched the main timer from software to hardware (PIT) to get a more stable output frequency. The bounce timers are still software but they are not critical at all.

Here an updated test sketch using your parameters.

Code:
#include "EncSim.h"

EncSim encSim(0, 1, 2);           // A=0, B=1, Z=2

constexpr float rpm  = 150;       // revolutions per minute
constexpr float ppr  = 32768;     // encoder pulses per revolution
constexpr float rps  = rpm / 60;  // revolutions per second
constexpr float freq = rps * ppr; // count frequency

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    while (!Serial && millis() < 1500) {}

    Serial.printf("rpm:\t%8.2f 1/min\n", rpm);
    Serial.printf("pps:\t%8.2f kHz\n", freq / 1000.0f);
    Serial.printf("phase freq:\t%8.2f kHz\n", freq / 4000.0f);
    Serial.printf("z-freq :\t%8.2f Hz\n", rps);

    encSim
        .begin()
        .setFrequency(freq)         // calculated above
        .setPeriod(ppr)             // defined above
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    encSim.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
}

void loop()
{
    digitalToggleFast(LED_BUILTIN); // heart beat
    delay(250);
}

Here the output of the sketch
Code:
rpm:          800.00 1/min
pps:          436.91 kHz
phase freq:   109.23 kHz
z-freq :      13.33 Hz

And here the measurement result. Expected frequencies fit nicely to the measurement now. Works up to a count frequency of >1MHz on a T4. -> Your 2000rpm should work as well.

Anmerkung 2020-08-23 183057.jpg

Anmerkung 2020-08-23 193319.jpg

Updated library is on GitHub (v2.2.1.0) https://github.com/luni64/EncSim
 
Last edited:
Tested and confirmed now it works nice and clean even with toggling led at loop.. Nice work thanks a lot!

Indeed it goes up to round 2500rpm now and i think i can use it "as is" for a long time from now.

For future i'm interested how or is it possible to improve still further some show?
If i had take it correctly now hw pit timer fires interrupts to toggling gpio right?
how about if using pit against DMA for switching pin or some other technique?

Here's my measurement with top frequency setting 2500rpm i've got avg @2441 rpm and peak to peak was only 12rpm Not Bad :)
enc.jpg

Thanks Tepa,
 
@luni, great library. Thanks very much for making this available. I am using EncSim to simulate output of an optical encoder on a generator, and I need the signal to continue forever, as opposed to stopping at a specified target, even INT32_MAX. To do this, I added public data member "bool continuous = false" and modified the test for the "stop" condition in pitISR() as shown below. This does what I want, and I thought I'd let you know in case you think this would be a useful enhancement. I know the new data member should probably be Protected rather than Public, but I'm not very knowledgeable on C++ and didn't understand the syntax of your get/set functions.

void pitISR()
{
current += direction;
if (current == target && continuous == false)
{
stop();
} else
 
Thanks for the enhancement idea. I implemented it and uploaded the code to GitHub. Can you give it a try?
 
luni, thanks very much for the update. I'm running it now and it's working fine. The data below shows the rollover. Note the delta stays the same during rollover.

Just one note, where you have "continous", it should be "continuous" (the first 'u' is missing).


Current = 2147392010 Delta = 32442 Revs = 0
Current = 2147424452 Delta = 32442 Revs = 0
Current = 2147456893 Delta = 32441 Revs = 0
Current = -2147477961 Delta = 32442 Revs = 0
Current = -2147445518 Delta = 32443 Revs = 0
Current = -2147413076 Delta = 32442 Revs = 0
 
Using EncSim to simulate 8 motors

Luni, Thank you for developing this code.

I am trying to simulate 8 encoders on a Teensy 4.0. I used the 'TwoSimulators.ino' file as a starting point and then increased the number of EncSim objects to 4 and then to 8. It seems that 4 is the limit. Objects beyond that are ignored, and if I comment out the first four EncSim instances, then the remaining four work. Is this a limit of the Teensy or one of the libraries?

Below is the code I am using:

edit:
I only need the angular velocity of the motors, so I left out the homing position. Also, I'm measuring the output on an oscilloscope, so I commented out the serial print commands.

Code:
#include "EncSim.h"

EncSim e1(0, 1);    // A=0, B=1
EncSim e2(3, 4);    // A=2, B=3
EncSim e3(6, 7);    // A=6, B=7
EncSim e4(9, 10);   // A=9, B=10

// looks like you can only simulate 4 signals per Teensy
EncSim e5(12, 13);  // A=12, B=13
EncSim e6(15, 16);  // A=15, B=16
EncSim e7(18, 19);  // A=18, B=19
EncSim e8(21, 22);  // A=21, B=22

void setup()
{
    // pinMode(LED_BUILTIN, OUTPUT);

    e1
        .begin()
        .setFrequency(2000)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    e2
        .begin()
        .setFrequency(2001)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    e3
        .begin()
        .setFrequency(2002)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    e4
        .begin()
        .setFrequency(2003)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    e5
        .begin()
        .setFrequency(2004)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    e6
        .begin()
        .setFrequency(2005)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    e7
        .begin()
        .setFrequency(2006)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing

    e8
        .begin()
        .setFrequency(2007)         // 1000 pps
        .setPhase(90)               // 90° phase signal
        .setTotalBounceDuration(0); // no bouncing


    e1.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
    e2.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
    e3.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
    e4.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647    
    e5.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
    e6.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
    e7.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
    e8.moveRelAsync(INT32_MAX); // run for some time INT32_MAX = 2147483647
}

void loop()
{
    // Serial.printf("e1: %d, e2: %d\n", e1.current, e2.current);
    // delay(100);
}
 
Last edited:
It seems that 4 is the limit.
EncSim uses the IntervalTimers (PIT) for generating the encoder signals. There are only 4 of those timers available. However, the timers are released after use so you can declare as many simulators as you want, but, you can only have 3 runnig at the same time.
 
@luni, I'm using TD 1.59b6 and trying to build an old sketch that uses your EncSim library. I'm getting errors in IntervalTimerEx that I think are associated with your changes to IntervalTimer. It looks like IntervalTimerEx.h and cpp need conditional code for TD >= 159 in addition to the CPP11 code. I tried changing it, copying the new in-place callback_t from IntervalTimer, and I got it to build, but I must have done something wrong because I'm not getting any output from the encoder simulator. When you have a chance, could you please take a look at EncSim and see if you can update it for compatibility with TD 1.59? Thanks very much.
 
@luni, I figured out what I was doing wrong and fixed it, and I opened an issue on your EncSim repository that explains how I modified your IntervalTimerEx for compatibility with TD 1.59
 
Back
Top