123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- /*
- * Copyright (c) 2019 David Antliff
- * Copyright 2011 Ben Buxton
- *
- * This file is part of the esp32-rotary-encoder component.
- *
- * esp32-rotary-encoder is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * esp32-rotary-encoder is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>.
- */
- /**
- * @file rotary_encoder.c
- * @brief Driver implementation for the ESP32-compatible Incremental Rotary Encoder component.
- *
- * Based on https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
- * Original header follows:
- *
- * Rotary encoder handler for arduino. v1.1
- *
- * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
- * Contact: bb@cactii.net
- *
- * A typical mechanical rotary encoder emits a two bit gray code
- * on 3 output pins. Every step in the output (often accompanied
- * by a physical 'click') generates a specific sequence of output
- * codes on the pins.
- *
- * There are 3 pins used for the rotary encoding - one common and
- * two 'bit' pins.
- *
- * The following is the typical sequence of code on the output when
- * moving from one step to the next:
- *
- * Position Bit1 Bit2
- * ----------------------
- * Step1 0 0
- * 1/4 1 0
- * 1/2 1 1
- * 3/4 0 1
- * Step2 0 0
- *
- * From this table, we can see that when moving from one 'click' to
- * the next, there are 4 changes in the output code.
- *
- * - From an initial 0 - 0, Bit1 goes high, Bit0 stays low.
- * - Then both bits are high, halfway through the step.
- * - Then Bit1 goes low, but Bit2 stays high.
- * - Finally at the end of the step, both bits return to 0.
- *
- * Detecting the direction is easy - the table simply goes in the other
- * direction (read up instead of down).
- *
- * To decode this, we use a simple state machine. Every time the output
- * code changes, it follows state, until finally a full steps worth of
- * code is received (in the correct order). At the final 0-0, it returns
- * a value indicating a step in one direction or the other.
- *
- * It's also possible to use 'half-step' mode. This just emits an event
- * at both the 0-0 and 1-1 positions. This might be useful for some
- * encoders where you want to detect all positions.
- *
- * If an invalid state happens (for example we go from '0-1' straight
- * to '1-0'), the state machine resets to the start until 0-0 and the
- * next valid codes occur.
- *
- * The biggest advantage of using a state machine over other algorithms
- * is that this has inherent debounce built in. Other algorithms emit spurious
- * output with switch bounce, but this one will simply flip between
- * sub-states until the bounce settles, then continue along the state
- * machine.
- * A side effect of debounce is that fast rotations can cause steps to
- * be skipped. By not requiring debounce, fast rotations can be accurately
- * measured.
- * Another advantage is the ability to properly handle bad state, such
- * as due to EMI, etc.
- * It is also a lot simpler than others - a static state table and less
- * than 10 lines of logic.
- */
- #include "rotary_encoder.h"
- #include "esp_log.h"
- #include "driver/gpio.h"
- #include "gpio_exp.h"
- #define TAG "rotary_encoder"
- //#define ROTARY_ENCODER_DEBUG
- // Use a single-item queue so that the last value can be easily overwritten by the interrupt handler
- #define EVENT_QUEUE_LENGTH 1
- #define TABLE_ROWS 7
- #define DIR_NONE 0x0 // No complete step yet.
- #define DIR_CW 0x10 // Clockwise step.
- #define DIR_CCW 0x20 // Anti-clockwise step.
- // Create the half-step state table (emits a code at 00 and 11)
- #define R_START 0x0
- #define H_CCW_BEGIN 0x1
- #define H_CW_BEGIN 0x2
- #define H_START_M 0x3
- #define H_CW_BEGIN_M 0x4
- #define H_CCW_BEGIN_M 0x5
- static const uint8_t _ttable_half[TABLE_ROWS][TABLE_COLS] = {
- // 00 01 10 11 // BA
- {H_START_M, H_CW_BEGIN, H_CCW_BEGIN, R_START}, // R_START (00)
- {H_START_M | DIR_CCW, R_START, H_CCW_BEGIN, R_START}, // H_CCW_BEGIN
- {H_START_M | DIR_CW, H_CW_BEGIN, R_START, R_START}, // H_CW_BEGIN
- {H_START_M, H_CCW_BEGIN_M, H_CW_BEGIN_M, R_START}, // H_START_M (11)
- {H_START_M, H_START_M, H_CW_BEGIN_M, R_START | DIR_CW}, // H_CW_BEGIN_M
- {H_START_M, H_CCW_BEGIN_M, H_START_M, R_START | DIR_CCW}, // H_CCW_BEGIN_M
- };
- // Create the full-step state table (emits a code at 00 only)
- # define F_CW_FINAL 0x1
- # define F_CW_BEGIN 0x2
- # define F_CW_NEXT 0x3
- # define F_CCW_BEGIN 0x4
- # define F_CCW_FINAL 0x5
- # define F_CCW_NEXT 0x6
- static const uint8_t _ttable_full[TABLE_ROWS][TABLE_COLS] = {
- // 00 01 10 11 // BA
- {R_START, F_CW_BEGIN, F_CCW_BEGIN, R_START}, // R_START
- {F_CW_NEXT, R_START, F_CW_FINAL, R_START | DIR_CW}, // F_CW_FINAL
- {F_CW_NEXT, F_CW_BEGIN, R_START, R_START}, // F_CW_BEGIN
- {F_CW_NEXT, F_CW_BEGIN, F_CW_FINAL, R_START}, // F_CW_NEXT
- {F_CCW_NEXT, R_START, F_CCW_BEGIN, R_START}, // F_CCW_BEGIN
- {F_CCW_NEXT, F_CCW_FINAL, R_START, R_START | DIR_CCW}, // F_CCW_FINAL
- {F_CCW_NEXT, F_CCW_FINAL, F_CCW_BEGIN, R_START}, // F_CCW_NEXT
- };
- static uint8_t _process(rotary_encoder_info_t * info)
- {
- uint8_t event = 0;
- if (info != NULL)
- {
- // Get state of input pins.
- uint8_t pin_state = (gpio_get_level_x(info->pin_b) << 1) | gpio_get_level_x(info->pin_a);
- // Determine new state from the pins and state table.
- #ifdef ROTARY_ENCODER_DEBUG
- uint8_t old_state = info->table_state;
- #endif
- info->table_state = info->table[info->table_state & 0xf][pin_state];
- // Return emit bits, i.e. the generated event.
- event = info->table_state & 0x30;
- #ifdef ROTARY_ENCODER_DEBUG
- ESP_EARLY_LOGD(TAG, "BA %d%d, state 0x%02x, new state 0x%02x, event 0x%02x",
- pin_state >> 1, pin_state & 1, old_state, info->table_state, event);
- #endif
- }
- return event;
- }
- static void _isr_rotenc(void * args)
- {
- rotary_encoder_info_t * info = (rotary_encoder_info_t *)args;
- uint8_t event = _process(info);
- bool send_event = false;
- switch (event)
- {
- case DIR_CW:
- ++info->state.position;
- info->state.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE;
- send_event = true;
- break;
- case DIR_CCW:
- --info->state.position;
- info->state.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE;
- send_event = true;
- break;
- default:
- break;
- }
- if (send_event && info->queue)
- {
- rotary_encoder_event_t queue_event =
- {
- .state =
- {
- .position = info->state.position,
- .direction = info->state.direction,
- },
- };
- if (info->pin_a < GPIO_NUM_MAX) {
- BaseType_t task_woken = pdFALSE;
- xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
- if (task_woken)
- {
- portYIELD_FROM_ISR();
- }
- }
- else
- {
- xQueueOverwrite(info->queue, &queue_event);
- }
- }
- }
- esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b)
- {
- esp_err_t err = ESP_OK;
- if (info)
- {
- info->pin_a = pin_a;
- info->pin_b = pin_b;
- info->table = &_ttable_full[0]; //enable_half_step ? &_ttable_half[0] : &_ttable_full[0];
- info->table_state = R_START;
- info->state.position = 0;
- info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
- // configure GPIOs
- gpio_pad_select_gpio_x(info->pin_a);
- gpio_set_pull_mode_x(info->pin_a, GPIO_PULLUP_ONLY);
- gpio_set_direction_x(info->pin_a, GPIO_MODE_INPUT);
- gpio_set_intr_type_x(info->pin_a, GPIO_INTR_ANYEDGE);
- gpio_pad_select_gpio_x(info->pin_b);
- gpio_set_pull_mode_x(info->pin_b, GPIO_PULLUP_ONLY);
- gpio_set_direction_x(info->pin_b, GPIO_MODE_INPUT);
- gpio_set_intr_type_x(info->pin_b, GPIO_INTR_ANYEDGE);
- // install interrupt handlers
- gpio_isr_handler_add_x(info->pin_a, _isr_rotenc, info);
- gpio_isr_handler_add_x(info->pin_b, _isr_rotenc, info);
- }
- else
- {
- ESP_LOGE(TAG, "info is NULL");
- err = ESP_ERR_INVALID_ARG;
- }
- return err;
- }
- esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable)
- {
- esp_err_t err = ESP_OK;
- if (info)
- {
- info->table = enable ? &_ttable_half[0] : &_ttable_full[0];
- info->table_state = R_START;
- }
- else
- {
- ESP_LOGE(TAG, "info is NULL");
- err = ESP_ERR_INVALID_ARG;
- }
- return err;
- }
- esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info)
- {
- esp_err_t err = ESP_OK;
- if (info)
- {
- gpio_num_t temp = info->pin_a;
- info->pin_a = info->pin_b;
- info->pin_b = temp;
- }
- else
- {
- ESP_LOGE(TAG, "info is NULL");
- err = ESP_ERR_INVALID_ARG;
- }
- return err;
- }
- esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
- {
- esp_err_t err = ESP_OK;
- if (info)
- {
- gpio_isr_handler_remove_x(info->pin_a);
- gpio_isr_handler_remove_x(info->pin_b);
- }
- else
- {
- ESP_LOGE(TAG, "info is NULL");
- err = ESP_ERR_INVALID_ARG;
- }
- return err;
- }
- QueueHandle_t rotary_encoder_create_queue(void)
- {
- return xQueueCreate(EVENT_QUEUE_LENGTH, sizeof(rotary_encoder_event_t));
- }
- esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue)
- {
- esp_err_t err = ESP_OK;
- if (info)
- {
- info->queue = queue;
- }
- else
- {
- ESP_LOGE(TAG, "info is NULL");
- err = ESP_ERR_INVALID_ARG;
- }
- return err;
- }
- esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state)
- {
- esp_err_t err = ESP_OK;
- if (info && state)
- {
- // make a snapshot of the state
- state->position = info->state.position;
- state->direction = info->state.direction;
- }
- else
- {
- ESP_LOGE(TAG, "info and/or state is NULL");
- err = ESP_ERR_INVALID_ARG;
- }
- return err;
- }
- esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info)
- {
- esp_err_t err = ESP_OK;
- if (info)
- {
- info->state.position = 0;
- info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
- }
- else
- {
- ESP_LOGE(TAG, "info is NULL");
- err = ESP_ERR_INVALID_ARG;
- }
- return err;
- }
|