I and others have occasionally commented in replies to questions that there is no need to debounce a rotary encoder if they are read using a state based approach. However, this has never, so far as I aware, ever been explained properly.
This tutorial provides that explanation along with sample code to demonstrate the principle. However, while the code works there are shortcomings that would need to be overcome in order to use the code or something like it in a project.
The code below has been tested on a Nano Every and sends a count to the serial monitor. The count increases if you turn the rotary encoder one way, and decreases for the other way. I have arbitrarily labelled the directions 'clockwise' and 'anticlockwise', but the actual direction depends on which way round the 2 encoder outputs are connected.
The encoder should be connected with the 2 outputs connected to digital pins 2 and 3, with the common to 0V (ground). The output is on the serial monitor, which should be set for 115200 baud.
// Input pins to connect to the encoder
const uint8_t CLK = 2;
const uint8_t DT = 3;
int16_t inputDelta = 0; // Counts up or down depending which way the encoder is turned
bool printFlag = false; // Flag to indicate that the value of inputDelta should be printed
void setup() {
Serial.begin(115200);
pinMode(CLK, INPUT_PULLUP);
pinMode(DT, INPUT_PULLUP);
}
void loop() {
readEncoder();
printDelta();
}
void readEncoder() {
static uint8_t state = 0;
bool CLKstate = digitalRead(CLK);
bool DTstate = digitalRead(DT);
switch (state) {
case 0: // Idle state, encoder not turning
if (!CLKstate){ // Turn clockwise and CLK goes low first
state = 1;
} else if (!DTstate) { // Turn anticlockwise and DT goes low first
state = 4;
}
break;
// Clockwise rotation
case 1:
if (!DTstate) { // Continue clockwise and DT will go low after CLK
state = 2;
}
break;
case 2:
if (CLKstate) { // Turn further and CLK will go high first
state = 3;
}
break;
case 3:
if (CLKstate && DTstate) { // Both CLK and DT now high as the encoder completes one step clockwise
state = 0;
++inputDelta;
printFlag = true;
}
break;
// Anticlockwise rotation
case 4: // As for clockwise but with CLK and DT reversed
if (!CLKstate) {
state = 5;
}
break;
case 5:
if (DTstate) {
state = 6;
}
break;
case 6:
if (CLKstate && DTstate) {
state = 0;
--inputDelta;
printFlag = true;
}
break;
}
}
void printDelta() {
if (printFlag) {
printFlag = false;
Serial.println(inputDelta);
}
}
While this code works reasonably well as it is with no other code present, there is a problem in that it needs to be called at least once per millisecond to stand a chance of keeping up with a rotary encoder turned quickly. If you try this code and turn the encoder too fast you will see the count jump the wrong way. I think this is because the prints take too long and stop the encoder being read often enough. The fix for this would involve either interrupts on the inputs or using one of the internal timers to call the code every millisecond. The purpose of this tutorial is to illustrate the principle, not to provide finished, usable code. I leave it to you to fix this and any other shortcomings. There are, of course, perfectly good libraries for reading encoders, but they leave the internal operation a mystery unless you dig deep into their code.
Here is the output from the encoder showing which states the code will go to as the output signal changes. This for 1 step clockwise. For anticlockwise the progression is similar through states 4, 5 and 6.
