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 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;
}