mirror of
https://github.com/Valeh2012/PersonalVotingMachine
synced 2024-11-25 18:31:00 +02:00
381 lines
12 KiB
C
381 lines
12 KiB
C
/*
|
|
* 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 <time.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.
|
|
#define CLCKD 0x30
|
|
|
|
#define SW_DEBOUNCE 100
|
|
|
|
// 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;
|
|
uint32_t t = (uint32_t) (clock() * 1000 / CLOCKS_PER_SEC);
|
|
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);
|
|
uint8_t sw_state = 1 - gpio_get_level(info->pin_sw);
|
|
if(sw_state == 1){
|
|
info->state.swState = 1;
|
|
}
|
|
if(info->state.swState == 1 && info->state.swDebounce == 0){
|
|
info->state.swLastTime = t;
|
|
info->state.swDebounce = 1;
|
|
}
|
|
if(info->state.swDebounce == 1 && (t - info->state.swLastTime > SW_DEBOUNCE) ){
|
|
info->state.swState = 0;
|
|
info->state.swDebounce = 0;
|
|
if(sw_state == 0 ){
|
|
return CLCKD;
|
|
}
|
|
}
|
|
// 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;
|
|
bool clicked = 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;
|
|
case CLCKD:
|
|
clicked = true;
|
|
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,
|
|
.clicked = clicked,
|
|
},
|
|
};
|
|
BaseType_t task_woken = pdFALSE;
|
|
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
|
|
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, gpio_num_t pin_c)
|
|
{
|
|
esp_err_t err = ESP_OK;
|
|
if (info)
|
|
{
|
|
info->pin_a = pin_a;
|
|
info->pin_b = pin_b;
|
|
info->pin_sw = pin_c;
|
|
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;
|
|
info->state.swLastTime = 0;
|
|
info->state.swState = 0;
|
|
info->state.swDebounce = 0;
|
|
|
|
|
|
// configure GPIOs
|
|
gpio_pad_select_gpio(info->pin_a);
|
|
gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY);
|
|
gpio_set_direction(info->pin_a, GPIO_MODE_INPUT);
|
|
gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE);
|
|
|
|
gpio_pad_select_gpio(info->pin_b);
|
|
gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY);
|
|
gpio_set_direction(info->pin_b, GPIO_MODE_INPUT);
|
|
gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE);
|
|
|
|
gpio_pad_select_gpio(info->pin_sw);
|
|
gpio_set_pull_mode(info->pin_sw, GPIO_PULLUP_ONLY);
|
|
gpio_set_direction(info->pin_sw, GPIO_MODE_INPUT);
|
|
gpio_set_intr_type(info->pin_sw, GPIO_INTR_ANYEDGE);
|
|
|
|
// install interrupt handlers
|
|
gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
|
|
gpio_isr_handler_add(info->pin_b, _isr_rotenc, info);
|
|
gpio_isr_handler_add(info->pin_sw, _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(info->pin_a);
|
|
gpio_isr_handler_remove(info->pin_b);
|
|
gpio_isr_handler_remove(info->pin_sw);
|
|
}
|
|
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;
|
|
}
|