De-bouncing a Rotary Encoder Joy-IT KY-040

Last week I purchased a Joy-IT KY-040 rotary encoder.
There is information from the supplier here.
This includes pictures and information on the unit, and some code.
My suspicion after reading through the code, is that the person who wrote it had scant knowledge of: either the dynamics of the unit; nor a method of obtaining meaningful results from it.
I spent some time yesterday in establishing the following:

  • This encoder generates TWO events per detent movement.
  • The first is when departing the current detent position, The second when arriving at the next detent position.
  • This is true for either clockwise or counter-clockwise rotation.
  • It is essential that BOTH these events are debounced.
  • Sequences: Clockwise - 0110; Counter-clockwise - 0011.

That the product was made in China, there is little doubt. The manufacturer would have supplied information with the unit, but this has not been passed on to suppliers, or if it was, they probably made no sense of it anyway. (See my previous post on ‘Etching your own Printed Circuit Boards.’)
However, the unit is cheap and it works given a little care. Below is code I developed to test and use the item. I hope this of some assistance to anyone who may be struggling to use the unit. I tested this code on a Arduino MEGA2560, so the pin numbers should be changed for a different board.


// KY-040 Rotary Encoder use.
// Provides rotational position and flag when shaft depressed.
// NOTE: This encoder generates TWO events per detent movement.
// The first is when departing the current detent position, 
// the second when arriving at the next detent position.
// This is true for either clockwise or counter-clockwise rotation.
// It is essential that BOTH these events are debounced.
// Sequences: Clockwise - 0110; Counter-clockwise - 0011
// Joe Brown 16th April 2021
// 
// Thanks to ezButton folks for neat debouncing routines.
// Be aware if copying below code that the 'less-than' and 'greater-than' symbols
// in the following include statement have been escaped.
#include <ezButton.h>

// uncomment following for internal info reporting
// #define _DEBUG_ENCODER_

class KY040b
{
    // pins
    int CLK;  // CK on encoder
    int DT;  // DT on encoder
    int PUSH; // push shaft on encoder (marked 'SW' on unit)
    // debounce time for all pins
    int debounce; // ms debounce time

  public:
    bool enc_pressed = false;
    int position = 0;
    
    // I exposed these for easy external access, rather than using delegates
    ezButton * buttonClk;
    ezButton * buttonDt;
    ezButton * buttonPush;

    // Contructor provides defaults for pins and debounce time - I am using a MEGA2560
    KY040b(int clkpin=32, int dtpin=34, int buttonpin=36, int deb=25 )
    // default construction using pro-forma parameters
       : CLK(clkpin), DT(dtpin), PUSH(buttonpin), debounce(deb)
    {
        buttonClk = new ezButton(CLK);
        buttonDt = new ezButton(DT);
        buttonPush = new ezButton(PUSH);

        buttonClk->setDebounceTime(debounce);
        buttonDt->setDebounceTime(debounce);
        buttonPush->setDebounceTime(debounce);

        pinMode (CLK,INPUT);
        pinMode (DT,INPUT);
        pinMode (PUSH, INPUT); 
    };

    ~KY040b()
    {
      delete buttonClk;
      delete buttonDt;
      delete buttonPush;
    };

    void loop(void)
    {
      static uint8_t accum = 0; // Not really required - used this in tests
    
      static int8_t count = 0; // each detent position change generates TWO events
      static int rotation = buttonClk->getState(); 
      int value, dt;

      buttonClk->loop(); // MUST call the loop() func for each pin
      buttonDt->loop(); //
      buttonPush->loop();

      enc_pressed = buttonPush->isPressed(); // test shaft 'pushed'
      #ifdef _DEBUG_ENCODER_
          if (enc_pressed)
              Serial.println("pressed");
      #endif
    
      value = buttonClk->getState();
      if (value != rotation) // compare with previous reading
      { 
        dt = buttonDt->getState();
        if (dt != value) 
        {  
          // Clockwise
          if (count == 1) 
          {
            position++; // update the global
            #ifdef _DEBUG_ENCODER_
                Serial.println ("clockwise");
            #endif
          }
        } 
        else 
        { 
          //Counterclockwise
          if (count == 1)
          {
            position--;
            #ifdef _DEBUG_ENCODER_
                Serial.println("counterclockwise");
            #endif
          }
        }
        rotation = value; // update last reading
        
        // the following is for interest only, to see the coded sequences
        accum = accum << 1;
        accum |= value;
        accum = accum << 1;
        accum |= dt;
        
        count++; // required
        if (count == 2)
        {
           #ifdef _DEBUG_ENCODER_
              Serial.print("accum: "); Serial.println(accum, BIN);
              Serial.print("Encoder Position: "); Serial.println(position);
           #endif
           count = 0; // reset!
           accum = 0;
        }
      } 
    };
    
}; // end KY040b

KY040b encoder; // defaults to: 32,34,36,25

void setup() 
{ 
  Serial.begin (9600);
}

void loop()
{
    static int position = 0;
    encoder.loop();
    int pos = encoder.position;
    if (pos != position)
    {
        position = pos;
        Serial.print("Encoder Position: "); Serial.println(position);
        
    }
    bool pressed = encoder.enc_pressed;
    if (pressed)
        Serial.println("pressed");
}


Hi. In my haste to upload this code yesterday, I flunked it and sent ‘position’ to the Serial Output BEFORE I updated it. I’ve corrected the mistake. Joe.