diff --git a/src/encoder.c b/src/encoder.c
index 8e6795e..1135881 100644
--- a/src/encoder.c
+++ b/src/encoder.c
@@ -2,252 +2,278 @@
#include "wvr_pins.h"
#include "ws_log.h"
#include "encoder.h"
+#include "rotary_encoder.h"
-// #define ROT_ENC_A_GPIO D9
-// #define ROT_ENC_B_GPIO D10
-// #define GPIO_MASK ((1ULL<.
+ */
+
+/**
+ * @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 "Arduino.h"
+#include "rotary_encoder.h"
+
+#include "esp_log.h"
+#include "driver/gpio.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
+
+DRAM_ATTR 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
+
+DRAM_ATTR 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
+};
+
+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(info->pin_b) << 1) | gpio_get_level(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;
+}
+
+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,
+ },
+ };
+ BaseType_t task_woken = pdFALSE;
+ xQueueOverwriteFromISR(info->queue, &queue_event, NULL);
+ if (task_woken)
+ {
+ portYIELD_FROM_ISR();
+ }
+ }
+}
+
+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
+ pinMode(pin_a, INPUT);
+ pinMode(pin_b, INPUT);
+ attachInterruptArg((uint8_t)pin_a, _isr_rotenc, (void*)info, CHANGE);
+ attachInterruptArg((uint8_t)pin_b, _isr_rotenc, (void*)info, CHANGE);
+
+ }
+ 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(info->pin_a);
+ gpio_isr_handler_remove(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;
+}
\ No newline at end of file
diff --git a/src/rotary_encoder.h b/src/rotary_encoder.h
new file mode 100644
index 0000000..6953a32
--- /dev/null
+++ b/src/rotary_encoder.h
@@ -0,0 +1,172 @@
+/*
+ * 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 .
+ */
+
+/**
+ * @file rotary_encoder.h
+ * @brief Interface definitions for the ESP32-compatible Incremental Rotary Encoder component.
+ *
+ * This component provides a means to interface with a typical rotary encoder such as the EC11 or LPD3806.
+ * These encoders produce a quadrature signal on two outputs, which can be used to track the position and
+ * direction as movement occurs.
+ *
+ * This component provides functions to initialise the GPIOs and install appropriate interrupt handlers to
+ * track a single device's position. An event queue is used to provide a way for a user task to obtain
+ * position information from the component as it is generated.
+ *
+ * Note that the queue is of length 1, and old values will be overwritten. Using a longer queue is
+ * possible with some minor modifications however newer values are lost if the queue overruns. A circular
+ * buffer where old values are lost would be better (maybe StreamBuffer in FreeRTOS 10.0.0?).
+ */
+
+#ifndef ROTARY_ENCODER_H
+#define ROTARY_ENCODER_H
+
+#include
+#include
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "esp_err.h"
+#include "driver/gpio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int32_t rotary_encoder_position_t;
+
+/**
+ * @brief Enum representing the direction of rotation.
+ */
+typedef enum
+{
+ ROTARY_ENCODER_DIRECTION_NOT_SET = 0, ///< Direction not yet known (stationary since reset)
+ ROTARY_ENCODER_DIRECTION_CLOCKWISE,
+ ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE,
+} rotary_encoder_direction_t;
+
+// Used internally
+///@cond INTERNAL
+#define TABLE_COLS 4
+typedef uint8_t table_row_t[TABLE_COLS];
+///@endcond
+
+/**
+ * @brief Struct represents the current state of the device in terms of incremental position and direction of last movement
+ */
+typedef struct
+{
+ rotary_encoder_position_t position; ///< Numerical position since reset. This value increments on clockwise rotation, and decrements on counter-clockewise rotation. Counts full or half steps depending on mode. Set to zero on reset.
+ rotary_encoder_direction_t direction; ///< Direction of last movement. Set to NOT_SET on reset.
+} rotary_encoder_state_t;
+
+/**
+ * @brief Struct carries all the information needed by this driver to manage the rotary encoder device.
+ * The fields of this structure should not be accessed directly.
+ */
+typedef struct
+{
+ gpio_num_t pin_a; ///< GPIO for Signal A from the rotary encoder device
+ gpio_num_t pin_b; ///< GPIO for Signal B from the rotary encoder device
+ QueueHandle_t queue; ///< Handle for event queue, created by ::rotary_encoder_create_queue
+ const table_row_t * table; ///< Pointer to active state transition table
+ uint8_t table_state; ///< Internal state
+ volatile rotary_encoder_state_t state; ///< Device state
+} rotary_encoder_info_t;
+
+/**
+ * @brief Struct represents a queued event, used to communicate current position to a waiting task
+ */
+typedef struct
+{
+ rotary_encoder_state_t state; ///< The device state corresponding to this event
+} rotary_encoder_event_t;
+
+/**
+ * @brief Initialise the rotary encoder device with the specified GPIO pins and full step increments.
+ * This function will set up the GPIOs as needed,
+ * Note: this function assumes that gpio_install_isr_service(0) has already been called.
+ * @param[in, out] info Pointer to allocated rotary encoder info structure.
+ * @param[in] pin_a GPIO number for rotary encoder output A.
+ * @param[in] pin_b GPIO number for rotary encoder output B.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b);
+
+/**
+ * @brief Enable half-stepping mode. This generates twice as many counted steps per rotation.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @param[in] enable If true, count half steps. If false, only count full steps.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable);
+
+/**
+ * @brief Reverse (flip) the sense of the direction.
+ * Use this if clockwise/counterclockwise are not what you expect.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info);
+
+/**
+ * @brief Remove the interrupt handlers installed by ::rotary_encoder_init.
+ * Note: GPIOs will be left in the state they were configured by ::rotary_encoder_init.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info);
+
+/**
+ * @brief Create a queue handle suitable for use as an event queue.
+ * @return A handle to a new queue suitable for use as an event queue.
+ */
+QueueHandle_t rotary_encoder_create_queue(void);
+
+/**
+ * @brief Set the driver to use the specified queue as an event queue.
+ * It is recommended that a queue constructed by ::rotary_encoder_create_queue is used.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @param[in] queue Handle to queue suitable for use as an event queue. See ::rotary_encoder_create_queue.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue);
+
+/**
+ * @brief Get the current position of the rotary encoder.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @param[in, out] state Pointer to an allocated rotary_encoder_state_t struct that will
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state);
+
+/**
+ * @brief Reset the current position of the rotary encoder to zero.
+ * @param[in] info Pointer to initialised rotary encoder info structure.
+ * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
+ */
+esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ROTARY_ENCODER_H
\ No newline at end of file
diff --git a/src/wvr_0.3.h b/src/wvr_0.3.h
index 400f59e..d92b0f9 100644
--- a/src/wvr_0.3.h
+++ b/src/wvr_0.3.h
@@ -1,7 +1,7 @@
#ifndef WVR_0_3_H
#define WVR_0_3_H
-#define VERSION_CODE "3.7.2"
+#define VERSION_CODE "3.8.0"
void wvr_init(bool useFTDI, bool useUsbMidi, bool checkRecoveryModePin);