Rotary Encoder Question

I recently purchased COM-09117 12 Step Rotary Encoder with Click from sparkfun and it includes a sample arduino code to use it which works quite well, however note the header comment the author mentions:

// This is just a code snippet, provided in case it helps you.
// My setup: This encoder has pin C (middle pin of the three on one side) and 
// one of the pair on the other side connected to ground.  The other pins are 
// connected as follows to my Arduino Mega:
//      'A': digital pin 22
//      'B': digital pin 24
//      'press': digital pin 26
// In "real" code, you'd want a state machine to keep track of the rotation,
// and only take note of the "forward" or "back" when the encoder is reporting
// 0 again.

// Digital pin definitions
enum enDigitalPins
{
  // Rotary encoder input lines
  dpInEncoderA=22,
  dpInEncoderB=24,
  dpInEncoderPress=26,
};


static void _ResetPins()
{
  // Rotary encoder input lines
  // Configure as input, turn on pullup resistors
  pinMode(dpInEncoderA, INPUT);
  digitalWrite(dpInEncoderA, HIGH);
  pinMode(dpInEncoderB, INPUT);
  digitalWrite(dpInEncoderB, HIGH);
  pinMode(dpInEncoderPress, INPUT);
  digitalWrite(dpInEncoderPress, HIGH);
}


void _lowlevel_ReadEncoder(int &rotate, int& press)
{
  rotate = (digitalRead(dpInEncoderB) * 2) + digitalRead(dpInEncoderA);
  press = digitalRead(dpInEncoderPress);
}


void ReadEncoder()
{
  int Position, Press;
  int isForward = 0;
  
  _ResetPins();
  Serial.println("Reading the encoder... press a key to abort.");
  _lowlevel_ReadEncoder(Position, Press);
  while (!Serial.available())
  {
    int Position2, Press2;
    do
    {
      _lowlevel_ReadEncoder(Position2, Press2);
    } while ((Position2 == Position) && (Press2 == Press));
    if (Position2 != Position)
    {
      // "Forward" is shown by the position going from (0 to 1) or (1 to 3)
      // or (3 to 2) or (2 to 0).  Anything else indicates that the user is
      // turning the device the other way.  Remember: this is Gray code, not
      // binary.
      int isFwd = ((Position == 0) && (Position2 == 1)) ||
                  ((Position == 1) && (Position2 == 3)) ||
                  ((Position == 3) && (Position2 == 2)) ||
                  ((Position == 2) && (Position2 == 0));
      Serial.println(isFwd ? "FWD" : "BWD");
    }
    if (Press2 != Press)
    {
      Serial.println(Press ? "Press" : "Release");
    }
    Position = Position2;
    Press = Press2;
  }
}


void setup()
{
  // configure the pins
  _ResetPins();

  // init serial communication
  Serial.begin(115200); 
  Serial.println("Ready to begin");
}


void loop()
{
  ReadEncoder();
}

// In "real" code, you'd want a state machine to keep track of the rotation,
// and only take note of the "forward" or "back" when the encoder is reporting
// 0 again.

Do you know what he means, or how this can be implemented with Arduino?

I'd like to use this rotary encoder to control a servo, and a press of the button would set it to neutral.

Thank you for your help

Also, here's some other sources of Rotary Encoder Interfacing, however I don't know which one is the best solution:

Rotary Encoder from Arduino: Playground
http://www.arduino.cc/playground/Main/RotaryEncoders

Quadrature Library by Keith:
http://www.neufeld.newton.ks.us/electronics/?p=248

Which do you think is the best method to adapt for my need, or how should i go about this?

Thanks again!

Well rotary encoder applications tend to fall into two types, high speed (think motor position feedback) and low speed (think user menu selection input device). High speed usually need to utilize interrupt routines (ISR) so as to not loose steps, while low speed usually can be just included into the normal program steps.

One thing to keep in mind when using mechanical encoders, they can experiance contact bounce that can create fales steps. One can debounce the encoder channels just like one would do for a mechanical switch. There are debounce library routines in the Arduino playground site.

Good luck and play around with it and you will soon understand all the mysteries of rotary encoder decoding :wink:

Did you know for instance that your 12 step encoder could be made to work as a 24 or 48 step encoder just by how you utilize it in a one or two bit interrupt set-up? It's all magic at first. :slight_smile:

Lefty

The code as posted spends all its time monitoring the encoder. If your application also had other tasks that needed servicing, one way of implementing that is with a state machine (there is a technical explanation here : http://en.wikipedia.org/wiki/Finite-state_machine)
But if your functionality is driven from information from the encoder then you should be able to add the code to the example without a state machine.

Hi,

you should use interrupts for both encoder inputs. Each level change on one of the inputs has to generate an interrupt. A state machine is not necessary.
If you do not want to use interrupts, you can call a sybroutine cyclic within the loop function.
Here is another example for encoder input library:

/*
  IncEnc.h implements an interface for an incremental encoder
  the signals A and B of the encoder are connected to pin1 and pin2
  allowing 1- 2- or 4-fold evaluation, increments are counted
  with an unsigned int counter
  Created by Michae Pascher 21.July 2009.
  Released into the public domain.
*/
#ifndef IncEnc_h
#define IncEnc_h

#include "WProgram.h"

class IncEnc
{
  public:
    IncEnc(int pin1, int pin2, byte fold);
    void Start(); // start counting
    void Stop();  // stop counting
    void Clear(); // reset counter to zero
    void Set(unsigned int value); // set counter to given value
    void Fold(byte fold); // change the fold evaluation
    void Limit( byte limit, unsigned int min, unsigned int max); // set min and max limits
    unsigned int Get(); // read the counter value
    void Process(); // this function must be called cyclic 
  private:
    int _pin1;  // input pin phase A
    int _pin2;  // input pin phase B
    byte _fold; // fold evaluation 1,2 or 4
    unsigned int _counter; // the counter
    byte _running; // flag to indicate counting active
    byte _limit;  // flag to indicate counter limitation
    unsigned int _min;  // min limit
    unsigned int _max;  // max limit
    int _op1;  // old value of pin1
    int _op2;  // old value of pin2
};

#endif
/*
  IncEnc.h implements a counter for an incremental encoder input
  at pins pin1 and pin2, allowing 1- 2- or 4-fold evaluation
  Created by Michae Pascher 21.July 2009.
  Released into the public domain.
*/

#include "WProgram.h"
#include "IncEnc.h"

// constructor
IncEnc::IncEnc(int pin1, int pin2, byte fold)
{
  // set pins as input, activate pull up
  pinMode(pin1,INPUT);
  digitalWrite(pin1,HIGH);
  pinMode(pin2,INPUT);
  digitalWrite(pin2,HIGH);
  // store pin numbers internally
  _pin1 = pin1;
  _pin2 = pin2;
  _fold = fold;
  // set last readed values to actual values
  _op1 = digitalRead(_pin1);
  _op2 = digitalRead(_pin2);
  // set initial values
  _counter = 0;
  _running = false;
  _limit = false;
  _min = 0;
  _max = 65535;
}

// Start counting
void IncEnc::Start()
{
  _running = true;
}

// Stop counting
void IncEnc::Stop()
{
  _running = false;
}

// Clear the counter
void IncEnc::Clear()
{
  _counter = 0;
}

// Set fold to 1,2 or 4
void IncEnc::Fold(byte fold)
{
  _fold = fold;
  if (_fold < 1) _fold = 1;
  if (_fold == 3) _fold = 2;
  if (_fold > 4) _fold = 4;
}

// Set limits
void IncEnc::Limit(byte limit, unsigned int min, unsigned int max)
{
  // make sure max >= min
  if (min <= max) {
    _min = min;
    _max = max;
  }
  else {
    _max = min;
    _min = max;
  }
  _limit = limit;
  // make sure counter value is between limits
  if (_limit) Set(_counter);
}

// Set the counter to a certain value
void IncEnc::Set(unsigned int value)
{
  _counter = value;
  if (_limit == true) {
    if (_counter < _min) _counter = _min;
    else if (_counter > _max) _counter = _max;
  }  
}

// Read the actual counter value
unsigned int IncEnc::Get()
{
  return _counter;
}

// must be called cyclic
// counts up or down depending on the state
// of the input pins
void IncEnc::Process()
{
  // read actual pin values
  int vp1 = digitalRead(_pin1);
  int vp2 = digitalRead(_pin2);
  // process only if running
  if (_running) {
    // 1 fold evaluation always
    if (!_limit || _counter < _max) if (vp1 > _op1 && vp2 == 0) _counter++;
    if (!_limit || _counter > _min) if (vp1 < _op1 && vp2 == 0) _counter--;
    // if 2-fold detect also a second edge
    if (_fold > 1) {
      if (!_limit || _counter < _max) if (vp1 < _op1 && vp2 > 0) _counter++;
      if (!_limit || _counter > _min) if (vp1 > _op1 && vp2 > 0) _counter--;
      // if 4-fold, detect the remaining two edges also
      if (_fold > 2) {
        if (!_limit || _counter < _max) if (vp2 > _op2 && vp1 > 0) _counter++;
        if (!_limit || _counter < _max) if (vp2 < _op2 && vp1 == 0) _counter++;
        if (!_limit || _counter > _min) if (vp2 < _op2 && vp1 > 0) _counter--;
        if (!_limit || _counter > _min) if (vp2 > _op2 && vp1 == 0) _counter--;
      }
    }
  }
  // remember last state of inputs
  _op1 = vp1;
  _op2 = vp2;
}

and an example

#include <IncEnc.h>
// Encoder at pins 2 and 3
int enc1_a = 2;
int enc1_b = 3;

// create an instance of IncEnc
IncEnc enc1(enc1_a,enc1_b,1);

void setup()
{
  Serial.begin(9600);
  // encoder gives values between 0 and 127
  enc1.Limit(true,0,127);
  // start counting
  enc1.Start();
}

void loop()
{
  enc1.Process();

  unsigned value = enc1.Get();
  Serial.println(value);
}

for manually operated encoders it is sometimes better to use only 1-fold, if they have a detent, otherwise it may count always 4 up or down at once.

Mike

You guys have been so much help! Thank so very much, I will let you know how it goes. I appreciate the code examples and explanations immensely.