WIRING of KY-040 Rotary Encoder plus Demo Code

ROTARY ENCODER KY-040 on Arduino UNO - May 2014

WIRING INFORMATION

Connect CLK to Pin 2 on Arduino Board (CLK is Data Output 1 of KY-040)
Connect DT to Pin 3 on Arduino Board (DT is Data Output 2 of KY-040)
Connect SW to Pin 4 on Arduino Board (Switch - goes LOW when pressed)
Connect GND to ground
Connect + to +5V (this will pull up CLK and DT with 10 KiloOhm resistors)

Connect a 0,47µ capacitor from ground to CLK (debouncing)
Connect a 0,47µ capacitor from ground to DT (debouncing)
Connect a 10 KiloOhm resistor from +5V to SW (no integrated pullup for SW !!)

It is better NOT to use internal pull-up resistors on the Arduino, instead
use the integrated pull-ups of KY-040 (this requires "+" to be connected to 5V).
You can check if your version of the KY-040 has pull-up resistors on the bottom
side ouf the printed circuit board.
If not, use internal pull-ups from Arduino or external pull-ups.

In the stopping positions the KY-040 has always HIGH signals on both CLK and DT.
When you turn the encoder from one position to another, either CLK or DT goes LOW
before the other signal goes LOW as well.
The signal that goes LOW first determines if the encoder is turned left or right.
Once you reach the next stopping position both signals will be HIGH again.

If you press the push button, the current count can be reset to ZERO.

For faster response you might increase the speed of the serial connection.
(Make sure, that the Serial Monitor is also set to a higher speed,
otherwise you will get no output).

My rotary encoder came from china for 2 dollars, and after some debugging i found out, that it sometimes
behaves strange (e.g. it gives a signal to the SW pin - although I did not press the button.)
So for serious projects invest a dollar more and buy a quality product.

volatile boolean TurnDetected;
volatile boolean up;

const int PinCLK=2;                   // Used for generating interrupts using CLK signal
const int PinDT=3;                    // Used for reading DT signal
const int PinSW=4;                    // Used for the push button switch

void isr ()  {                    // Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK
  if (digitalRead(PinCLK))
    up = digitalRead(PinDT);
  else
    up = !digitalRead(PinDT);
  TurnDetected = true;
}


void setup ()  {
  pinMode(PinCLK,INPUT);
  pinMode(PinDT,INPUT);  
  pinMode(PinSW,INPUT);
  attachInterrupt (0,isr,FALLING);   // interrupt 0 is always connected to pin 2 on Arduino UNO
  Serial.begin (9600);
  Serial.println("Start");
}

void loop ()  {
  static long virtualPosition=0;    // without STATIC it does not count correctly!!!

  if (!(digitalRead(PinSW))) {      // check if pushbutton is pressed
    virtualPosition=0;              // if YES, then reset counter to ZERO
    Serial.print ("Reset = ");      // Using the word RESET instead of COUNT here to find out a buggy encoder
    
    Serial.println (virtualPosition);
  }  
  
  if (TurnDetected)  {		    // do this only if rotation was detected
    if (up)
      virtualPosition++;
    else
      virtualPosition--;
    TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
    Serial.print ("Count = ");  
    Serial.println (virtualPosition);
  }
}

Hi and welcome.

Did you read this (click !) ?
Don't answer that question, it's a truism, i already know you didn't.
Please do and edit your post accordingly.
That will make your post / example as useful as you hoped it would / meant it to be.

Thank you @arduinoaleman

This has definitely helped me with my KY-040 encoders I've just received from eBay. I appreciate the post.

Cheers
Ben

void isr ()  {                    // Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK
  if (digitalRead(PinCLK))
    up = digitalRead(PinDT);
  else
    up = !digitalRead(PinDT);
  TurnDetected = true;
}

There is redundancy in your ISR because it is only triggered by FALLING. and the digitalRead of PinCLK (the interrupt pin) can not be HIGH.

This code works fine with the simplified ISR

void isr ()  {                    // Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK
    up = !digitalRead(PinDT);
  TurnDetected = true;
}

cattledog:
This code works fine with the simplified ISR

And it just might, except that you will be in a world of pain "down the track" using interrupts inappropriately and trying to figure out how to debounce the thing which will be necessary for any serious use of this encoder given its construction.

Interrupts - and the glib tutorials detailing them - are a major trap for "newbies". Rotary encoders for manual input should in almost all cases be polled using suitable debounce routines, not interrupts.

Rotary encoders for manual input should in almost all cases be polled using suitable debounce routines, not interrupts.

Agreed. I probably should have commented upon the OP's approach as well as his specific code.

To his credit, the OP did have his encoder debounced with hardware, and did not try and place debounce routines with millis() timers within the interrupt.

Hi guys! Today I found in mailbox the encoder KY040 from China.

So I found this thread and copy/paste original code.
I found out that when you turn the encoder, Arduino always skip one step. That really irritates me.

So I must little bit change base code from first post.
I change interrupt mode from FALLING to CHANGE

  attachInterrupt (0,isr0,CHANGE);

modify isr routine

void isr0 ()  {
  up = (digitalRead(PinCLK) == digitalRead(PinDT));
  TurnDetected = true;
}

and correct little bug.

  if (!(digitalRead(PinSW)) && (Position != 0)) {

I haven't found a 0,47 uF capacitor, but with 1 uF it's working fine. Without bouncing.
Add one capacitor on the SW pin too.

When it comes to time critical responses you should NOT use polling techniques.

In many cases software debouncing seems to be the more elegant solution. However, hardware debouncing saves you processing time and does not need any code. It requires just a couple of resistors and capacitors in most cases (this is why software people always go for software debouncing).

So there is NO GENERAL RULE when it comes to choosing between interrupts and polling.

It just depends on your project and if you can afford to lose some input signals. In most situations it is not critical to lose an input signal from a rotary encoder, because you simply have to turn it once more.

If you have any doubts please google for: "jeremy blum tutorial 10".
This tutorial is about HW debouncing and interrupts. Pretty good stuff.
You will like his other tutorials as well.

And please DO NOT use SW debouncing in combination with interrupts.
Otherwise - do whatever you want to do.

@arduinolaleman.... please don't change the post's subject title. That made this thread get a new name (at least while your post was the latest) with no "Re:" in the list. It's my opinion, but that is really annoying. At least one member disagrees with me; at least one mod agrees with me.

JimboZA:
That made this thread get a new name (at least while your post was the latest) with no "Re:" in the list. It's my opinion, but that is really annoying. At least one member disagrees with me; at least one mod agrees with me.

That is bizarre.

When I look in the index, I see only the original thread name.

Paul__B:
That is bizarre.

When I look in the index, I see only the original thread name.

NOW you do yes, since it seems it reverts to the thread title once another post is made.

While the post that changes the title is the last post, the changed title is the one that appears in the list, no "Re:" and looking like a new thread.

So by the time you had seen my post, my post was of course the latest one, and the title had reverted.

Ahh, dear - you are just not getting the message!

I just ran a quick test and am vindicated. Whatever I may change the title to, the "subject" field in the index does not change.

We know that, as we are so often telling "newbies" to change the subject line in their first post to include the term "Solved" so that is then indicated in the index because it is that subject that is used in the index.

What is confusing you, and on what I have been challenging you, is that you are not using the Category index, but some other listing - presumably the overall "Recent Posts" listing which is somewhere available and which you would have to customise to choose which particular categories you "follow". And that is of course, your prerogative, but since that is not the general index, I see it differently.


Actually, you now cause me to research the matter. :wink: It actually must be the "Unread posts" listing which is rather clever because it knows which categories I follow (in separate tabs on my "main" computer but not my wife's or the "Church" machine while I print CDs or burn CDs respectively, in quantity).

But I have a particularly elegant solution for you! Since you know I may have altered a topic as it appears in that particular listing, then just do not read any topic where you see I was clearly listed as the most recent contributor, because it tells you that.

Dead easy really! :grinning:

I bow to your superior something or other.

I'm out.

Been meaning to leave the forum for ages.... now I just did,

Toodles everyone.

1 Like

Hello arduinoaleman,

Your code work well, thank you for the explanations.
And for you have no regret. You say :

"My rotary encoder came from china for 2 dollars, and after some debugging i found out, that it sometimes behaves strange (e.g. it gives a signal to the SW pin - although I did not press the button.).
So for serious projects invest a dollar more and buy a quality product."

No worries about this, after i was get one kit on Amazon in France i was get my things from China.
Even the kit make the sensor an things less expensive that still more than in China, i think around 1,50€ a bit less than your.
And at last the rotary encoder from the kit a "Keyes" product don't work well, it have weird bounces, and respond in inverse ! The Chinese product work like a charm, and in more it's 0,95€.
Of course the both came from China, but Keyes is very knew and that's not a good deal sometime.

Regards.

I am very new to this arduino, but am learning. I want to control a small stepper motor remotely wired to turn an air capacitor for a magnetic loop antenna. I figured using a rotary encoder to turn the motor at the same speed and movement as the encoder. does anyone have any insight to this and can point me in the right direction, thank you

I'm using this encoder and I'm getting really imprecise values.

reklamchef:
I'm using this encoder and I'm getting really imprecise values.
https://forum.pjrc.com/attachment.php?attachmentid=4644&d=1436318401

And your point is?

1 Like

Try this state machine based code. It uses two pins for pin-change interrupts. It's nearly unbreakable.

It's really pointless to try to filter an encoder with caps. What are the characteristics of your "noise"? What is the worst case period and rise-time of your bounce? What is the typical frequency of your bounce? No one knows, but somehow they know what capacitor value to use. On top of that, you are also filtering your "real" signal. You're slowing the rise time and shortening the period of the signal you ARE interested in.

This state machine just follows the bounce. You can use a high dollar encoder or a very cheap one that has terrible bounce. The state machine will handle them both without the extra cost more components and a degraded signal.

roto_sm.ino (4.81 KB)

2 Likes

Hi, guys! My version of code

volatile boolean TurnDetected;
volatile boolean up;
static long virtualPosition=0;    // without STATIC it does not count correctly!!!

const int PinCLK=2;                   // Used for generating interrupts using CLK signal
const int PinDT=3;                    // Used for reading DT signal
const int PinSW=4;                    // Used for the push button switch

void isr ()  {                    // Interrupt service routine is executed when any CHANGE transition is detected on CLK
    volatile boolean CLK = digitalRead(PinCLK);
    volatile boolean DT = digitalRead(PinDT);
    up=((!CLK && DT)||(CLK && !DT));
   
    TurnDetected = true;
}

void setup ()  {
 pinMode(PinCLK,INPUT);
 pinMode(PinDT,INPUT);  
 pinMode(PinSW,INPUT);
 attachInterrupt (0,isr,CHANGE);   // interrupt 0 is always connected to pin 2 on Arduino UNO
 Serial.begin (9600);
 Serial.println("Start");
}

void loop ()  {

 if (!digitalRead(PinSW)) {      // check if pushbutton is pressed
   virtualPosition=0;              // if YES, then reset counter to ZERO
   Serial.print ("Reset = ");      // Using the word RESET instead of COUNT here to find out a buggy encoder
   Serial.println (virtualPosition);
 }  
 
 if (TurnDetected)  {       // do this only if rotation was detected
     if (up)
     virtualPosition++;
   else
     virtualPosition--;
   TurnDetected = false;          // do NOT repeat IF loop until new rotation detected
   Serial.print ("Count = ");  
   Serial.println (virtualPosition);
 }
}

encoder01.ino (1.47 KB)

Thanks guys! I just received my KY-040 encoders for a project I'm going to start (a B737 MSC panel) so this example was very useful for me to understand encoders principles!

Btw, I suggest enabling internal pull-up for SW pin, as KY-040 SW output is not pulled-up (there is a spot for a 10k resistor below, but the resistor isn't there...), then a few other little changes like adding a LED to show current value, so this is my version.

const int PinCLK = 2;                   // Used for generating interrupts using CLK signal
const int PinDT = 3;                    // Used for reading DT signal
const int PinSW = 4;                    // Used for the push button switch
const int PinLED = 9;

int SW;
int oldSW;

volatile boolean encChanged;
volatile long encPosition = 0;
volatile boolean up;

void isr()  {                    // Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK
	volatile boolean CLK = digitalRead(PinCLK);
	volatile boolean DT = digitalRead(PinDT);
	up = ((!CLK && DT) || (CLK && !DT));
	if (!up)
		encPosition++;
	else
		encPosition--;
	if (encPosition < 0) encPosition = 0;
	encChanged = true;
	delay(10);
}


void setup()  {
	pinMode(PinCLK, INPUT);
	pinMode(PinDT, INPUT);
	pinMode(PinSW, INPUT_PULLUP);
	pinMode(PinLED, OUTPUT);
	SW = HIGH;
	oldSW = HIGH;
	attachInterrupt(0, isr, FALLING);   // interrupt 0 is always connected to pin 2 on Arduino UNO
	Serial.begin(115200);
	Serial.println("Start");
}

void loop()  {
	SW = digitalRead(PinSW);
	if ( SW == LOW && oldSW == HIGH) {      // check if pushbutton is pressed
		encPosition = 0;              // if YES, then reset counter to ZERO
		Serial.print("Reset");
		encChanged = true;
	}
	oldSW = SW;

	if (encChanged)  {		    // do this only if rotation was detected
		encChanged = false;          // do NOT repeat IF loop until new rotation detected
		Serial.print("Count = ");
		Serial.println(encPosition);
		analogWrite(PinLED, encPosition);
	}
}