| 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 0x5static 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  0x6static 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;}
 |