Skip to content

Instantly share code, notes, and snippets.

@tfry-git
Last active November 6, 2023 06:34
Show Gist options
  • Save tfry-git/75b715d0fef2e77960bab3050dbc0d2b to your computer and use it in GitHub Desktop.
Save tfry-git/75b715d0fef2e77960bab3050dbc0d2b to your computer and use it in GitHub Desktop.
Reliable, portable, and efficient reading form a cheap rotary encoder, without hardware debouncing.
/* Reading from a rotary encoder, with software debouncing.
* Works even on cheap quadrature encoders without any hardware debouncing, _reliably_. */
#define ENCODER_PIN_A PA1 // Input pins. NOTE That these _must_ support intterupts, _and_ interrupts must not interfere with other pins
#define ENCODER_PIN_B PA2 // // (e.g. on STM32F103C8T6, PAx will interfere with PBx)
#define BOUNCE_TIMEOUT 2 // ms
#define USER_INPUT_HIGHER_RES 0 // You probably want this: Gives lower granularity at higher values
volatile uint16_t pin_a_change_time;
volatile uint16_t pin_b_change_time;
volatile bool encoder_change_pending;
uint16_t last_handled_change_time;
int8_t encoder_micro_tick;
void encoderTickA () {
// record that _something_ has happened with the encoder...
encoder_change_pending = true;
// ... and when - so we can discard bounces
pin_a_change_time = millis ();
}
void encoderTickB () {
encoder_change_pending = true;
pin_b_change_time = millis ();
}
// Taken from https://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino/ by Oleg Mazurov
// (modified to be more portable, and to add debouncing)
// call this frequently to make sure to catch all encoder changes. Call read_encoder() to read number of
// ticks since last read.
void update_encoder () {
// unfortunately, trying to keep track of the pin state in the two encoderTickX()-functions (without reading the pins, manually)
// does not work, reliably. So we do need to do digitalRead() on the pins, here. _But_ if we know, no pin changed since the last iteration,
// we can simply skip that.
if (!encoder_change_pending) return;
encoder_change_pending = false;
uint16_t nowt = millis ();
if (nowt < last_handled_change_time) { // handle timer overflow
pin_a_change_time = pin_b_change_time = 0;
}
last_handled_change_time = nowt;
uint8_t pin_a_val = 0;
if (last_handled_change_time - pin_a_change_time < BOUNCE_TIMEOUT) {
// if the pin level has been fluctuating, recently, we always assume the contact is closed (corresponding to 0 reading!)...
// ... but we should make sure to revisit the pin after the bounce timeout
encoder_change_pending = true;
} else {
// Note that we don't shy away from "expensive but portable" digitalRead(), here:
// - We'll only get to this part of the code, if something _has_ changed, and
// - We'll not get here more often than every BOUNCE_TIMEOUT milliseconds
// But replace with a faster read, if you prefer.
pin_a_val = digitalRead (ENCODER_PIN_A);
}
uint8_t pin_b_val = 0;
if (last_handled_change_time - pin_b_change_time < BOUNCE_TIMEOUT) {
encoder_change_pending = true;
} else {
pin_b_val = digitalRead (ENCODER_PIN_B) << 1;
}
// table of valid (1/-1) or invalid (0) transitions from previous state to next state.
// where previous state is the two higher bits of the array position, and next state is the two lower bits
int8_t enc_states[16] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
static uint8_t old_AB = 0;
old_AB <<= 2; // previous state as high bits
old_AB |= pin_a_val | pin_b_val;
encoder_micro_tick += enc_states[(old_AB & 0x0f)];
}
int8_t read_encoder () {
int8_t ret = encoder_micro_tick / 4;
if (ret != 0) {
encoder_micro_tick = 0;
// TODO: What the??? Somehow the version below will cause audible clicks, even while the encoder is not turning at all.
// In practice, the line above, while not quite correct, will not be noticeably different.
// encoder_micro_tick -= ret * 4;
}
return ret;
}
void setup_encoder() {
pinMode (ENCODER_PIN_A, INPUT_PULLUP);
pinMode (ENCODER_PIN_B, INPUT_PULLUP);
attachInterrupt (ENCODER_PIN_A, encoderTickA, CHANGE);
attachInterrupt (ENCODER_PIN_B, encoderTickB, CHANGE);
pin_a_change_time = pin_b_change_time = last_handled_change_time = millis ();
encoder_micro_tick = 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment