Encoder Library

Dear All, here’s my Encoder library which I’m using for my own projects.

http://www.wusik.com/download/arduino/Encoder.zip

/*
 
 www.Wusik.com - Created by WilliamK @ Wusik Dot Com (c) 2010
 
*/

#ifndef ENCODER_h
#define ENCODER_h

#include <inttypes.h>
#include "HardwareSerial.h"

// 12 Step Rotary Encoder with Click //
// http://www.sparkfun.com/products/9117 //
#define EncoderPinA 20      // Rotary Encoder Left Pin //
#define EncoderPinB 19      // Rotary Encoder Right Pin //
#define EncoderPinP 21      // Rotary Encoder Click //

// ======================================================================================= //
class Encoder
{
public:
      Encoder() 
      { 
            pinMode(EncoderPinA, INPUT);
            digitalWrite(EncoderPinA, HIGH);
            pinMode(EncoderPinB, INPUT);
            digitalWrite(EncoderPinB, HIGH);
            pinMode(EncoderPinP, INPUT);
            digitalWrite(EncoderPinP, HIGH);

            Position = 0; 
            Position2 = 0; 
            Max = 127; 
            Min = 0;
            clickMultiply = 10;
      }

      void Tick(void)
      { 
            Position2 = (digitalRead(EncoderPinB) * 2) + digitalRead(EncoderPinA);;
            if (Position2 != Position)
            {
                  isFwd = ((Position == 0) && (Position2 == 1)) || ((Position == 1) && (Position2 == 3)) || 
                        ((Position == 3) && (Position2 == 2)) || ((Position == 2) && (Position2 == 0));
                  if (!digitalRead(EncoderPinP)) { if (isFwd) Pos += clickMultiply; else Pos -= clickMultiply; }
                        else { if (isFwd) Pos++; else Pos--; }
                  if (Pos < Min) Pos = Min;
                  if (Pos > Max) Pos = Max;
            }
            Position = Position2;
      }

      int getPos(void)
      {
            return (Pos/4);
      }

      void setMinMax(int _Min, int _Max) 
      { 
            Min = _Min*4; 
            Max = _Max*4; 
            if (Pos < Min) Pos = Min;
            if (Pos > Max) Pos = Max;
      }

      void setClickMultiply(int _clickMultiply)
      {
            clickMultiply = _clickMultiply;
      }

private:
      int clickMultiply;
      int Max;
      int Min;
      int Pos;
      int Position;
      int Position2;
      int isFwd;
};

#endif

You could simplify the rotation detection with this code:

const int8_t encodeSeq[] = {1, 3, 0, 2};
...

void Tick() {
    int8_t Position2 = (digitalRead(EncoderPinB) * 2) + digitalRead(EncoderPinA);
    int8_t increment = digitalRead(EncoderPinP) ? 1 : clickMultiply;

    if (encodeSeq[Position2] == Position) {
        Pos -= increment;
    }
    else if (encodeSeq[Position] == Position2) {
        Pos += increment;
    }
    Pos = constrain(Pos, Min, Max);
    Position = Position2;
}

This code has the added benefit, that it ignores jumps on the encoding wheel (like it happens on fast turning encoders sometimes) or if the wheel didn't turn (due to encoding wheel bouncing). It will increment only on movements that can be attributed a direction. In case the encoder turns the wrong way (I didn't check) switch the -= and the +=.

About the general layout of your library, you should consider creating a constructor which will accept the pin numbers for the button and the two encoderswitches, just like LiquidCrystal does. Furthermore get rid of those private variables just used in one function (eg Position2, isFwd) and make those local variables for the concerned function.

Korman

int8_t Position2 = (digitalRead(EncoderPinB) * 2) + digitalRead(EncoderPinA);

you can't read encoder like that. Reading of both pins must happen simultaneously, otherwise you'll get strange results when one of the pins changes state between first and second read. You need to read the whole port to a variable and then read pin states from this variable.

Felis, it doesn’t matter. Physically the encoders are mounted at an offset, so both chnging at the same time is unlikely. So if one changes during reading, we just read the next state at the earliest possible state or we just missed reading the next state and read the last state and the next iteration will bring the new state. The case of very fast turning encoders where you can miss states, we’re in trouble anyway, so adding this won’t make things worse.

Korman

Tomorrow I will clean up the library a bit, like I did with the C595 and C165 classes. 8-)

A question, is it easy to make this Interrupt compatible? I'm not getting good results when moving the encoder fast. And I wish to add the option to go Min and Max when moving very fast. (like most encoders do anyway)

Wk

I think you would add more to the community if you changed your APIs to conform to the Arduino namingConvention :) Something to consider.

Thanks, but where can I find information on how to do this?

Wk

conform to the Arduino namingConvention

Thanks, but where can I find information on how to do this?

Everything you need to know is right there. No special characters in the names. Each word is spelled out. The first word starts with a lower case letter. All the others start with an upper case letter. All letters other than the first letter in each word are lower case.

I guess I should be more "literal" them? ;-)

Wk

I guess I should be more "literal" them?

I guess, since I can't tell if that is a question.

Ignore that, I got what you guys said. I already updated the library and will upload a more finished set of files later on. Its turning out to be a great lib, thanks to all involved!

Wk

Korman, thanks for the new code, it works better indeed. :sunglasses:

Wk

Here we go. Now its a complete library for reading that SparkFun encoder with both Forward, Backwards and Click. (plus Click Holding) I also added Click Debounce and some other improvements.

http://arduino.wusik.com

The ZIP file has Examples too, including Interrupt based examples.

http://www.wusik.com/arduino/Libraries/Encoder/Encoder.zip

Encoder.h

/*
 
      Created by WilliamK @ Wusik Dot Com (c) 2010
      http://arduino.wusik.com

*/

#ifndef ENCODER_h
#define ENCODER_h

#include <inttypes.h>
#include "HardwareSerial.h"

const int8_t encodeSeq[] = {1, 3, 0, 2};
#define clickDebounceTime 50
#define clickHoldTime 500

// ------------------------------------------------------------------------------------------- //
class Encoder
{
public:
      Encoder(int8_t pinLeft, int8_t pinRight, int8_t pinClick);
      boolean tick(void);
      void lowLevelTick(void);
      void lowLevelClick(void);
      float getPosition(void);
      void setMinMax(float _min, float _max);
      void setPosition(float _position);
      void setRate(float _rate);
      boolean hasChanged(void);
      boolean hasClick(void);
      boolean onClickHold(void);
      void setIntegerMode(boolean mode);

private:
      int8_t encoderPinLeft;
      int8_t encoderPinRight;
      int8_t encoderPinClick;

      float prevPosition;
      float maxValue;
      float minValue;
      float currentPosition;
      float moveRate;
      int8_t tempPosition;
      int8_t tempPosition2;
      boolean isForward;
      boolean isClickedEvent;
      boolean isClicked;
      int8_t prevClickValue;
      int8_t newClickValue;
      boolean integerMode;
      unsigned long timeLastClick;
      unsigned long timeClickStarted;
};

#endif

Encoder.cpp

/*
 
      Created by WilliamK @ Wusik Dot Com (c) 2010
      http://arduino.wusik.com
 
*/

#include "WConstants.h"
#include "Encoder.h"

// ------------------------------------------------------------------------------------------- //
Encoder::Encoder(int8_t pinLeft, int8_t pinRight, int8_t pinClick)
{ 
      encoderPinLeft = pinLeft;
      encoderPinRight = pinRight;
      encoderPinClick = pinClick;

      pinMode(encoderPinLeft, INPUT);
      digitalWrite(encoderPinLeft, HIGH);
      pinMode(encoderPinRight, INPUT);
      digitalWrite(encoderPinRight, HIGH);
      pinMode(encoderPinClick, INPUT);
      digitalWrite(encoderPinClick, HIGH);

      isClicked = false;
      prevClickValue = 1;
      integerMode = false;
      isClickedEvent = false;
      currentPosition = 0;
      prevPosition = 0;
      tempPosition = 0; 
      tempPosition2 = 0; 
      maxValue = 127; 
      minValue = 0;
      moveRate = 1.0f;
      timeLastClick = millis();
      timeClickStarted = 0;
}

// ------------------------------------------------------------------------------------------- //
void Encoder::lowLevelTick(void)
{
    tempPosition2 = (digitalRead(encoderPinRight) * 2) + digitalRead(encoderPinLeft);
    if (encodeSeq[tempPosition2] == tempPosition) currentPosition -= moveRate;
          else if (encodeSeq[tempPosition] == tempPosition2) currentPosition += moveRate;
    tempPosition = tempPosition2;
}

// ------------------------------------------------------------------------------------------- //
void Encoder::lowLevelClick(void)
{
      newClickValue = digitalRead(encoderPinClick);
      if (newClickValue != prevClickValue)
      {
            if ((millis() - timeLastClick) < clickDebounceTime) return;

            if (newClickValue == 0 && !isClicked) 
            {
                  isClickedEvent = true;
                  isClicked = true;
                  timeClickStarted = millis();
            }
            else if (newClickValue == 1) isClicked = false; 
            timeLastClick = millis();
      }
      prevClickValue = newClickValue;
}

// ------------------------------------------------------------------------------------------- //
boolean Encoder::onClickHold(void)
{
      if (isClicked && (millis() - timeClickStarted) >= clickHoldTime) return true;
      return false;
}

// ------------------------------------------------------------------------------------------- //
boolean Encoder::hasClick(void)
{
      if (isClickedEvent)
      {
            isClickedEvent = false;
            return true;
      }

      return false;
}

// ------------------------------------------------------------------------------------------- //
void Encoder::setIntegerMode(boolean mode)
{
      integerMode = mode;
}

// ------------------------------------------------------------------------------------------- //
boolean Encoder::tick(void)
{ 
      lowLevelTick();
      lowLevelClick();
      
      return (hasChanged() || isClickedEvent);
}

// ------------------------------------------------------------------------------------------- //
boolean Encoder::hasChanged()
{
      currentPosition = constrain(currentPosition, minValue, maxValue);

      if (integerMode)
      {
            if ((int)getPosition() != (int)prevPosition)
            {
                  prevPosition = getPosition();
                  return true;
            }
      }
      else
      {
            if (getPosition() != prevPosition)
            {
                  prevPosition = getPosition();
                  return true;
            }
      }

      return false;
}

// ------------------------------------------------------------------------------------------- //
float Encoder::getPosition(void)
{
      if (integerMode) return (int)currentPosition;
      return currentPosition;
}

// ------------------------------------------------------------------------------------------- //
void Encoder::setMinMax(float _min, float _max) 
{ 
      minValue = _min;
      maxValue = _max;

      currentPosition = constrain(currentPosition, minValue, maxValue);
}

// ------------------------------------------------------------------------------------------- //
void Encoder::setPosition(float _position)
{
      currentPosition = _position;

      currentPosition = constrain(currentPosition, minValue, maxValue);
}

// ------------------------------------------------------------------------------------------- //
void Encoder::setRate(float _rate)
{
      moveRate = _rate;
}

I also created a “light” version of the same Encoder library. Which I’m using on my own project. :wink:

http://arduino.wusik.com

http://www.wusik.com/arduino/Libraries/Encoder2/Encoder2.zip

Encoder2.h

/*
 
      Created by WilliamK @ Wusik Dot Com (c) 2010
      http://arduino.wusik.com

*/

#ifndef ENCODER2_h
#define ENCODER2_h

#include <inttypes.h>
#include "HardwareSerial.h"

const int8_t encodeSeq[] = {1, 3, 0, 2};

// ------------------------------------------------------------------------------------------- //
class Encoder2
{
public:
      Encoder2(int8_t pinLeft, int8_t pinRight, int8_t pinClick);
      boolean tick(void);
      void lowLevelTick(void);
      int getPosition(void);
      void setMinMax(int _min, int _max);
      void setPosition(int _position);
      void setRate(float _rate);
      boolean hasChanged(void);

private:
      int8_t encoderPinLeft;
      int8_t encoderPinRight;
      int8_t encoderPinClick;

      int prevPosition;
      int maxValue;
      int minValue;
      float rate;
      float currentPosition;
      int8_t tempPosition;
      int8_t tempPosition2;
};

#endif

Encoder.cpp

/*
 
      Created by WilliamK @ Wusik Dot Com (c) 2010
      http://arduino.wusik.com
 
*/

#include "WConstants.h"
#include "Encoder2.h"

// ------------------------------------------------------------------------------------------- //
Encoder2::Encoder2(int8_t pinLeft, int8_t pinRight, int8_t pinClick)
{ 
      encoderPinLeft = pinLeft;
      encoderPinRight = pinRight;
      encoderPinClick = pinClick;

      pinMode(encoderPinLeft, INPUT);
      digitalWrite(encoderPinLeft, HIGH);
      pinMode(encoderPinRight, INPUT);
      digitalWrite(encoderPinRight, HIGH);
      pinMode(encoderPinClick, INPUT);
      digitalWrite(encoderPinClick, HIGH);

      currentPosition = 0;
      prevPosition = 0;
      tempPosition = 0; 
      tempPosition2 = 0; 
      maxValue = 127; 
      minValue = 0;
}

// ------------------------------------------------------------------------------------------- //
void Encoder2::lowLevelTick(void)
{
    tempPosition2 = (digitalRead(encoderPinRight) * 2) + digitalRead(encoderPinLeft);
      if (!digitalRead(encoderPinClick)) rate = 5.0f; else rate = 0.25f;
    if (encodeSeq[tempPosition2] == tempPosition) currentPosition -= rate;
          else if (encodeSeq[tempPosition] == tempPosition2) currentPosition += rate;
    tempPosition = tempPosition2;
}

// ------------------------------------------------------------------------------------------- //
boolean Encoder2::tick(void)
{ 
      lowLevelTick();
      
      return hasChanged();
}

// ------------------------------------------------------------------------------------------- //
boolean Encoder2::hasChanged()
{
      currentPosition = constrain(currentPosition, minValue, maxValue);

      if (getPosition() != prevPosition)
      {
            prevPosition = getPosition();
            return true;
      }

      return false;
}

// ------------------------------------------------------------------------------------------- //
int Encoder2::getPosition(void)
{
      return (int)currentPosition;
}

// ------------------------------------------------------------------------------------------- //
void Encoder2::setMinMax(int _min, int _max) 
{ 
      minValue = _min;
      maxValue = _max;

      currentPosition = constrain(currentPosition, minValue, maxValue);
}

// ------------------------------------------------------------------------------------------- //
void Encoder2::setPosition(int _position)
{
      currentPosition = _position;

      currentPosition = constrain(currentPosition, minValue, maxValue);
}