Reading rotary encoders as a state machine

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.

6 Likes

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.