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

I do not understand everything, but im not going to blame you for that :smiley: Im just happy that you take the time to share your experience :slight_smile:

You say the goal is to keep track of the position of the rotory encoder. I understand that it is the case if you for exemple will use the rotoryencoder to make a motor shaft turn or something.

But as i understand it, when it is used in a "button box" build. The only thing that happens when the button box is connected to the computer is that every time the rotory encoder is turn one step, the "game/joystick/computer" will registrer a "button being pressd". One "Button" for clockewise turns and one "button" for conturclockwise. So the computer basiclly thinks that the rotory encoder is 2 buttons. The exact location of the rotory encoder is not needed (in this project). As i understand it.

There is a video on youtube "how to build a button box" were he builds a button box (and ill post the code here). But he says @3:33 that the rotory encoders wont register correctly if they are turnd to fast. (Same goes for the button matrix, they need to be ghosted. but i havent lookt in to that fully yet.)

Most of the buttonbox builds dont seems to include the rotory encoder libery. Maby that can fix the fast turning issue?
Or maby that is because the exact location of the rotory encoder is not relevant?
Or maby something else :smiley:

#include <Keypad.h>
#include <Joystick.h>

//DEFINITIONS
#define ENABLE_PULLUPS
#define NUMROTARIES ? //replace "?" with number of rotary encoders you are using
#define NUMBUTTONS ? //replace "?"with number of buttong you are using
#define NUMROWS ? //replace "?" with number of rows you have
#define NUMCOLS ? //replace "?" with number of columns you have

//BUTTON MATRIX
//first change number of rows and columns to match your button matrix, 
//then replace all "?" with numbers (starting from 0)
byte buttons[NUMROWS][NUMCOLS] = {
  {?,?,?,?},
  {?,?,?,?},
  {?,?,?,?},
  {?,?,?,?}
  
 
 
};

struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;
  int cwchar;
  volatile unsigned char state;
};

//ROTARY ENCODERS
//each line controls a different rotary encoder
//the first two numbers refer to the pins the encoder is connected to 
//the second two are the buttons each click of the encoder wil press 
//do NOT exceed 31 for the final button number
rotariesdef rotaries[NUMROTARIES] {
  {0,1,22,23,0}, //rotary 1
  {2,3,24,25,0}, //rotary 2
  {4,5,26,27,0}, //rotary 3
  {6,7,28,29,0} //rotary 4


};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#ifdef HALF_STEP
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,  R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
};
#else
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

const unsigned char ttable[7][4] = {
  // R_START
  {R_START,    R_CW_BEGIN,  R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT,  R_START,     R_CW_FINAL,  R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT,  R_CW_BEGIN,  R_START,     R_START},
  // R_CW_NEXT
  {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START,     R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START,     R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif

//BUTTON MATRIX PART 2
byte rowPins[NUMROWS] = {?,?,?}; //change "?" to the pins the rows of your button matrix are connected to
byte colPins[NUMCOLS] = {?,?,?,?}; //change "?" to the pins the rows of your button matrix are connected to

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS);

//JOYSTICK SETTINGS
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,
  JOYSTICK_TYPE_JOYSTICK,
  32, //number of buttons
  0, //number of hat switches
  //Set as many axis to "true" as you have potentiometers for
  false, // y axis
  false, // x axis
  false, // z axis
  false, // rx axis
  false, // ry axis
  false, // rz axis
  false, // rudder
  false, // throttle
  false, // accelerator
  false, // brake
  false); // steering wheel

const int numReadings = 20;
 
int readings[numReadings];      // the readings from the analog input
int index = 0;              // the index of the current reading
int total = 0;                  // the running total
int currentOutputLevel = 0;

//POTENTIOMETERS PART 1
//add all the axis' which are enabled above
int zAxis_ = 0;
int RxAxis_ = 0;   

               
//POTENTIOMETERS  PART 2
//Which pins are your potentiometers connected to?
int potentiometerPin1 = ?; //Change "?" to the pin your potentiometer is connected to
int potentiometerPin2 = ?;
const bool initAutoSendState = true;


void setup() {
  Joystick.begin();
  rotary_init();
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}

void loop() {

  CheckAllEncoders();
  CheckAllButtons();
  CheckAllPotentiometers();
 
}

//POTENTIOMETERS PART 3
//change the details to match teh details above for each potentiometer you are using
void CheckAllPotentiometers(){
                           
  //potentiometer 1
  currentOutputLevel = getAverageOutput(potentiometerPin1);
  zAxis_ = map(currentOutputLevel,0,1023,0,255);
  Joystick.setZAxis(zAxis_); 

  //potentiometer 2
  currentOutputLevel = getAverageOutput(potentiometerPin2);
  RxAxis_ = map(currentOutputLevel,0,1023,0,255);
  Joystick.setRxAxis(RxAxis_);


}

int getAverageOutput(int pinToRead){
  index = 0;
  total = 0; 
 
  while (index < numReadings){
    readings[index] = analogRead(pinToRead);
    total = total + readings[index];
    index = index + 1;
    //delay (1);
  }
  return total / numReadings;
}


void CheckAllButtons(void) {
      if (buttbx.getKeys())
    {
       for (int i=0; i<LIST_MAX; i++)   
        {
           if ( buttbx.key[i].stateChanged )   
            {
            switch (buttbx.key[i].kstate) { 
                    case PRESSED:
                    case HOLD:
                              Joystick.setButton(buttbx.key[i].kchar, 1);
                              break;
                    case RELEASED:
                    case IDLE:
                              Joystick.setButton(buttbx.key[i].kchar, 0);
                              break;
            }
           }   
         }
     }
}


void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif
  }
}


unsigned char rotary_process(int _i) {
  //Serial.print("Processing rotary: ");
  //Serial.println(_i);
  unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}

void CheckAllEncoders(void) {
  Serial.println("Checking rotaries");
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Serial.print("Rotary ");
      Serial.print(i);
      Serial.println(" <<< Going CCW");
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Serial.print("Rotary ");
      Serial.print(i);
      Serial.println(" >>> Going CW");
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
  Serial.println("Done checking");
}

Thank you for that information. i dont understand everything yet :smiley: but im trying to read up on it

I just need the rotory encoders to be detected as 3 buttons by the computer. turn left, turn right and push down. But i want to be able to turn the rotory encoders fast as well.

here are a couple more links discussing the various methods.
TronicsBench: Rotory encoder
Reading Encoder Switches

The first link goes into a good bit of detail on the issues and methods to resolve them. One thing mentioned is that the author believes the encoder on the KY-040 is a knockoff of the Bournes PEC11L encoder which states in it's Datasheet that Max RPM is 60. I would guess that the knockoff cannot match that :roll_eyes:

Okey! thank you!

So it seems the KY-040 might have a hardwear issue. But if debounced they work okey. I might just want to switch them out for something else.

In the button box video the uses some other rotory encoder. That has 2 pins for the switch but it dose not seem to have any VVC.

KY-040 has:
VVS
Ground
SW
CLK
DT

The rotoryencoder he uses seems to have:
CLK
Ground
DT
Switch
Switch (Ground ?)

I cant find info about that rotory encoder so i dont know the model it is.
Do we know if there is 2 typs of rotory encoders in this regard?
The one he uses is "better" because the button can be added to the "button matrix" in the buttonbox. That cant be done with the KY-040 (sens it only have 1 pin for the switch)?
If there is 2 types. Do we know if one or the other handles fast rotation better?

This may help with your encoder questions and to what type of encoder to use.

The ones I have seen you refer to are binary encoders, which can give errors.
*Edit, I started writing this before your last post regarding the KY-040 encoder, which apparently is grey code. The rest is background reading for you.

Grey Code is a better way of reading a rotational angle and is more accurate than binary encoders.
# What is Gray Code Encoder?
https://www.allaboutcircuits.com/technical-articles/gray-code-basics/

As regards to the switch question you have, this should explain it.
https://www.epitran.it/ebayDrive/datasheet/25.pdf

As stated before, turning any rotary device by hand is never fast, in terms of the hardware being able to read the data.It is the software and the way it is written that determines data gaps, but this can be overcome easily enough, by a bit of careful programming.

The button matrix depends on how you read it and at what speed, again an I2C keyboard will help, given it is dedicated to just the keyboard.

The PEC11L works out to about 20 counts per sec.

I found this one PEC-16 that has 24 pulses per rev. and 100 RPM max.
that works out to 40 counts per sec.
It's <$2 at Digikey and a drop in replacement for the one on the KY-040

I don't know what a button box is, but would offer a few comments about encoders.

Even though you have four encoders, I would expect that two at most could be turning at the same time. (Could it even be just one?) So that would somewhat limit the servicing load.

If you are turning the encoder knob fast, how will you know whether it is dropping a click now and then? You can't turn the knob rapidly and accurately at the same time, so maybe it doesn't matter much whether it tracks perfectly when it's turning fast.

There are routines that increase the "gain" the faster you turn the knob. They keep track of how fast the knob is turning, and above a certain point start counting two clicks per detent rather than one, or maybe even four clicks per detent.

If there is a lot going on, the biggest burden is probably dealing with switch bounce. A single switch transition can result in a dozen or more interrupts caused by bouncing, and all have to be serviced even if ultimately ignored. So it may be a good idea to use hardware debouncing as well as software. That should at least reduce the number of interrupts.

I developed a routine which has only one interrupt enabled at a time. When a switch transitions and generates an interrupt, within the ISR further interrupts on that line are disabled, and interrupts are enabled on the other pin, which should already be stable. So any further bouncing on the interrupting pin causes no interrupts at all. It gets complicated when you change direction, but the routine greatly reduces the servicing burden if that's important. But I don't know if it would work if there are also other things generating interrupts.

https://github.com/gbhug5a/Rotary-Encoder-Servicing-Routines/tree/master/Arduino

From following along I surmise @ghostflyer wants a pulse train for each direction to simulate key presses. All well and good but, what is the limit for the keyboard decoder in the PC? Is there a chance the encoder pulse train can send pulses too fast for the PC make sense of them?

It does not drop a click, given this is an optical reader. (LED through a masking plate to an optical sensor) The switch is a NO switch on the end of the encoder shaft, used for any utility you wish. ON/OFF or course/fine etc

Hi Sherman!

Thank you for your input!

It is true that its not likely that more than 2 rotory encoders will be moved at the same time, however in the code on the same arduino i will have a button matrix and 2 poti. Some of them might be moved tho it is unlikly.

I have seen exempel (cant find it now). Where the rotory encoder is trund to fast, it dose not only skip it sometimes goes the other way in the count. So if it is turnd to the right to get higher numbers and you turn to fast, the numbers might go down insted. it seems random, and the only way to get the number you want is to start turning the rotory encoder slow.

The function where it starts counting more clicks as one click doe to fast turning, is not something i wish in this project.

i don not understand your explanation of one interrupt fully, but i will try to read up on it

That's an artifact of the logic used in servicing the encoder. When an encoder generates an interrupt, the servcing routine determines the new state of the encoder and compares it to the previous state. There are three possibilities (if polling, the fourth possibility is no change). It can be a right turn, or a left turn, or an illegal change. An illegal change would be one where both switches change state, which is not possible in gray encoding. But if you are turning fast, or even if there is a lot of bouncing, and the code can't keep up with all the transitions, getting it wrong can be interpreted as a change of direction when no change has actually occurred.

But if your servicing routine follows all transitions of both switches, it usually works out pretty well. After all, with the type of encoder used in the KY-040, you have to accumulate a net of four transitions in the same direction before it recognizes a click. So with well-written code, there should not be a lot of incorrect direction changes. But if you turn the knob fast enough, or if there is too much bouncing, you will get errors with even the best code.

Poor coding can lead to this situation.
If you look at the grey code, a swift turn of the encoder can look as if the numbers drop rather than increase as expected.
If you read the 25.pdf, you will see the following

If both switches are closed, turning the encoder either clockwise or counterclockwise one
position will cause both switches to open

If both switches are open, turning the encoder either clockwise or counterclockwise one
position will cause both switches to close.
The illustration below is representative of how the switch is constructed.
As you can see, the angular position of the A terminal and the B terminal is such that:
Rotating the switch clockwise wll cause the switch connecting A and C to change states first.
Rotating the switch counterclockwise will cause the switch connecting B and C to change
states first.
If we were to represent the opening an closing of the switches as wave forms, it would look
something like this.

If you look at the waveform pulses shown you will see a pictorial example of this.

Just a note: If you use any more than one interrupt, then a trigger on that interrupt will necessitate you disabling that interrupt until you have serviced its call.

AS to the why? if any interrupt comes along with a lower priority, than the one already triggered, it will fail to be serviced.
An example would be where you have just 2 interrupts A and B, A having the highest priority.
The condition where the two encoders are turning at the same time, B will never be read, given that A always has priority.

Polling of the encoders would be more preferable, than using interrupts, given the complexities you may face.

Thank you for your answers, i have so much to read now that i have more questions than when i started :slight_smile:

When you say ”Polling of the encoders would be more preferable” do you mean polling them of the project, or is it a term i have not yet learnd for some programing function? :slight_smile: if its a ”code term” do you have a code exemple (maby ill understand easyer).

I have posted codes 2 times in this thread, can we say if ones is ”better” than the other regarding the part of the rotory encoders. Dose anyone have experience whit one or the other code and some specific encoders. Or if anyone experienced can make a qualifyd guess :slight_smile: ?

I now understand the rotory encoders better because of you all! But i still dont fully understand the diffrent ways to code, and the pros and cons of eatch way to code, when it comes to rotory encoders. Ill keep reading :slight_smile:

Polling is a term used when you look at each device in turn to see if there has been a state change.
The most typical use of polling is when you read a keyboard ( Where each line of a matrix is polled for a change in state), but it can be used on nearly any device attached to the MPU.

Imagine that you have 6 encoders, where your input port reads each of these encoders sequentially, one after the other. This is known as polling.

A real time system uses polling but has a priority based system for devices which must be queried faster than others. A multitasking PC based system uses this method.
On a PC the polling rate for a USB is 8mS, or 125Hz, which is fairly slow in terms of relative speed to the CPU speed.
In other words the fastest you can send data to the PC is limited by the PC read speed. There are methods via the PC to speed this up, within the settings, but it is rarely needed.

So lets take it that you have 6 encoders and the speed of the MPU is 8MHz, then you can have up to 8MIPS(instruction per second). However if we say it is only 1 MIP, then you can see you have ample time to read all 6 encoders, in less time than the PC takes to poll a single port.

Actually you have only posted 1 piece of code, given the first one only deals with a single encoder which is read via an interrupt based system.

The second one is done via polling, so is the only option given.
Whether the code is good enough or not is down to you running it and finding out for yourself.
There is no point in reading articles saying how there are skips in the code, if you do not have the knowledge in place to understand what is happening with the code and all the parts you are putting together.

I have tried to give information to you so that you can see the pitfalls for yourself and learn that it is not as straightforward as you think.

The best teaching tool is yourself, something that many students find out after graduation, they have read all the articles have all the knowledge, but no practical study.

The shortcut method is to Build it, Program it, Test it, then if you have a problem, come back and state the actual problem.

The long winded method is to read all the articles you can find, take what you think is every factor into consideration, then do the shortcut and possibly find you have forgotten something along the way to the design.