Incremental rotary encoders (Part 3)
Part 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
The very first rotary encoder that I used had no integrated push button, which is a pity for designing human interfaces. I ordered a couple of ALPS encoders which integrate this feature. Note that the ALPS encoders (at least the ones that I received) generate 4 state changes during one rotational step.
Then I had to revise my code in order to interpret the push button state. I left apart the -1, 0, 1 returned values from the early design, and I used constants instead. The interpretation of the interval between two counts has been moved in the calling procedure (enc_test()).
The following code works great, as long as the delay between two calls is not too long (say less than 5 ms). So that care shall be taken while integrating this procedures in complex code.
/* Rotary encoder read example --- --- --- --- --- --- | | | | | | A | | | | | | - --- --- - - --- --- -- --- --- --- --- --- --- | | | | | B | | | | | | --- --- --- --- --- --- Turn Left Turn right A 1 1 0 0 1 A 1 1 1 0 1 B 1 0 0 1 1 B 0 1 0 0 0 */ #define ENC_PIN_BUTTON PINB2 #define ENC_PIN_A PINB3 #define ENC_PIN_B PINB4 #define ENC_STT_IDLE 0x00 #define ENC_STT_CW 0x01 #define ENC_STT_CCW 0x02 #define ENC_STT_BTN_UP 0x00 #define ENC_STT_BTN_DOWN 0x04 #define LED_PIN PINB5 byte cwRotorState[4] = {B10, B00, B11, B01}; byte ccwRotorState[4] = {B01, B11, B00, B10}; byte pulsesPerStep = 4; // This value may change according to encoders specifications unsigned int samplingTime = 100; // In us void setup() { // Setup encoder pins as inputs DDRB &= ~(1 << ENC_PIN_B); DDRB &= ~(1 << ENC_PIN_A); DDRB &= ~(1 << ENC_PIN_BUTTON); // Bias pins PORTB |= (1 << ENC_PIN_B); PORTB |= (1 << ENC_PIN_A); PORTB |= (1 << ENC_PIN_BUTTON); blinkLed(5, 200); Serial.begin (115200); Serial.println("Ready"); } void loop() { delay(1); enc_test(); } void enc_test(void) { static int counts = 0; static long tim_lastRead = 0; long tim_now = millis(); byte encoderRead = enc_read(); if (encoderRead) { int multiplier = 1; int interval = (tim_now - tim_lastRead); if (interval < 20) multiplier = (200 / interval); tim_lastRead = tim_now; // Record last read time if (encoderRead & ENC_STT_BTN_DOWN) counts = 0; else if (encoderRead & ENC_STT_CW) counts += multiplier; else if (encoderRead & ENC_STT_CCW) counts -= multiplier; Serial.print(counts, DEC); Serial.println(); } } void blinkLed(int nbrBlinks, int intervals){ // Blink status led // Set led pin as output DDRB |= (1 << LED_PIN); for (int i = 0; i < (nbrBlinks * 2); i++) { PORTB ^= (1 << LED_PIN); // toggle status led delay (intervals); } } byte state(void) { return(((PINB >> ENC_PIN_B) & 0x01) | (((PINB >> ENC_PIN_A) & 0x01) << 1) | (((~PINB >> ENC_PIN_BUTTON) & 0x01) << 2)); } byte enc_read(void) { // returns change in encoder state (-1: ccw, 0: no change, 1: cw) byte result = 0; static byte prevRotorState = 0; static byte prevButtonState = 0; static int bufferedCounts = 0; byte startState = state(); // Get current state delayMicroseconds(samplingTime); // Wait safety bounce time byte stopState = state(); // Get current state if (startState == stopState) { // Check if the state was stable // Interpret button state byte buttonState = stopState & B00000100; if (buttonState != prevButtonState) { // Check if button state has changed if (buttonState) result |= ENC_STT_BTN_DOWN; prevButtonState = buttonState; } // Interpret rotor state byte rotorState = stopState & B00000011; // That's the A and B pin state if (rotorState != prevRotorState) { // Check if rotor state has changed if (rotorState == cwRotorState[prevRotorState]) bufferedCounts++; else if (rotorState == ccwRotorState[prevRotorState]) bufferedCounts--; else bufferedCounts = 0; if (abs(bufferedCounts) == pulsesPerStep) { if (bufferedCounts > 0) result |= ENC_STT_CW; if (bufferedCounts < 0) result |= ENC_STT_CCW; bufferedCounts = 0; } prevRotorState = rotorState; // Record state for next pulse interpretation } } return(result); }