Removing the Bounce from a Rotary Encoder in C++

This code was developed from an idea by Oleg Mazurov and is presented here as a complete class that can be added as a tab in an Arduino sketch. The code will detect forward and reverse movement and will detect if the encoder is turned more quickly.

Hopefully the code is self explanatory with the possible exception of anti-bounce approach taken. Oleg Mazurov has described his work at www.circuitsathome.com. The scaleReduction parameter is a means to control the sensitivity of the rotaion, i.e. a setting of 8 would require 8 clockwise rotation changes before output was reported as a single clockwise rotation.

An example of using this code is shown below with the header and code files shown further below. This particular example was taken from a larger project and used a raw pointer to access the class, there is no requirement to use pointers.

/* ===========================================================================
* Rotary encoder increments for slow/fast turning
* =========================================================================*/
const uint8_t SLOW_INCREMENT = 10;
const uint8_t FAST_INCREMENT = 100;

RotaryEncoder * rotaryEncoder;
double rotValue = 0;

void setup()
{
   rotaryEncoder = new RotaryEncoder(
        ROTARY_ENCODER_SW_PIN,
        ROTARY_ENCODER_CLK_PIN,
        ROTARY_ENCODER_DT_PIN);
   rotaryEncoder->scaleReduction = 2;
}

void loop()
{

  RotaryEncoderState rData = rotaryEncoder->getState();

  int adjustment = SLOW_INCREMENT;

  if (rData.isFast) {
      adjustment = FAST_INCREMENT;
  }

  if (rData.rotation == RotaryEncoderRotationClockwise) {
     rotValue += adjustment;
  }
  else if (rData.rotation == RotaryEncoderRotationAntiClockwise) {
     rotValue -= adjustment;
  }

  if (rData.rotation != RotaryEncoderRotationStationary) {
    //... 
    //
  }
}

RotaryEncoder.h

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <arduino.h>
#include <stdint.h>

/*
 * Encoder Rotation
 */
enum RotaryEncoderRotation {
  RotaryEncoderRotationStationary,  
  RotaryEncoderRotationClockwise,
  RotaryEncoderRotationAntiClockwise
};

/*
 * Encoder State
 */
class RotaryEncoderState {

  public:
    RotaryEncoderRotation rotation;
    bool buttonPressed;
    bool isFast;
};

/*
 * Encoder
 */
class RotaryEncoder {


  public:
    RotaryEncoder (int swPin, int clkPin, int dtPin);
    RotaryEncoderState getState();
    int scaleReduction; //  e.g. a setting of 8 would require 8 clockwise rotation      
                        //  changes before output was reported as a single clockwise

  private:
    int _swPin;
    int _clkPin;
    int _dtPin;
    uint8_t history = 0;
    int clockwiseScaleCounter = 0;
    int antiClockwiseScaleCounter = 0;
    unsigned long _timeStamp = 0;
    RotaryEncoderState _previousState;
    bool isBounce();
    bool isFast();
};

RotaryEncoder.cpp

#include "RotaryEncoder.h"

const byte ENC_STATES[] PROGMEM = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
const int BOUNCE_MICROS = 5000;
const long SLOW_MICROS = 80000;

RotaryEncoder::RotaryEncoder(int swPin, int clkPin, int dtPin) {

  //store the pin details
  _swPin = swPin;
  _clkPin = clkPin;
  _dtPin = dtPin;

  //configure the pins
  pinMode(_swPin, INPUT);
  digitalWrite(_swPin, HIGH);

  pinMode(_clkPin, INPUT);
  digitalWrite(_clkPin, HIGH);

  pinMode(_dtPin, INPUT);
  digitalWrite(_dtPin, HIGH);

  _timeStamp = micros();
}

RotaryEncoderState RotaryEncoder::getState()
{
  RotaryEncoderState result;

  //read the status of clk (A) and dt (B) pins
  uint8_t clk = digitalRead(_clkPin);
  uint8_t dt = digitalRead(_dtPin);

  //shift the dt over by one and combine dt and  clk
  clk <<= 1;
  uint8_t clkDt = dt | clk;

  //make room in the history for the new 2 bit combined state
  history <<= 2;

  //add the new state to the two empty low bits
  history |= ( clkDt & 0x03 );

  //based on the low four bits of the history,
  //look up the new status
  //int rotation = encStates[(history & 0x0f)];

  //this is the PROGMEM version of above but is a byte value not int
  int rotation = pgm_read_byte_near(ENC_STATES + (history & 0x0f));

  //default to Stationary
  result.rotation = RotaryEncoderRotationStationary;

  //how long has it been since last rotation
  //unsigned long elapsedTime = micros() - _timeStamp;

  //if clockwise
  if ((int8_t)rotation > 0 && !isBounce()) {

    //factor in the scale reduction
    //works like a reduction drive in a variable capacitor
    clockwiseScaleCounter++;
    antiClockwiseScaleCounter = 0;

    if (clockwiseScaleCounter > scaleReduction) {
      result.rotation = RotaryEncoderRotationClockwise;
      result.isFast = isFast();
      clockwiseScaleCounter = 0;
    }

  }

  //if anti-clockwise unsigned (byte) so 255 is -1
  if ((int8_t)rotation < 0 && !isBounce()) {

    antiClockwiseScaleCounter++;
    clockwiseScaleCounter = 0;

    //factor in the scale reduction
    //works like a reduction drive in a variable capacitor
    if (antiClockwiseScaleCounter > scaleReduction) {
      result.rotation = RotaryEncoderRotationAntiClockwise;
      result.isFast = isFast();
      antiClockwiseScaleCounter = 0;
    }

  }

  //check for a button press, note that this may not work if this function
  //is called from a CLK or DT interrupt
  result.buttonPressed = !digitalRead(_swPin);

  _previousState = result;  
  return  result;

}

bool RotaryEncoder::isBounce(){
  return micros() - _timeStamp < BOUNCE_MICROS;
}

bool RotaryEncoder::isFast(){
  bool result = micros() - _timeStamp < SLOW_MICROS;  
  _timeStamp = micros();
  return result;
}

Leave a Reply

Your email address will not be published. Required fields are marked *