Topics

► Games

► Sound & Music

► Clocks

► GPS

► Tools

► Tutorials

By processor

► ATtiny85

► ATtiny84

► ATtiny841

► ATtiny2313

► ATtiny861

► ATmega328

► ATmega1284

About me

  • About me

Feeds

RSS feed

Simple Rotary Encoder Interface

3rd October 2015

Rotary encoders are a nice alternative to a potentiometer in a project, because they're multi-turn, and you can specify in the software how you want the amount you rotate the knob to vary the parameter that you're controlling. They work by providing two switches that are activated in sequence when you rotate the knob. By checking the state of both switches your program can tell both how far the knob has been rotated, and in which direction:

RotaryEncoder.jpg

A typical rotary encoder - this one is from Adafruit.

This article describes a simple demo project based on an ATtiny85, using the rotary encoder to adjust the brightness of an LED:

RotaryEncoderDemo.jpg

Demo ATtiny85 project using a rotary encoder to adjust the brightness of an LED.

For this example I used a basic rotary encoder available from Adafruit [1], or from Makersify in the UK [2], which gives 24 pulses per rotation. I also tested it with Sparkfun's rotary encoder [3], which gives 12 pulses per rotation; it's available in the UK from Proto-PIC [4] or HobbyTronics [5]. Both of these rotary encoders also incorporate a switch that's activated when you press the knob. They can be persuaded to fit in a breadboard, but you may have to cut off the metal tabs on each side of the body.

Converting Gray code

A typical rotary encoder has three connections, usually labelled A, B, and C. It works by outputting a Gray code sequence of bits on outputs A and B, with C as the common connection.

The Gray code ensures that only one bit changes at a time:

RotaryCodes1.gif

To convert the Gray code to binary, shift the two-bit code right and exclusive-OR it with the original code:

RotaryCodes2.gif

To detect which way the encoder is rotating, subtract the current binary code from the previous binary code. If the encoder has been rotated clockwise, the last two bits of the result are always 01:

RotaryCodes3.gif

If the encoder has been rotated anticlockwise, the last two bits of the result are always 11:

RotaryCodes4.gif

Here's the code to do these steps. It assumes that the Gray code is in the bottom two bits of Gray, and that the variable Laststate stores the last state in binary:

static int Laststate;
int State = (Gray>>1) ^ Gray;            // Convert from Gray code to binary
int Value = ((Laststate-State) & 3) - 2; // Gives -1 or +1
Laststate=State;

This sets Value to -1 or 1 depending on whether the rotary encoder was rotated one step anticlockwise or clockwise.

Circuit

Here's the circuit of the demo rotary encoder interface. It allows you to vary the brightness of an LED by rotating the encoder. The push switch on the encoder switches the LED between its current brightness and off:

RotaryEncoder.gif

Demo ATtiny85 project uses a rotary encoder to adjust the brightness of an LED.

The rotary encoder outputs are connected to PB1 and PB2, and the push button is connected to PB3.

The program

The best way to read the rotary encoder is via an interrupt service routine, as this will ensure that you don't miss changes even when your main program is busy doing something else. The interrupt service routine can also check the push-button switch. In fact, a single interrupt-service routine could handle two or more encoders, for applications where you're controlling several things with different encoders.

First, pin change interrupts are set up on each of the rotary encoder inputs, so the interrupt service routine will be called whenever any of the inputs change:

void setup() {
  pinMode(LED, OUTPUT);
  pinMode(EncoderA, INPUT_PULLUP);
  pinMode(EncoderB, INPUT_PULLUP);
  pinMode(EncoderSwitch, INPUT_PULLUP);
  // Configure pin change interrupts on PB1, PB2, and PB3
  PCMSK |= 1<<PCINT1 | 1<<PCINT2 | 1<<PCINT3;
  GIMSK = 1<<PCIE;                // Enable pin change interrupts
  GIFR = 1<<PCIF;                 // Clear pin change interrupt flag.
}

Here's the full definition of the interrupt service routine, which also handles an interrupt from the push switch, on PB3:

ISR (PCINT0_vect) {
  static int Laststate;
  int Gray = (PINB & 0x06)>>1;               // Read PB1 and PB2
  int State = (Gray>>1) ^ Gray;              // Convert from Gray code to binary
  if (State != Laststate) {
    int Value = ((Laststate-State) & 3) - 2; // Gives -1 or +1
    ChangeValue(Value);
    Laststate=State;
  } else ChangeSwitch((PINB & 0x08)>>3);     // Gives 0 or 1
  GIFR = 1<<PCIF;                            // Clear pin change interrupt flag.
}

This calls ChangeValue() when the encoder is rotated, or ChangeSwitch() when the encoder switch is pushed or released.

In my demo application ChangeValue() simply changes the brightness of an LED on PB0:

void ChangeValue (int Change) {
  Brightness = max(min(Brightness+Change, 255), 0);
  analogWrite(0, Brightness);
}

ChangeSwitch() switches the LED on at its current brightness, or off:

void ChangeSwitch (int On) {
  analogWrite(0, Brightness*On);
}

I compiled the program using the Arduino-Tiny core extension to the Arduino IDE [6]. Select the ATtiny85 @ 1 MHz (internal oscillator; BOD disabled) option on the Board submenu; this is the default configuration of the ATtiny85 fuses. I uploaded the program using Sparkfun's Tiny AVR Programmer [7].

Here's the whole Rotary Encoder Demo program: Rotary Encoder Demo Program.


  1. ^ Rotary Encoder + Extras on Adafruit
  2. ^ Adafruit Rotary Encoder + Extras on Makersify
  3. ^ Rotary Encoder on Sparkfun.
  4. ^ Rotary Encoder on Proto-PIC.
  5. ^ Rotary Encoder on HobbyTronics.
  6. ^ Arduino-Tiny core on Google Code.
  7. ^ Tiny AVR Programmer on Sparkfun.

blog comments powered by Disqus