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");
}