Being able to turn rotory encoder fast (KY-040)

Hi!

Im farely new to Arduino, ive been reading up, but cant make out whats actually works and what dose not. Right now I am looking for a code for rotory encoders to include in my bottun box build.

Here is the catch! I want to be able to turn the rotory encoder fast whitout it skipping or bouncing.

Here is some code that according to a youtube video that seem good to me. However its to complicated for me to understand fully as of now.

My questions:

  1. Am I going to be able to use this code, and somehow expand it so it covers 3 diffrent rotory encoders?
  2. Can this code be included in a button box build with a ghosted button matrix, and 2 potensimeters?
  3. If i have to much code will the buttonbox "lagg"? or is this code "better"

Marco Pinteric is the brain behind this, and it is being shown in a youtube video by Ralph S Bacon. (i dont know if i can post videos)

#include <Arduino.h>

// Rotary encoder pins
#define PIN_A 32
#define PIN_B 4
#define PUSH_BTN 16

// A turn counter for the rotary encoder (negative = anti-clockwise)
int rotationCounter = 200;

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

// Interrupt routine just sets a flag when rotation is detected
void IRAM_ATTR rotary()
{
    rotaryEncoder = true;
}

// Rotary encoder has moved (interrupt tells us) but what happened?
// See https://www.pinteric.com/rotary.html
int8_t checkRotaryEncoder()
{
    // Reset the flag that brought us here (from ISR)
    rotaryEncoder = false;

    static uint8_t lrmem = 3;
    static int lrsum = 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(PIN_A);
    int8_t r = digitalRead(PIN_B);

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

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

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

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

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

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

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

    // The module already has pullup resistors on board
    pinMode(PIN_A, INPUT);
    pinMode(PIN_B, INPUT);

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

    // We need to monitor both pins, rising and falling for all states
    attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE);
    attachInterrupt(digitalPinToInterrupt(PIN_B), rotary, CHANGE);
    Serial.println("Setup completed");
}

void loop()
{
    // Has rotary encoder moved?
    if (rotaryEncoder)
    {
        // Get the movement (if valid)
        int8_t rotationValue = checkRotaryEncoder();

        // If valid movement, do something
        if (rotationValue != 0)
        {
            rotationCounter += rotationValue * 5;
            Serial.print(rotationValue < 1 ? "L" :  "R");
            Serial.println(rotationCounter);
        }
    }

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

There are Arduinos and then there are fast Arduinos and some even faster. Which are you using?

What is turning the encoder?

What does fast mean? Give us a number.

I use the Encoder library available through the IDE library manager.

i have an arduino uno, will it not be possible with thatone?

But im doing all the reserch to see what i need to get this build to work good. So if you have a suggestion for a good arduino im very gratefull :slight_smile:

Not with that code.
You have done this

#define PIN_A 32
#define PIN_B 4

Whereas the interrupts are on different pins on the UNO which are digital pins 2 & 3

i am turning it with my fingers, like if you have a knob on ur microwave and you want to go from 0 to 5 min. You almost "hit" it on the side to make it spin fast to go to your number as fast as possible.

(its going to be used in a flight simulator, and if your on a 270 heading (west) and have to do a right turn to 180 heading (south). The first 180-250 degress will be turnd fast on the rotoryencoder

Thank you for letting me know. I understand that it depends on what board you have. I understand that the code needs to be changed

My main question is if this is the best possible code idea for my purpuse regarding multible rotory encoders in a button box build (that can be turnd fast).

Sens i lack the experience, and i want to learn from your experience on what works well in the real world :slight_smile:

Well you need to consider the number of encoders you want vs. the number of interrupt pins on your Arduino. Two pins per encoder and how many interrupt pins on your Arduino? The documentation tells you what pins can be used.

Nothing that you can do with your fingers is anywhere near "fast" to the slowest Arduino. Rotary encoders, with much better resolution than yours, are used on electric motors all the time. 10,000 RPM is fast.

The Encoder library provides switch debounce. The Encoder library can work with 2 interrupts per encoder, 1 interrupt per encoder or no interrupts.

How encoders and the Encoder library work.

The Arduino Nano Every (costs 13 euros) has 22 interrupt pins that the Encoder library can use.

It depends on the rest of your code, how fast a encoder change is detected.

A button box is always an emulation of a computer keyboard, as far as I know.
The Arduino Uno or Arduino Nano Every can not emulate a keyboard.

Can you tell us about your project ? Please give us a broader view. How many buttons, joysticks, dial knobs, encoders, leds, and so on, do you have in mind ?

What i understand from my reserch. "all" normal code have the issues: if the rotoryencoder is being turnd to fast it bounces or skips (even if debounced), ive read about it and watcht alot of builds on youtube.

What i understand there is a Joystick libery and a keypad libery that might be good with an arduino. Most builds uses these liberys and arduino Leonardo or Pro Micro.

My idea is as shown on the picture:
-I want 12 buttons (momentary bottons) more then one can be pressed at the same time, and if one is held down it is detected. (for ex steering a camera in a videogame, go up and left at the same time).
-I want 2 potensiometers (for lightnings in cockpit etc)
-I want 4 rotoryencoders (i can do with 3 if there is no space, for heading, altitude, radio etc.)
-I want a 3 way switch if possible (if it is in its most left possition all the buttons works, and if it is moved to its middle possition all the buttons can be mapped to another function in game, i have this on my joystick (saitek X52 pro) and it saves alot of buttons:D so if this works "i have 36 buttons, 6 potensiometers, 12 rotory encoders". But i cant find any information on this, is it possible to do?

Why I am asking about help is because i want a buttonbox that works as well as a bought joystick. If you say it will be bad in some way because of for ex to many buttons. i will then remove some buttons. But the drawing shows my refined idea.Thank you for your help!

BUT!!!! Are they all using your type rotary encoder? Are they all using the same code on the same Arduino? If not, you are comparing apples to oranges.

You need a Arduino Leonardo, (Pro) Micro or a MKR Zero to emulate a keyboard and/or joystick.

12 buttons in a 3x4 matrix = 7 pins.
2 potentiometers = 2 analog inputs.
4 rotary encoder = 8 high speed inputs.
I assume that there are not push buttons on the rotary encoders ?
A 3-way switch = 3 pins ?

You need to add extra hardware to make more pins. That is not a big problem.
The 3-way switch can be added to the matrix if you make it 4x4.

What do you think of this: https://www.adafruit.com/product/4991
It uses the I2C bus, which is 2 pins. You don't need interrupts or a fast sketch with dedicated chips.

What i understand they all use the KY-040 on Leonardo or Pro Micro. Ive seen hardware debounce and diffrent code debounce. But they still cant be turnd fast whitout skiping or bouncing.

The only code ive found that seems to be working on fast rotation is the code in post #1.
But i dont have much experience of codeing multible rotory encoders. If you have experience and wanne share an exemple that you know work, im very greatfull :slight_smile: thank you

I'm not convinced that the "Encoder" library is not as good as the code in your first post.
The "Encoder" library can use two interrupts and the code that you showed seems to be for a ESP32.
You need to have all the code and all the hardware together to say something about how fast it is.

When a encoder skips, then something is wrong. Even when a human gives a maximum swing, then the pulses are still not very fast.

Ralph S Bacon about a rotary encoder:

The Github page about it: https://github.com/RalphBacon/226-Better-Rotary-Encoder---no-switch-bounce

Thank you for all your advice!

Yes there are buttons on the rotory encoders (my misstake not mentioning that)

So that leavs us at:

12 buttons + 3 way switch, in a 4x4 matrix = 8 pins.
2 potentiometers = 2 analog inputs.
4 rotary encoder = 8 high speed inputs.
4 buttons on rotoryencoders = 4 pins (any pins will is good?)

I would LOVE that solution whit the I2C for the rotory encoders. But if you look at the video in the link you posted to adafruit there is a video that "JP" has done, @9:55 and forward when he starts turning the encoders a little faster and has his serial open. You can see that it skips numbers quite often. Now is it cus of low baud rate or something else i dont know? (Ralph uses a higher baudrate in his video)

If you look at Ralphs video you posted @20:51 there is a short moment when he turns the encoder fast, if you go frame by fram you can se all the numbers are there in order.(no skiping, no bouncing)

This is how i came to my conclusion that only that code work. (But im hear because im not sure if i understand it correctly)

As i understod Ralphs video, he did a rotery encoder video on an ardoino. And then he got the comment from Marco Pinteric that there was an "easyer/better" way to do it on the arduino, and that his code is that solution. There is a comment under the video tha Marco made were he explane that he just made "Raspberry Pi code" to "arduino code". Am i lost in translation here :smiley: so its not for Ardoino as i think :smiley:

Can you give more links please :pleading_face:
The "JP" video at 9:55 is this link: https://youtu.be/t4V5e9aX9cA?t=595

What do mean by skipping ?
That is serial output goes from: 1,2,3, 8,9,10 ?
That is no problem at all. That is is just the serial output and the encoder does not skip.

In Ralphs video, 20:51 is here: https://youtu.be/sQNPAsZKnDw?t=1251

I'm sorry, but I'm still not convinced. I understand that it is a slightly different way, but I disagree that it is better. You might see a continuous counting number because interrupts could be missed. From the source code and the videos, it is not better.

Someone could add a motor to a rotary encoder and measure at what rpm speed it fails. Then we know. But it depends mostly on the rest of the code. A clean test would not be the same as a full sketch for a button box.

Im not sure how it works, but if the serial misses some numbers, wont the game miss them aswell (when i plugg in my buttonbox?)

Ralph made another video: #19 How to use a Rotary Encoder with an Interrupt routine on your Arduino - YouTube

@12:25 he explanes that ”acording to the arduino forum, if the rotory encoder is being turned to fast, it will skip some turns in order to get the serial right. (This makes the fast turning ”useless”). I dont know the exact post he read, but this is also what i have read when i workt the google machine.

Im not trying to convince anyone of anything here :slight_smile: iam just looking for the best/good working solution :slight_smile: im very glad for all the answers!

If you or someone else have experience in what work in the real world and what dose not. Im very happy :slight_smile:

No, not if the sketch uses the position correctly.

Yes, for example if the rotary encoder pulses are read in the loop(), without the use of interrupts.

The "Encoder" library works in the real world. It is optimized to keep track of the rotary encoder as good as possible. The hardware chips of those Adafruit modules don't need software interrupts, they are even better.

If you don't understand it, then you can blame me for not explaining it well :face_with_raised_eyebrow:

:point_right: The goal is to keep track of the position of the encoder, so the game or other app knows exactly the position. That means that pulses from the encoder should not be missed.
Suppose there is a lot of code in the loop() and when the encoder position is read again, it has jumped from 0 to 20. That is good, it is supposed to work that way. The current position of the knob is 20 pulses further and the interrupts kept track of the position :+1:

Encoder library: In the interrupt routine, a variable is incremented:

arg->position++;

If the sketch is doing other things in the loop(), and once in a while checks the encoder, then it will still read the right position, because the interrupt keeps track of the encoder.

Ralph S Bacon code: In the interrupt routine a flag is set:

void IRAM_ATTR rotary()
{
    rotaryEncoder = true;
}

That flag is reset by code in the loop(). If the loop() has other things to do, then a number of pulses could have been missed. It is not possible to know what has been missed. Of course, the output to the Serial Monitor will show continuous numbers, because all the pulses in between were missed.

Warning: Some libraries turn off the interrupts for a while :scream: Then the interrupt from the rotary encoder will not be handled. Those libraries are: Neopixel, FastLED, DHT, OneWire, SoftwareSerial, and more.

Conclusion: What you see in the Serial Monitor is not important. The position of the knob of the rotary encoder is important. How that position is passed on to the computer is a matter of good code.

Interrupts are turned off for the duration of servicing the interrupt, otherwise you may get another interrupt coming along, whether intentional or not.
However the time it is turned off is usually very short and dependent on the speed of the processor.

Interrupts have priorities, so that may be an issue where a number of interrupts are used.
If it is just a sequential read of each encoder in turn, this is not an issue, however if the encoders are all active at the same time and can be turned at the same time, then timing becomes a problem.

The best solution with the interrupt service routine is to increment a counter rather than just checking to set a state. This way you know how many counts have happened, rather than assume that you are servicing the interrupt data in a timely manner.

The I2C option is probably the best for this task, given the separate board will store the encoders value and with the highest speed I2C (400K) it is ample for the task.

As far as keyboards are concerned, this is not really an issue software wise, but it does depend on the hardware used. Noisy circuits are just one example, where buttons are in many different places. This is particularly true when it comes to reading multiple buttons pressed at the same time.