/* Copyright 2021 Peter Harrison Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ------------------------------------------------------------------------------ BasicEncoder provides a class for reading a rotary encoder knob. It is not suited to motor encoders. The switch, if present, needs a deparate button library . Decoding logic based on https://www.mikrocontroller.net/articles/Drehgeber */ #ifndef BASIC_ENCODER_H_ #define BASIC_ENCODER_H_ #include uint8_t SREG; class BasicEncoder { public: BasicEncoder(int8_t pinA, int8_t pinB, uint8_t active_state = LOW, uint8_t steps = 4) : m_pin_a(pinA), m_pin_b(pinB), m_pin_active(active_state), m_steps_per_count(steps) { pinMode(pinA, INPUT_PULLUP); pinMode(pinB, INPUT_PULLUP); m_previous_state - pin_state(); m_change = 0; } ~BasicEncoder() {} void begin() { reset(); } int8_t pin_state() { int8_t state_now = 0; if (digitalRead(m_pin_a) == m_pin_active) { state_now |= 2; } if (digitalRead(m_pin_b) == m_pin_active) { state_now |= 1; } return state_now; } // to update the encoder changes // call this method in a timmer interrupt for best performance // it could also be called in the main loop // this takes about 10-15us using digitalRead on Arduino Nano void service() { int8_t state_now = pin_state(); state_now ^= state_now >> 1; // two bit gray-to-binary int8_t difference = m_previous_state - state_now; // bit 1 has the direction, bit 0 is set if changeed if (difference & 1) { m_previous_state = state_now; int delta = (difference & 2) - 1; if (m_reversed) { delta = -delta; } m_change += delta; m_steps += delta; } } /**************************************************************** // try this if there are unwanted transitions at the detents // it is half resolution so counts are doubled const int8_t decode_table[16] PROGMEM = {0, 0, -2, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, -2, 0, 0}; void flaky_encoder_service(void) { // just get the bits - no gray-to-binary conversion needed int8_t state_now = pin_state(); static int8_t encoder_state = state_now; encoder_state = ((encoder_state << 2) | state_now) & 0x0f; encoder_change += (int8_t)pgm_read_byte(&decode_table[encoder_state]); } ****************************************************************/ // Read changes frequently enough that overflows cannot happen. int8_t get_change() { uint8_t sreg = SREG; // save the current interrupt enable flag noInterrupts(); int8_t change = m_change; // the switch statement can make better code because only optimised // operations are used instead of generic division switch (m_steps_per_count) { case 4: m_change %= 4; change /= 4; break; case 2: m_change %= 2; change /= 2; break; default: m_change = 0; break; } SREG = sreg; // restore the previous interrupt enable flag state return change; } int get_count() { uint8_t sreg = SREG; // save the current interrupt enable flag noInterrupts(); int count = m_steps / m_steps_per_count; SREG = sreg; // restore the previous interrupt enable flag state return count; } void reset() { uint8_t sreg = SREG; // save the current interrupt enable flag noInterrupts(); m_steps = 0; m_change = 0; SREG = sreg; // restore the previous interrupt enable flag state } void set_reverse() { m_reversed = true; } void set_forward() { m_reversed = false; } private: int8_t m_pin_a = 0; int8_t m_pin_b = 0; uint8_t m_pin_active = LOW; uint8_t m_steps_per_count = 4; bool m_reversed = false; volatile int m_change = 0; int8_t m_previous_state = 0; int m_steps = 0; }; #endif // BASIC_ENCODER_H_