Speed of Arduino Nano and Rotary Encoders

Hi,

I'm playing around with Rotary Encoders and after some research and tinkering I've settled on this test sketch here based on the work of Marko Pinteric (MP Electronic Devices: Yet another algorithm for rotary encoder control) . I modified it to handle two Rotary Encoders (RE)

Here is the code:

#include <Arduino.h>
#include <FastLED.h>     // Just included to use beatsin8 to give Arduino busy-work in line 170. Otherwise not needed in this sketch


// RE 1
#define RE1_CLK_PIN  2
#define RE1_DT_PIN  3
#define RE1_SW_PIN  6

// RE 2
#define RE2_CLK_PIN  8
#define RE2_DT_PIN  9
#define RE2_SW_PIN  10
 
int RE1[3] = {RE1_CLK_PIN, RE1_DT_PIN, 0};    // Pin, Pin, Rotery Endoder number
int RE2[3] = {RE2_CLK_PIN, RE2_DT_PIN, 1};    // Pin, Pin, Rotery Endoder number  


// A turn counter for the rotary encoder (negative = anti-clockwise)
int rotationCounter1 = 0;
int rotationCounter2 = 0;


// Flag from interrupt routine (moved=true)
volatile bool rotaryEncoder1 = false;
volatile bool rotaryEncoder2 = false;


// Interrupt routine just sets a flag when rotation is detected
void rotary1()
{ rotaryEncoder1 = true; }

void rotary2()                  // Currently not needed. Only if there were 2 additionl interrupt pins
{ rotaryEncoder2 = true; }

// Rotary encoder has moved (interrupt tells us) but what happened?  See https://www.pinteric.com/rotary.html
int8_t checkRotaryEncoder(int RE[])
{
    // Reset the flag that brought us here (from ISR)
    if (RE[2] == 0) { rotaryEncoder1 = false; }
    if (RE[2] == 1) { rotaryEncoder2 = false; }               // Currently not needed. Only if there were 2 additionl interrupt pins 

    static uint8_t lrmem[2] = {3,3};
    static int lrsum[2] = {0,0};
    static int8_t TRANS[] = {0, -1, 1, 14, 1, 0, 14, -1, -1, 14, 0, 1, 14, 1, -1, 0};

    // Read BOTH pin states to deterimine validity of rotation (ie not just switch bounce)
    int8_t l = digitalRead(RE[0]);
    int8_t r = digitalRead(RE[1]);

    // Move previous value 2 bits to the left and add in our new values
    lrmem[RE[2]] = ((lrmem[RE[2]] & 0x03) << 2) + 2 * l + r;

    // Convert the bit pattern to a movement indicator (14 = impossible, ie switch bounce)
    lrsum[RE[2]] += TRANS[lrmem[RE[2]]];

    /* encoder not in the neutral (detent) state */
    if (lrsum[RE[2]] % 4 != 0)
    {
        return 0;
    }

    /* encoder in the neutral state - clockwise rotation*/
    if (lrsum[RE[2]] == 4)
    {
        lrsum[RE[2]] = 0;
        return 1;
    }

    /* encoder in the neutral state - anti-clockwise rotation*/
    if (lrsum[RE[2]] == -4)
    {
        lrsum[RE[2]] = 0;
        return -1;
    }

    // An impossible rotation has been detected - ignore the movement
    lrsum[RE[2]] = 0;
    return 0;
}

void setup()
{
    Serial.begin(2000000);

    // The module already has pullup resistors on board
    pinMode(RE1_CLK_PIN, INPUT);
    pinMode(RE1_DT_PIN, INPUT);
    pinMode(RE2_CLK_PIN, INPUT);
    pinMode(RE2_DT_PIN, INPUT);

    // But not for the push switch
    pinMode(RE1_SW_PIN, INPUT_PULLUP);
    pinMode(RE2_SW_PIN, INPUT_PULLUP);


//    // We need to monitor both pins, rising and falling for all states
    attachInterrupt(digitalPinToInterrupt(RE1_CLK_PIN), rotary1, CHANGE);
    attachInterrupt(digitalPinToInterrupt(RE1_DT_PIN), rotary1, CHANGE);
    
//    attachInterrupt(digitalPinToInterrupt(RE2_CLK_PIN), rotary2, CHANGE);     // Nano only has 2 intterup pins
//    attachInterrupt(digitalPinToInterrupt(RE2_DT_PIN), rotary2, CHANGE);
    
    Serial.println("Setup completed");
}

void loop()
{
    // Has rotary encoder moved?
    if (rotaryEncoder1 == 1)
    {
        // Get the movement (if valid)
        int8_t rotationValue1 = checkRotaryEncoder(RE1);


        // If valid movement, do something
        if (rotationValue1 != 0 )
        {
            rotationCounter1 += rotationValue1 * 5;
            Serial.print(rotationValue1 < 1 ? "L1 " :  "R1 ");
            Serial.println(rotationCounter1);
        }
    }
    
    if (rotaryEncoder2 == 0)
    {
        // Get the movement (if valid)
        int8_t rotationValue2 = checkRotaryEncoder(RE2);


        // If valid movement, do something
        if (rotationValue2 != 0 )
        {
            rotationCounter2 += rotationValue2 * 5;
            Serial.print(rotationValue2 < 1 ? "L2 " :  "R2 ");
            Serial.println(rotationCounter2);
        }
    }


    if (digitalRead(RE1_SW_PIN) == LOW)
    {
        rotationCounter1 = 0;
        Serial.print("X1");
        Serial.println(rotationCounter1);
 
        // Wait until button released (demo only! Blocking call!)
        while (digitalRead(RE1_SW_PIN) == LOW)
        {
            delay(100);
        }
    }

    
    if (digitalRead(RE2_SW_PIN) == LOW)
    {
        rotationCounter2 = 0;
        Serial.print("X2");
        Serial.println(rotationCounter2);
 
        // Wait until button released (demo only! Blocking call!)
        while (digitalRead(RE2_SW_PIN) == LOW)
        {
            delay(100);
        }
    }
    
    // for (int i = 0; i < 100; i++) {
      
    //       int value = beatsin8(random(2,20), 10*i, 255, 0 ,0);
    // }
    
}

The Nano only has two interrupt pins (2, 3), which I'm using for RE #1 and pins 8 and 9 for RE #2.

With the lines 168 - 171 disabled, the code runs beautifully fast. Both REs record the movement perfectly even when spun very fast. So far so good.

However, when I enable lines 168 - 171 (to give the Arduino some busy work) both REs start lagging, especially when spun fast and especially the non interrupt pin RE.

Is this to be expected with the Arduino Nano? Are there ways around this issue with the Nano, especially when spinning a RE fast?

Would a different chip like the esp32 work better because of faster CPU freqs?

I appreciate your time and insight. Thank you for any help.

The PJRC Encoder library has excellent performance. How does that compare?

I haven't giving this one a try yet. Will do so now.
Thank you for your suggestion.

Get rid of all the serial.Print statements in loop()
At end of loop, only print if encoders have been idle for x mS

Thanks for the reply.

I don't think that's it, tho...

Enabling/disabling the "for loop" changes how fast it reads the REs.

If it were the Serial.Print, it would already be slow without the "for loop".

Am I thinking this through correctly?

Thanks again.

No.
You have generated 5 posts in the last 60 minutes...
In my opinion, you need to stop scavenging code and start to study the logic of what you are doing.

An easy solution is to just turn the encoder knobs slower.

A better solution is to track encoder state using flags (or bitfield) and after all conditionals, check how long it has been since any of your 4 events.
Assuming you used 4 flags, then if the time has exceed the threshold you set for "idle" you would then inspect each flag and print appropriate message.
At this point, the loop repeats and the first 4 statements clears those flags.

(yes, you will need to set up appropriate timing using millis() against a var that gets reset in any of the 4 if() conditionals.

Do not think in your head, draw the states and necessary logic on paper.

"Thought Experiments" can only be utilized after you develop maturity in understanding programming logic and a very good vocabulary of computer operational insight. Otherwise you are in this mode of operation:
v

Thank you mrburnette.
I will give this is a try.

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