use rotary encoder lib
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
idf_component_register(SRCS "H2201_dispatcher.c" "H2201_bluetooth.c" "H2201_buttons.c" "H2201_i2c.c" "H2201_a2dp.c" "H2201_i2s.c"
|
||||
idf_component_register(SRCS "rotary_encoder.c" "H2201_dispatcher.c" "H2201_bluetooth.c" "H2201_buttons.c" "H2201_i2c.c" "H2201_a2dp.c" "H2201_i2s.c"
|
||||
|
||||
"main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include "driver/adc.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#include "rotary_encoder.h"
|
||||
#include "H2201_dispatcher.h"
|
||||
#include "H2201_buttons.h"
|
||||
#include "H2201_i2c.h"
|
||||
@@ -36,7 +38,8 @@ static void h2201_volume_subtract(uint8_t *volume)
|
||||
|
||||
static void h2201_buttons_eventhandler(uint16_t event, void *p_param)
|
||||
{
|
||||
ESP_LOGD(H2201_BUTTONS_TAG, "%s evt %d", __func__, event);
|
||||
ESP_LOGI(H2201_BUTTONS_TAG, "%s evt %d", __func__, event);
|
||||
|
||||
switch (event)
|
||||
{
|
||||
case BUTTON_PRESS_EVENT:;
|
||||
@@ -51,6 +54,7 @@ static void h2201_buttons_eventhandler(uint16_t event, void *p_param)
|
||||
case ENCODER_UP_EVENT:
|
||||
if (current_button == MASTER_VOLUME_BUTTON)
|
||||
{
|
||||
printf("m add vol %u\n",master_volume);
|
||||
h2201_volume_add(&master_volume, MASTER_VOLUME_0DB);
|
||||
H2201_i2c_mastervolume(master_volume);
|
||||
}
|
||||
@@ -66,6 +70,7 @@ static void h2201_buttons_eventhandler(uint16_t event, void *p_param)
|
||||
}
|
||||
if (current_button == BLUETOOTH_VOLUME_BUTTON)
|
||||
{
|
||||
printf("bt add vol %u\n",bluetooth_volume);
|
||||
h2201_volume_add(&bluetooth_volume, BLUETOOTH_VOLUME_0DB);
|
||||
H2201_i2c_bluetoothvolume(bluetooth_volume);
|
||||
}
|
||||
@@ -73,6 +78,7 @@ static void h2201_buttons_eventhandler(uint16_t event, void *p_param)
|
||||
case ENCODER_DOWN_EVENT:
|
||||
if (current_button == MASTER_VOLUME_BUTTON)
|
||||
{
|
||||
printf("m sub vol %u\n",master_volume);
|
||||
h2201_volume_subtract(&master_volume);
|
||||
H2201_i2c_mastervolume(master_volume);
|
||||
}
|
||||
@@ -88,6 +94,7 @@ static void h2201_buttons_eventhandler(uint16_t event, void *p_param)
|
||||
}
|
||||
if (current_button == BLUETOOTH_VOLUME_BUTTON)
|
||||
{
|
||||
printf("bt sub vol %u\n",bluetooth_volume);
|
||||
h2201_volume_subtract(&bluetooth_volume);
|
||||
H2201_i2c_bluetoothvolume(bluetooth_volume);
|
||||
}
|
||||
@@ -97,17 +104,6 @@ static void h2201_buttons_eventhandler(uint16_t event, void *p_param)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// // printf("1=%d ", H2201_dipswitch_read(1));
|
||||
// // printf("2=%d ", H2201_dipswitch_read(2));
|
||||
// // printf("3=%d ", H2201_dipswitch_read(3));
|
||||
// // printf("4=%d ", H2201_dipswitch_read(4));
|
||||
// // printf("5=%d ", H2201_dipswitch_read(5));
|
||||
// // printf("6=%d ", H2201_dipswitch_read(6));
|
||||
// // printf("7=%d ", H2201_dipswitch_read(7));
|
||||
// // printf("8=%d\n", H2201_dipswitch_read(8));
|
||||
// }
|
||||
// }
|
||||
//******************* shiftregsiter ********************************
|
||||
void H2201_shiftregister_init(void)
|
||||
{
|
||||
@@ -176,64 +172,40 @@ void H2201_buttons_init(void)
|
||||
gpio_isr_handler_add(BLUETOOTH_VOLUME_BUTTON, H2201_buttons_ISR, (void *)BLUETOOTH_VOLUME_BUTTON);
|
||||
}
|
||||
//******************* encoder ********************************
|
||||
bool IRAM_ATTR H2201_encoder_ISR(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
|
||||
QueueHandle_t encoder_event_queue;
|
||||
rotary_encoder_info_t encoder_info;
|
||||
|
||||
static void H2201_encoder_task(void *arg)
|
||||
{
|
||||
if (edata->watch_point_value == 1)
|
||||
for (;;)
|
||||
{
|
||||
return h2201_dispatcher_dowork_simple_fromISR(h2201_buttons_eventhandler, ENCODER_UP_EVENT);
|
||||
}
|
||||
else
|
||||
{
|
||||
return h2201_dispatcher_dowork_simple_fromISR(h2201_buttons_eventhandler, ENCODER_DOWN_EVENT);
|
||||
rotary_encoder_event_t event = {0};
|
||||
if (xQueueReceive(encoder_event_queue, &event, (TickType_t)portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
ESP_LOGI(H2201_BUTTONS_TAG, "Event: direction %s", event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
|
||||
if (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE)
|
||||
{
|
||||
h2201_dispatcher_dowork_simple_fromISR(h2201_buttons_eventhandler, ENCODER_DOWN_EVENT);
|
||||
}
|
||||
if (event.state.direction == ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE)
|
||||
{
|
||||
h2201_dispatcher_dowork_simple_fromISR(h2201_buttons_eventhandler, ENCODER_UP_EVENT);
|
||||
}
|
||||
ESP_ERROR_CHECK(rotary_encoder_reset(&encoder_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void H2201_encoder_init(void)
|
||||
{
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.high_limit = ROTARY_HIGH_LIMIT,
|
||||
.low_limit = ROTARY_LOW_LIMIT,
|
||||
};
|
||||
pcnt_unit_handle_t pcnt_unit = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));
|
||||
{
|
||||
ESP_ERROR_CHECK(rotary_encoder_init(&encoder_info, ROTARY_A_GPIO, ROTARY_B_GPIO));
|
||||
// ESP_ERROR_CHECK(rotary_encoder_enable_half_steps(&info, ENABLE_HALF_STEPS));
|
||||
// #ifdef FLIP_DIRECTION
|
||||
// ESP_ERROR_CHECK(rotary_encoder_flip_direction(&info));
|
||||
// #endif
|
||||
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = ROTARY_GLITCH_NS,
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
|
||||
encoder_event_queue = rotary_encoder_create_queue();
|
||||
ESP_ERROR_CHECK(rotary_encoder_set_queue(&encoder_info, encoder_event_queue));
|
||||
|
||||
pcnt_chan_config_t chan_a_config = {
|
||||
.edge_gpio_num = ROTARY_A_GPIO,
|
||||
.level_gpio_num = ROTARY_B_GPIO,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_a = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_a_config, &pcnt_chan_a));
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
|
||||
pcnt_chan_config_t chan_b_config = {
|
||||
.edge_gpio_num = ROTARY_B_GPIO,
|
||||
.level_gpio_num = ROTARY_A_GPIO,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_b = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_b_config, &pcnt_chan_b));
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
|
||||
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
|
||||
|
||||
int watch_points[] = {ROTARY_LOW_LIMIT, ROTARY_HIGH_LIMIT};
|
||||
for (size_t i = 0; i < sizeof(watch_points) / sizeof(watch_points[0]); i++)
|
||||
{
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, watch_points[i]));
|
||||
}
|
||||
|
||||
pcnt_event_callbacks_t cbs = {
|
||||
.on_reach = H2201_encoder_ISR,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, NULL));
|
||||
|
||||
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
|
||||
xTaskCreate(H2201_encoder_task, "encoderT", 2048, NULL, configMAX_PRIORITIES - 5, NULL);
|
||||
}
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
#define ROTARY_A_GPIO 27
|
||||
#define ROTARY_B_GPIO 32
|
||||
#define ROTARY_GLITCH_NS 10000
|
||||
#define ROTARY_HIGH_LIMIT 1
|
||||
#define ROTARY_LOW_LIMIT -1
|
||||
//#define ROTARY_GLITCH_NS 10000
|
||||
//#define ROTARY_HIGH_LIMIT 1
|
||||
//#define ROTARY_LOW_LIMIT -1
|
||||
|
||||
#define SHIFT_CLOCK 19
|
||||
#define SHIFT_DATA 18
|
||||
@@ -33,6 +33,7 @@
|
||||
#define BUTTON_PRESS_EVENT 1
|
||||
#define ENCODER_UP_EVENT 2
|
||||
#define ENCODER_DOWN_EVENT 3
|
||||
//#define ENCODER_EVENT 4
|
||||
|
||||
void H2201_shiftregister_init(void);
|
||||
bool H2201_dipswitch_read(int sw);
|
||||
|
||||
@@ -146,7 +146,7 @@ static void H2201_dispatcher_task(void *arg)
|
||||
if (xQueueReceive(h2201_dispatcher_queue, &msg, (TickType_t)portMAX_DELAY))
|
||||
{
|
||||
// ESP_LOGI(H2201_DISPATCHER_TAG, "sig 0x%x, event 0x%x, param 0x%x ", msg.sig, msg.callback, msg.param);
|
||||
ESP_LOGI(H2201_DISPATCHER_TAG, "sig 0x%x", msg.sig);
|
||||
//ESP_LOGI(H2201_DISPATCHER_TAG, "sig 0x%x", msg.sig);
|
||||
switch (msg.sig)
|
||||
{
|
||||
case H2201_DISPATCHER_SIG_DOWORK:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#define H2201_DISPATCHER_TAG "H2201_DISPATCHER"
|
||||
|
||||
#define H2201_DISPATCHER_QUEUE_SIZE 30
|
||||
#define H2201_DISPATCHER_QUEUE_SIZE 50
|
||||
|
||||
#define H2201_DISPATCHER_SIG_DOWORK (0x01)
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ void H2201_i2c_mastervolume_task(void *arg)
|
||||
uint8_t volume = *((uint8_t *)arg);
|
||||
if (xSemaphoreTake(i2c_block_mutex, (TickType_t)portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
printf("master vol %u\n",volume);
|
||||
|
||||
uint8_t vol = volume << R30_PB_HEADPHONE_LEFT_VOL_IC1_SHIFT;
|
||||
vol |= (1 << R30_PB_HEADPHONE_LEFT_PWRUP_IC1_SHIFT); // Headphone volume control enable.
|
||||
vol |= (1 << R30_PB_HEADPHONE_LEFT_MUTE_IC1_SHIFT); // unmute
|
||||
@@ -65,6 +67,7 @@ void H2201_i2c_telephonevolume_task(void *arg)
|
||||
uint8_t volume = *((uint8_t *)arg);
|
||||
if (xSemaphoreTake(i2c_block_mutex, (TickType_t)portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
printf("telephone vol %u\n",volume);
|
||||
// printf("switch left %d\n", H2201_dipswitch_read(DIPSWITCH_TEL_LEFT));
|
||||
uint8_t vol = 0;
|
||||
if (H2201_dipswitch_read(DIPSWITCH_TEL_LEFT))
|
||||
@@ -105,6 +108,8 @@ void H2201_i2c_pcvolume_task(void *arg)
|
||||
uint8_t volume = *((uint8_t *)arg);
|
||||
if (xSemaphoreTake(i2c_block_mutex, (TickType_t)portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
printf("pc vol %u\n",volume);
|
||||
|
||||
bool left = H2201_dipswitch_read(DIPSWITCH_PC_LEFT);
|
||||
bool right = H2201_dipswitch_read(DIPSWITCH_PC_RIGHT);
|
||||
|
||||
@@ -171,7 +176,7 @@ void H2201_i2c_bluetoothvolume_task(void *arg)
|
||||
ADI_REG_TYPE volume[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
volume[3] = *((uint8_t *)arg);
|
||||
// printf("bluetooth vol %d\n", volume[3]);
|
||||
printf("bluetooth vol %d\n", volume[3]);
|
||||
|
||||
if (left == false && right == false)
|
||||
{
|
||||
|
||||
@@ -1,27 +1,5 @@
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//#include <stdio.h>
|
||||
//#include <stdlib.h>
|
||||
//#include <unistd.h>
|
||||
//#include <string.h>
|
||||
//#include "freertos/FreeRTOS.h"
|
||||
//#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
//#include "esp_system.h"
|
||||
//#include "esp_log.h"
|
||||
|
||||
#include "H2201_dispatcher.h"
|
||||
#include "H2201_i2c.h"
|
||||
@@ -52,6 +30,6 @@ void app_main(void)
|
||||
h2201_bluetooth_init();
|
||||
|
||||
H2201_shiftregister_init();
|
||||
H2201_buttons_init();
|
||||
H2201_encoder_init();
|
||||
H2201_buttons_init();
|
||||
}
|
||||
352
ESP32/main/rotary_encoder.c
Normal file
352
ESP32/main/rotary_encoder.c
Normal file
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#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(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;
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
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)
|
||||
{
|
||||
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(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_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_ANYEDGE;
|
||||
io_conf.pin_bit_mask = ((1ULL << info->pin_a) | (1ULL << info->pin_b));
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// install interrupt handlers
|
||||
gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
|
||||
gpio_isr_handler_add(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(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;
|
||||
}
|
||||
172
ESP32/main/rotary_encoder.h
Normal file
172
ESP32/main/rotary_encoder.h
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
@@ -614,7 +614,7 @@ CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT=y
|
||||
# GPIO Configuration
|
||||
#
|
||||
# CONFIG_GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL is not set
|
||||
# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set
|
||||
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
|
||||
# end of GPIO Configuration
|
||||
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user