Rotary Encoders

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.
———————————————————————————-

Arduino

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

volatilebooleanTurnDetected;

volatilebooleanup;

constintPinCLK=2;// Used for generating interrupts using CLK signal

constintPinDT=3;// Used for reading DT signal

constintPinSW=4;// Used for the push button switch

voidisr(){// Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK

staticlongvirtualPosition=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);

}

}

}

----------------------------------------------

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.

* Since this code was in the form of an arduino library, and I had unresolvable

* references that I could not figure out, I just modified it as a single sketch.

* The only library support required is if you want to use interrupt polling,

* and then the MsTimer2 library needs to be installed.

*/

/* Define PINCHANGEINT if you want to interrupt on any encoder pin change */

#define PINCHANGEINT

/* --- OR --- */

/* Define TIMER2INT if you want to use periodic interrupts to poll the encoder */

//#define TIMER2INT

/* Define ENABLEPULLUPS if there are no external pull-ups on encoder AB pins */

//#define ENABLEPULLUPS

/* Define to enable the ISR debug flag */

//#define ISRFLAG

/* You may need to install this library (MsTimer2) from the arduino site */

#ifdef TIMER2INT

#include &lt;MsTimer2.h&gt;

#endif

#define DIR_NONE 0x00 // No complete step yet.

#define DIR_CW 0x10 // Clockwise step.

#define DIR_CCW 0x20 // Anti-clockwise step.

unsignedintstate;

unsignedintA=2;// pins connected to the encoder (digital_pin 2)

unsignedintB=3;// " (digital_pin 3)

unsignedintISRflag=5;// " (digital_pin 3)

intcount=0;// count each indent

intold_count=0;// check for count changed

/*

* The below state table has, for each state (row), the new state

* to set based on the next encoder output. From left to right in,

* the table, the encoder outputs are 00, 01, 10, 11, and the value

* in that position is the new state to set.

*/

// State definitions state table (emits a code at 00 only)

// states are: (NAB) N = 0: clockwise; N = 1: counterclockwiswe

#define R_START 0x3

#define R_CW_BEGIN 0x1

#define R_CW_NEXT 0x0

#define R_CW_FINAL 0x2

#define R_CCW_BEGIN 0x6

#define R_CCW_NEXT 0x4

#define R_CCW_FINAL 0x5

constunsignedcharttable[8][4]={

{R_CW_NEXT,R_CW_BEGIN,R_CW_FINAL,R_START},// R_CW_NEXT

{R_CW_NEXT,R_CW_BEGIN,R_CW_BEGIN,R_START},// R_CW_BEGIN

{R_CW_NEXT,R_CW_FINAL,R_CW_FINAL,R_START|DIR_CW},// R_CW_FINAL

{R_START,R_CW_BEGIN,R_CCW_BEGIN,R_START},// R_START

{R_CCW_NEXT,R_CCW_FINAL,R_CCW_BEGIN,R_START},// R_CCW_NEXT

{R_CCW_NEXT,R_CCW_FINAL,R_CCW_FINAL,R_START|DIR_CCW},// R_CCW_FINAL

{R_CCW_NEXT,R_CCW_BEGIN,R_CCW_BEGIN,R_START},// R_CCW_BEGIN

{R_START,R_START,R_START,R_START}// ILLEGAL

};

voidsetup(){

pinMode(A,INPUT);

pinMode(B,INPUT);

#ifdef ENABLEPULLUPS

digitalWrite(A,HIGH);// set pullups

digitalWrite(B,HIGH);// "

#endif

#ifdef TIMER2INT

MsTimer2::set(1,T2_isr);// interrupt polling:

MsTimer2::start();

#endif

#ifdef PINCHANGEINT

attachInterrupt(0,AB_isr,CHANGE);// pin-change interrupts:

attachInterrupt(1,AB_isr,CHANGE);

#endif

#ifdef ISRFLAG

pinMode(ISRflag,OUTPUT);// time the ISR

digitalWrite(ISRflag,HIGH);// set pull-up ON

#endif

state=(digitalRead(A)&lt;&lt;1)|digitalRead(B);// Initialise state.

old_count=0;

Serial.begin(9600);

Serial.println("Rotary Encoder Tests");

}

#ifdef PINCHANGEINT

voidAB_isr(){

// Grab state of input pins.

unsignedcharpinstate=(digitalRead(A)&lt;&lt;1)|digitalRead(B);

// Determine new state from the pins and state table.

state=ttable[state&amp;0x07][pinstate];

if(state&amp;DIR_CW)count++;

if(state&amp;DIR_CCW)count--;

}

#endif

#ifdef TIMER2INT

voidT2_isr(){

#ifdef ISRFLAG

digitalWrite(ISRflag,HIGH);

#endif

// Grab state of input pins.

unsignedcharpinstate=(digitalRead(A)&lt;&lt;1)|digitalRead(B);

// Determine new state from the pins and state table.

state=ttable[state&amp;0x07][pinstate];

if(state&amp;DIR_CW)count++;// count up for clockwise

if(state&amp;DIR_CCW)count--;// count down for counterclockwise

#ifdef ISRFLAG

digitalWrite(ISRflag,LOW);

#endif

}

#endif

voidloop(){

if(old_count!=count){

Serial.println(count);

old_count=count;

}

}

1

If you count a pulse every time aVal changes state, (LOW to HIGH and HIGH to LOW) and don’t otherwise account for it, you will negate your counts and only count the spurious ones. Therefore use aVal *only* when it transitions from HIGH to LOW (or visa-versa, but this works, so I use it.) This seems to be the reason many have resorted to using Interrupts so they can use FALLING or RISING in their code. A change in the ‘if’ statement mimics the FALLING interrupt and made it all work with my encoders:

if ((aVal != aLast)&&(aVal==LOW)) {

The bold text above is the change. Now we use the datapoint only when transitioning from HIGH to LOW to indicate that the knob moved. And…

if(bVal == LOW){ encoderCount++; }
else { encoderCount–; } }

Gives direction. If bVal is also LOW, then the knob was turned CW, so lets increment our counter by one step. Else, if bVal was HIGH, CCW and let’s decrement our counter.

This works from fairly slow up to a fairly fast knob rotation. And NO USE OF INTERRUPTS!