Rotary Encoder: Immediately Tame your Noisy Encoder!

Rotary Encoder: Immediately Tame your Noisy Encoder! Find out how to Instantly Stop Switch Bounce using one of 2 software methods. Easily get Reliable Operation from your Encoder.


Arduino rotary encoders: Are you struggling to get one reasonable output from your encoder? Encoders are notoriously difficult to get a single output change because of switch bounce. What tends to happen is that multiple increments or decrements occur.
  • Find out two reliable methods to tame your noisy encoder.

  • Get consistent output from an encoder.

  • Use them to ensure one position move results on only one change in output.

In this page the Keys KY-040 encoder is used throughout.

A rotary encoder is an input device that you can rotate in either direction continuously. As you turn the device it generates digital pulses to show the direction of rotation using two phased output signals. These two outputs also indicate single position movements, so you can use them in control panels to increment or decrement parameters.

Hair Saved!
Neil Gleaden : Been pulling my hair out trying to debounce a KY-040 on the STM32, Your "Robust Rotary encoder reading" worked perfectly. You've saved me from having to wear a hat, Many thanks.
Facebook comment on page.
The Scope Lies! (or needs a closer look;)
Jessie Kropp : Fantastic. Thank you. I was about to give up on coding for this rotary. On the scope it all looked good but it's amazing how quickly the MCU can read and mis-interpret things (or my bad code I should say).
Facebook comment on page.


The type of encoder used below for demonstration is also known as an incremental rotary encoder since it generates pulses indicating single step changes. Other types generate an absolute output i.e. the same output number (4 or more bits depending on the accuracy required) is generated for a specific position of the encoder and you would use these in robotics applications.

Note: Rotary encoders produce extremely noisy output oscillations due to switch bounce and the information on this page gives you two techniques to eliminate that noise. The first is a simple filter method and the second method uses table decoding to get really good output from low quality devices.

The purpose of this tutorial is to provide an example for the Arduino of a simple rotary encoder implementation.

Rotary Encoders allow you to easily increase or decrease parameters by a single value.

As well as generating directional information and step change pulses the device has a physical feedback mechanism that lets you feel when you move from one position to the next. These points are known as detents and range from 12 to 24 positions within a 360º rotation. For the device used here there are 20 detents.

Unlike a potentiometer the rotary encoder has no end stops so you can use one to continually increase or decrease a parameter (once decoded by the microcontroller) and there is no need to set the control position back to a start point (there is none).

They also often have a push button switch built into the shaft which is useful for menu selection etc.

You can use them for many applications ranging from:

  • Volume control
  • Illumination level control
  • Parameter control e.g. for a process e.g. speed, height, temperature etc.
  • Menu selection (the push button is useful here).

Since the outputs are digital signals you can process them using a microcontroller and use the result in any way you want i.e. to change the value of a variable representing a system parameter.

In fact rotary encoders look simple, but there is quite a lot going on inside these small devices (~11mm x ~13mm ).

Here's what is going on:

  • Two outputs providing quadrature coded signals.
  • Physical position feedback and bump stop (known as detents) - for this device there are 20 detents.
  • Main shaft push button (a push to make switch).

As you turn the control knob you can feel the each of the 'detent' position stops, so you know when you have turned the device by exactly one position. This provides fine grained physical feedback allowing exact parameter changing. This is very different to using a potentiometer to set the volume level etc. where there is no physical feedback.

Quadrature Phase Shift Encoding

This rather technical sounding encoding method is in fact very simple. all it means is that two signals are offset from one another by a quarter of a period (or phase shifted by 90º). It also falls out in the wash that the signals generated are grey coded which just means that no two signals edges are aligned i.e. signal outputs do not change state at the same time.

Gray coding is useful for electro-mechanical devices to generate signals that are unambiguous. For example if the output was binary coded then at the point of transition (due to small delays in signal paths) you might decode a completely erroneous value i.e. at the point of transition any codes could be generated.

This could be a problem especially if only combinatorial logic is used as the decoder. Gray code stops that from happening (although it does not stop switch bounce).

The following diagram shows the rotary encoder waveform output on pins A and B (CLK) and (DT) respectively.

rotary encoder quadrature signals A and B

[Source PEC11L datasheet]

Note: The D in the diagram above shows where the detent position is located. In fact this is where the outputs are not connected to ground hence they are pulled high by the 10k resistors on the breakout board.

Inside the Rotary Encoder

The following diagram shows the inner workings of the rotary encoder. Each of the three connections 8A, 8B and 8C is formed of a spring arm that pushes down on the substrate.

There are three signals, one connected to the metal substrate (Ground) and two others that move over the alternating substrate pattern. So the outputs are shorted to ground as the device is rotated and then are left floating (unconnected) when the contact is in the substrate gap.

Note how the spring arm contacts are physically offset by a quarter of the period (defined by the physical substrate) - contacts 8B and 8C in the diagram below - this is how the quadrature encoded outputs are generated.

internal rotary encoder operation

Source: Expired Patent (Now in the public domain).

Note: The contacts 8A, 8B, 8C are springs that bounce on and off the contact substrate causing the output signals to bounce between high and low i.e. switch bounce.

However spring contact 8A, connecting to pin 8D, won't bounce as it is on a substrate with no breaks. This should be used as ground with 8E and 8F connected to pullup resistors.

Types of Rotary Encoder

Contacting Incremental Rotary Encoders

This is the type of device used in the demonstration on this page. At each detent position two quadrature signals are generated indicating a single position change and showing the direction of rotation.

This particular device has quite a high rotational life - 100k rotations (see datasheet) - but since there is physical contact, the device will eventually wear out. In the Bourns catalogue other physical devices range from 15k to 200k maximum rotations.

Optical Encoder

The PEC11Lhas a maximum RPM of 60RPM whereas optical encoders, in that catalogue, have a 10 million revolution life and can operate at 3000rpm - these are the types you could use for measurements in high speed machinery but see magnetic encoder below which has an even higher life and of course a higher cost!

Magnetic Encoder

For even higher rotational life, a magnetic encoder offers the best choice (since there is no physical contact within the device) the only part that will wear out is the shaft bearings. These offer a rotational life of 100 million revolutions!

These devices come in 4 different flavours:

  1. Incremental Quadrature (same as the PEC11L used here).
  2. Direction/Step encoder - offers better resolution (up to 512 pulses per revolution).
  3. Absolute encoder - allows the absolute position detection of the encoder (1024 codes define the position).
  4. PWM Encoder - generates a PWM output from 1us to 1024us width - advantages claimed, are noise immunity and faster data acquisition.

Measurements: Using Rotary Incremental Encoders

The following examples cover the following measurements:

  • Velocity
  • logarithmic change

Velocity

You might want to measure velocity to use as a parameter in your code e.g. if you turn the wheel faster then perform a different action e.g. change the parameter at a different rate.

Logarithmic

This is a parameter adjustment that measures the rotation speed and if found to be constant periodically increases the parameter. This is very useful for devices that have a large range of control e.g. a DDS (Direct Digital Synthesis) that can output frequency form 1 to 10MHz. You really don't want to sit there turning the knob by 1Hz periods to get to 10MHz!

Decoding Methods

There are two ways to decode rotary encoder outputs:

  • Polling
  • Interrupts

On the KY-040 there are two signals labelled DT and CLK meaning CLOCK and DATA. If you look at the timing diagrams for these signals it seems obvious to use the CLOCK as a clock and read the DATA input on the rising edge of the clock. However this ignores the fact that the signals bounce all over the place.

If you use the CLK signal as an interrupt you will get into deep trouble as the random bouncing of the input will trigger interrupts all the time (and not at the time when you want to read the data signal) so you will get incorrect data.

There are polling methods to decode the grey coded signals using a state machine so that bouncing signals are ignored i.e. error states are ignored. These are quite complex and sometimes get out of sync.

The way I use these devices is a combination of a small amount of smoothing capacitor and a simple digital rotary switch debounce algorithm. (See code below). This provides easy to understand code (also small code size) and works to accurately obtain individual detent position information and also accurate directional rotation information.

Sometimes, though, you may have a very poor quality encoder and that needs more effort to decode and in that case you will need to look at the more complex robust decoder code here.

Device Decoding Techniques

You can use many clever ways to decode the outputs involving complex state machines and gray decoding algorithms. Some use interrupts, and most use polling.

The problem with connecting the outputs to an interrupt pin is that you have no control over the bounce that you may experience and the processor could be interrupted too much to do any useful work (and may even hang) and will get the wrong value from the data input anyway.

Warning: Rotary encoders are extremely noisy because of switch bounce due to the internal construction of the device (using physical contact springs that bounce over the substrate connections). This makes it extremely difficult to accurately decode the device outputs.

However see my new technique - the last code example code.

Switch bounce occurs as the contacts are springs that bounce on and off a substrate contact - even the data sheet indicates the switch bounce you can get for each turn is up to 10ms (Bourns PEC11L).

rotary encoder quadrature signals A and B

[Source PEC11L datasheet]

You can see that the signals labelled A and B can be changed to CLK (clock) and DT (data) and that a rising edge on a clock signal (A) will give a a logic low on DT (B) if turned clockwise, and a logic high if turned anti-clockwise (when turned in the opposite direction the falling edge becomes the rising edge!).

Capacitor Smoothing

Adding a huge smoothing capacitor (and resistor see diagram below and replace the 0.01uF with 470nF as an example of a "too big" capacitor" - which is suggested by some people) to stop the bounce will stop the bounce but also slows the input signal level to the point where it will go through the undefined logic input level (below the top threshold VIH and above the lower threshold VIL) of the microcontroller. In this input region noise on that input could (and often does!) trigger the input to high or low causing oscillation i.e. making more bounce signals and not solving the problem at all.

Note: Some microcontroller inputs have built in Schmitt trigger inputs.

You can get around this by using a schmitt trigger device such as a 74HC14 to create the correct fast edge signals but you may alter the timing too much to get a useful output signal.

RC pair and digital Filter

One way I have found is to use a small smoothing capacitor resistor pair along with a digital debounce filter. This allows individual detent positions to be accurately identified (slow turning of the control shaft is accurately decoded). At faster revolutions codes are missed but the real point of the rotary encoder is to allow accurate individual detent (and direction) detection. You don't need to know exact detent stops for fast revolutions - all you need is to know that the user wants to increase the parameter faster.

Digital Debounce Filter

The digital filter is made up of a single 16 bit integer variable into which you shift the current state of the input pin:

state=(state<<1) | digitalRead(CLK_PIN) | 0xe000;

This is a very compact filter - each time round the loop a new bit is shifted left (at bit 0). The "or" action with 0xe000 defines the number of iterations i.e. the top 3 bits are blocked off leaving the rest as useful inputs. The idea is that you test for the state 0xf000 which can only occur if there was a sequence of 1 0000 0000 0000 inputs meaning that the signal has been stable for 12 iterations around the loop i.e. not bouncing around.

Arduino Rotary Encoder Datasheet

The rotary encoder used in the KY-040 looks like a Bourns PEC11L device - you can download that rotary encoder datasheet from the link below. All that the breakout board does is add two 10k pullup resistors (R2 and R3) while the space for the switch pullup has been left blank.

Download PEC11L datasheet.

Arduino Rotary Encoder Software Setup:

IDE Version Used : 1.6.4
Board used : Arduino Uno R3

Rotary Encoder Hardware Setup

Device Used : KY-040 (breakout board)

Other components 10k resistor and 10nF capacitor - only for the clock signal, connected as shown below:

rotary encoder connections

[Source PEC11L datasheet]

Note: 10k and 10n are extra to the breakout board (A and B have the 10k pullups on the board). Only add them to the clock signal (A).

Example Rotary Encoder Code:

This is a an arduino ky-040 rotary encoder example that shows you how to decode the 20 turn encoder by removing switch bounce. using a digital filter technique. The operation of this digital filter is discussed here.

Note: The quality of the encoder will affect the output signal (one of mine skips codes whereas a higher quality one does not!).

#define CLK_PIN  2
#define DATA_PIN 7
#define YLED A1

////////////////////////////////////////////////////
void setup() {
   pinMode(CLK_PIN,INPUT);
   pinMode(DATA_PIN,INPUT);
   pinMode(YLED,OUTPUT);

   Serial.begin(9600);
   Serial.println("Rotary Encoder KY-040");
}

////////////////////////////////////////////////////
void loop() {
static uint16_t state=0,counter=0;

    delayMicroseconds(100); // Simulate doing somehing else as well.

    state=(state<<1) | digitalRead(CLK_PIN) | 0xe000;

    if (state==0xf000){
       state=0x0000;
       if(digitalRead(DATA_PIN))
         counter++;
       else
         counter--;
       Serial.println(counter);
    }
}


Taming Noisy Rotary Encoders

The keyes-040 encoder can be very noisy due to switch bounce and you may need to use a much more robust way of decoding it - I have one that is fairly well behaved and one that is massively noisy.

The following example uses a table decode method which requires more code than in the previous example but is capable of reading rotary encoders without needing any debounce capacitors at all. (However check that this works with your own hardware to make sure of this).

The way it works is to encode the outputs of the decoder as a binary number. To do this you can define the CLK as an LSB binary digit and DATA as the MSB binary digit.

Then observe the valid states that the outputs can occupy i.e. the ones shown in the diagram below by dotted lines.

Because the outputs are in quadrature and because this results in a gray code output no output changes state at the same time as another. What this means is that only one of the two outputs will be bouncing around at any transition edge. This means bouncing signals can be easily ignored because the bouncing mostly produces invalid encoder states.

rotary encoder table assignments

If you look at the diagram above you can see that there are four states (11, 10, 00, 01). In addition to that, there are only 8 ways that you can move from one state to the next including going backwards (Anti Clockwise).

For clockwise motion you can only perform the following actions:

(11 > 10), (10 > 00), (00 > 01) and (01 >11)

Similarly only the following encoder output transitions are valid for Anti-Clockwise rotation:

(01 > 00), (00 > 10), (10 > 11), and (11 > 01)

You can find other rotary decoding methods (including this one) here.

The idea behind the table method is that you store the previous state and current state and set them out as a binary code. In this way the table directly encodes the transition of valid outputs - the main purpose behind the technique is that invalid ones caused by switch bounce are discarded.

Valid Code Output

So for the clockwise direction above, there are four valid outputs (where the 2 MSBits are the previous state and the 2 LSBits are the current state):

1110

1000

0001

0111

Only these are valid states. In theory only these should be output by the rotary encoder but in practice switch bounce generates other codes.

For the opposite direction (Anti Clockwise) the following codes are valid:

0100

0010

1011

1101

To allow the microcontroller to check for valid codes and ignore invalid ones a table is needed (with the 4 bit PSNS - Previous State Next State - code as the input):


PSNS (Prev State, Next State) Valid code Direction
0000 X X
0001 Valid CW
0010 Valid CCW
0011 X X
0100 Valid CCW
0101 X X
0110 X X
0111 Valid CW
1000 Valid CW
1001 X X
1010 X X
1011 Valid CCW
1100 X X
1101 Valid CCW
1110 Valid CW
1111 X X

Coding this into a C table and replacing CW with 1 and CCW with -1 and invalid as 0 results in the following:

rot_enc_table[]= {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0};

You can find code that uses this method elsewhere on the web ( I may have swapped -1 for 1 - does not really matter just swap SIG A and SIG B) but that method takes any CW or CCW valid output as a real transition and so for a "detent" to "detent" movement four CWs or four CCW states are returned (the detent position is shown in the diagram below). The problem is that bouncing may cause the a state change backwards until the switch settles and goes forwards again.

rotary encoder quadrature signals A and B

Improved Table Decode Method

By using the following code you can see the outputs generated between each detent. The code simply generates a newline when it finds either 7 or 0xB. These are the last codes generated when performing a detent to detent rotation.

Rotary Encoder Quality Test Program

Use the following program to see how good/bad your encoder is (observe typical results below).

#define CLK 2
#define DATA 7
#define BUTTON A5
#define YLED A2

void setup() {
  pinMode(CLK, INPUT);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DATA, INPUT);
  pinMode(DATA, INPUT_PULLUP);
  pinMode(BUTTON, INPUT);
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(YLED,OUTPUT);

  Serial.begin (115200);
  Serial.println("KY-040 Quality test:");
}

static uint8_t prevNextCode = 0;

void loop() {
uint32_t pwas=0;

   if( read_rotary() ) {

      Serial.print(prevNextCode&0xf,HEX);Serial.print(" ");

      if ( (prevNextCode&0x0f)==0x0b) Serial.println("eleven ");
      if ( (prevNextCode&0x0f)==0x07) Serial.println("seven ");
   }

   if (digitalRead(BUTTON)==0) {

      delay(10);
      if (digitalRead(BUTTON)==0) {
          Serial.println("Next Detent");
          while(digitalRead(BUTTON)==0);
      }
   }
}

// A vald CW or CCW move returns 1, invalid returns 0.
int8_t read_rotary() {
  static int8_t rot_enc_table[] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0};

  prevNextCode <<= 2;
  if (digitalRead(DATA)) prevNextCode |= 0x02;
  if (digitalRead(CLK)) prevNextCode |= 0x01;
  prevNextCode &= 0x0f;

  return ( rot_enc_table[( prevNextCode & 0x0f )]);
}

Using the program above I pushed the rotary encoder pushbutton to generate the text "Next Detent" before turning the encoder to the next detent position. This allows you to see all the codes that were generated during one position change.

You can see that some of the rotations caused a lot of codes but the codes were only able to go back by one state before returning to the correct one. More importantly you can see that the final 2 codes are always the same matching the 2 last nibbles for complete rotation sequences : D42B and E817.

For the "bad" rotary encoder the following output was generated:

Poor Quality Rotary Encoder

KY-040 Quality test:
D 4 2 8 2 B eleven
Next Detent
D 4 2 8 2 B eleven
Next Detent
D 4 1 4 2 B eleven
Next Detent
E 8 2 8 2 8 2 8 1 4 1 7 seven
D 7 seven
Next Detent
E B eleven
E 8 2 8 2 8 1 7 seven
Next Detent
E 8 2 8 2 8 2 8 1 4 1 7 seven
Next Detent
E B eleven
E 8 2 8 2 8 1 4 1 4 1 7 seven
Next Detent
E 8 1 4 1 4 1 4 1 4 1 4 1 7 seven
Next Detent
E 8 1 4 1 7 seven
Next Detent
E 8 1 7 seven
Next Detent
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E 8 2 8 2 8 1 4 1 7 seven
Next Detent

For the good quality encoder the following output was generated:

Rotary Encoder test better quality encoder.

KY-040 Quality test:
E 8 1 7 seven
Next Detent
E 8 1 7 seven
Next Detent
E 8 1 7 seven
Next Detent
E 8 1 7 seven
Next Detent
E 8 1 7 seven
E B eleven
Next Detent
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E B eleven
E 8 1 7 seven
Next Detent
D 4 2 B eleven
D 7 seven
D 7 seven
Next Detent
D 4 2 B eleven
Next Detent
D 4 2 B eleven
Next Detent

You can see that there is quite a difference between the two with the 1st one generating far more code outputs (due to switch bouncing). The actual codes that indicate a single detent-to-detent movement are E817 and D42B which are the same values (shown in the discussion on valid binary codes above) for the "valid" prevstate,nextstate coding.

You can see that there is a lot of bouncing while the switch is between detents but not when it reaches the end. All the code outputs start with the correct code either E or D then bounce around a lot and then end with the final two codes.

Operation of Improved Table Decode Code

The code below looks for the last two states to indicate a valid rotary code output ( 0x2b and 0x17 ). This gives a double processing debounce - the first being a "valid" output and the second being a "valid rotation". This works very well and even allows the rotary encoder to return to its original position (rotate CW 20 positions then rotate CCW 20 positions) with no missing codes - even for an extremely noisy rotary encoder.

You may be able to use the full 16 bit hex code for less noisy ones (or high quality ones). Your results may vary.

Note that this is with direct connection to the encoder - no debounce resistors or capacitors (only the 10k resistor pull ups on the breakout board).

Code For Improved Table Decode

// Robust Rotary encoder reading
//
// Copyright John Main - best-microcontroller-projects.com
//
#define CLK 2
#define DATA 7

void setup() {
  pinMode(CLK, INPUT);
  pinMode(CLK, INPUT_PULLUP);
  pinMode(DATA, INPUT);
  pinMode(DATA, INPUT_PULLUP);
  Serial.begin (115200);
  Serial.println("KY-040 Start:");
}

static uint8_t prevNextCode = 0;
static uint16_t store=0;

void loop() {
static int8_t c,val;

   if( val=read_rotary() ) {
      c +=val;
      Serial.print(c);Serial.print(" ");

      if ( prevNextCode==0x0b) {
         Serial.print("eleven ");
         Serial.println(store,HEX);
      }

      if ( prevNextCode==0x07) {
         Serial.print("seven ");
         Serial.println(store,HEX);
      }
   }
}

// A vald CW or  CCW move returns 1, invalid returns 0.
int8_t read_rotary() {
  static int8_t rot_enc_table[] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0};

  prevNextCode <<= 2;
  if (digitalRead(DATA)) prevNextCode |= 0x02;
  if (digitalRead(CLK)) prevNextCode |= 0x01;
  prevNextCode &= 0x0f;

   // If valid then store as 16 bit data.
   if  (rot_enc_table[prevNextCode] ) {
      store <<= 4;
      store |= prevNextCode;
      //if (store==0xd42b) return 1;
      //if (store==0xe817) return -1;
      if ((store&0xff)==0x2b) return -1;
      if ((store&0xff)==0x17) return 1;
   }
   return 0;
}


----


Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.



Privacy Policy | Contact | About Me

Site Map | Terms of Use


5894-118