Removing the Bounce from a Rotary Encoder in C++

From GlassTTY
Jump to: navigation, search

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 http://www.circuitsathome.com. The scaleReduction parameter is a means to control the sensitivity of the rotation, 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;
   }